This commit is contained in:
parent
d43406380c
commit
539b58ea87
3
admin/components.d.ts
vendored
3
admin/components.d.ts
vendored
|
|
@ -52,4 +52,7 @@ declare module 'vue' {
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
}
|
}
|
||||||
|
export interface GlobalDirectives {
|
||||||
|
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,4 @@ export * from './points'
|
||||||
export * from './stamp'
|
export * from './stamp'
|
||||||
export * from './upload'
|
export * from './upload'
|
||||||
export * from './user'
|
export * from './user'
|
||||||
|
export * from './testAccount'
|
||||||
|
|
|
||||||
18
admin/src/api/testAccount.ts
Normal file
18
admin/src/api/testAccount.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 测试账号 API
|
||||||
|
|
||||||
|
/** 获取测试账号列表 */
|
||||||
|
export function getTestAccounts() {
|
||||||
|
return request.get('/test-account')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 新增/更新测试账号 */
|
||||||
|
export function saveTestAccount(data: { phone: string; code: string }) {
|
||||||
|
return request.post('/test-account', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除测试账号 */
|
||||||
|
export function deleteTestAccount(phone: string) {
|
||||||
|
return request.delete(`/test-account/${encodeURIComponent(phone)}`)
|
||||||
|
}
|
||||||
|
|
@ -96,6 +96,7 @@ const menuItems = [
|
||||||
{ path: '/points', title: '积分配置', icon: 'Coin' },
|
{ path: '/points', title: '积分配置', icon: 'Coin' },
|
||||||
{ path: '/user', title: '用户管理', icon: 'User' },
|
{ path: '/user', title: '用户管理', icon: 'User' },
|
||||||
{ path: '/content', title: '内容管理', icon: 'Document' },
|
{ path: '/content', title: '内容管理', icon: 'Document' },
|
||||||
|
{ path: '/test-account', title: '测试账号', icon: 'Iphone' },
|
||||||
]
|
]
|
||||||
|
|
||||||
function handleCommand(command: string) {
|
function handleCommand(command: string) {
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,12 @@ const routes: RouteRecordRaw[] = [
|
||||||
component: () => import('@/views/content/index.vue'),
|
component: () => import('@/views/content/index.vue'),
|
||||||
meta: { title: '内容管理', icon: 'Document' },
|
meta: { title: '内容管理', icon: 'Document' },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'test-account',
|
||||||
|
name: 'TestAccount',
|
||||||
|
component: () => import('@/views/test-account/index.vue'),
|
||||||
|
meta: { title: '测试账号', icon: 'Iphone' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
||||||
123
admin/src/views/test-account/index.vue
Normal file
123
admin/src/views/test-account/index.vue
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
<template>
|
||||||
|
<div class="test-account">
|
||||||
|
<!-- 添加表单 -->
|
||||||
|
<el-card shadow="never" style="margin-bottom: 20px">
|
||||||
|
<template #header>
|
||||||
|
<span>添加测试账号</span>
|
||||||
|
</template>
|
||||||
|
<el-form :model="form" :rules="rules" ref="formRef" inline>
|
||||||
|
<el-form-item label="手机号" prop="phone">
|
||||||
|
<el-input v-model="form.phone" placeholder="请输入手机号" style="width: 200px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="验证码" prop="code">
|
||||||
|
<el-input v-model="form.code" placeholder="请输入固定验证码" style="width: 160px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" :loading="saving" @click="handleSave">保存</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 列表 -->
|
||||||
|
<el-table :data="list" border style="width: 100%" v-loading="loading">
|
||||||
|
<el-table-column label="手机号" prop="phone" width="200" />
|
||||||
|
<el-table-column label="验证码" prop="code" width="160" />
|
||||||
|
<el-table-column label="创建时间" min-width="180">
|
||||||
|
<template #default="{ row }">{{ formatDate(row.createdAt) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="120" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button size="small" type="danger" @click="handleDelete(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { getTestAccounts, saveTestAccount, deleteTestAccount } from '@/api/testAccount'
|
||||||
|
|
||||||
|
interface TestAccount {
|
||||||
|
phone: string
|
||||||
|
code: string
|
||||||
|
createdAt?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
const form = reactive({ phone: '', code: '' })
|
||||||
|
const rules: FormRules = {
|
||||||
|
phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
|
||||||
|
code: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = ref<TestAccount[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const saving = ref(false)
|
||||||
|
|
||||||
|
// 格式化日期
|
||||||
|
function formatDate(dateStr: string) {
|
||||||
|
if (!dateStr) return '-'
|
||||||
|
return new Date(dateStr).toLocaleString('zh-CN')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载列表
|
||||||
|
async function loadList() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res: any = await getTestAccounts()
|
||||||
|
list.value = res.data || []
|
||||||
|
} catch {
|
||||||
|
// 错误已由拦截器处理
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存
|
||||||
|
async function handleSave() {
|
||||||
|
if (!formRef.value) return
|
||||||
|
await formRef.value.validate()
|
||||||
|
saving.value = true
|
||||||
|
try {
|
||||||
|
await saveTestAccount({ phone: form.phone, code: form.code })
|
||||||
|
ElMessage.success('保存成功')
|
||||||
|
form.phone = ''
|
||||||
|
form.code = ''
|
||||||
|
formRef.value.resetFields()
|
||||||
|
await loadList()
|
||||||
|
} catch {
|
||||||
|
// 错误已由拦截器处理
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
async function handleDelete(row: TestAccount) {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(`确定删除测试账号 ${row.phone}?`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
})
|
||||||
|
await deleteTestAccount(row.phone)
|
||||||
|
ElMessage.success('已删除')
|
||||||
|
await loadList()
|
||||||
|
} catch {
|
||||||
|
// 用户取消或错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.test-account {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using VendingMachine.Infrastructure.Data;
|
||||||
|
using VendingMachine.Domain.Entities;
|
||||||
|
|
||||||
|
namespace VendingMachine.Api.Controllers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 管理后台 - 测试账号管理
|
||||||
|
/// </summary>
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/admin/test-account")]
|
||||||
|
[Authorize]
|
||||||
|
public class AdminTestAccountController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly AppDbContext _db;
|
||||||
|
|
||||||
|
public AdminTestAccountController(AppDbContext db)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取测试账号列表
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> GetAll()
|
||||||
|
{
|
||||||
|
var list = await _db.TestAccounts
|
||||||
|
.OrderByDescending(t => t.CreatedAt)
|
||||||
|
.ToListAsync();
|
||||||
|
return Ok(new { success = true, data = list });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 新增或更新测试账号
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> Save([FromBody] SaveTestAccountRequest req)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(req.Phone) || string.IsNullOrWhiteSpace(req.Code))
|
||||||
|
return Ok(new { success = false, message = "手机号和验证码不能为空" });
|
||||||
|
|
||||||
|
var existing = await _db.TestAccounts
|
||||||
|
.FirstOrDefaultAsync(t => t.Phone == req.Phone);
|
||||||
|
|
||||||
|
if (existing != null)
|
||||||
|
{
|
||||||
|
// 已存在则更新验证码
|
||||||
|
existing.Code = req.Code;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_db.TestAccounts.Add(new TestAccount
|
||||||
|
{
|
||||||
|
Phone = req.Phone,
|
||||||
|
Code = req.Code
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
return Ok(new { success = true, message = "保存成功" });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除测试账号
|
||||||
|
/// </summary>
|
||||||
|
[HttpDelete("{phone}")]
|
||||||
|
public async Task<IActionResult> Delete(string phone)
|
||||||
|
{
|
||||||
|
var entity = await _db.TestAccounts
|
||||||
|
.FirstOrDefaultAsync(t => t.Phone == phone);
|
||||||
|
|
||||||
|
if (entity == null)
|
||||||
|
return Ok(new { success = false, message = "测试账号不存在" });
|
||||||
|
|
||||||
|
_db.TestAccounts.Remove(entity);
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
return Ok(new { success = true, message = "已删除" });
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SaveTestAccountRequest
|
||||||
|
{
|
||||||
|
public string Phone { get; set; } = string.Empty;
|
||||||
|
public string Code { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
backend/src/VendingMachine.Domain/Entities/TestAccount.cs
Normal file
12
backend/src/VendingMachine.Domain/Entities/TestAccount.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
namespace VendingMachine.Domain.Entities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试账号实体
|
||||||
|
/// </summary>
|
||||||
|
public class TestAccount
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Phone { get; set; } = string.Empty;
|
||||||
|
public string Code { get; set; } = string.Empty;
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,7 @@ public class AppDbContext : DbContext
|
||||||
public DbSet<ContentConfig> ContentConfigs => Set<ContentConfig>();
|
public DbSet<ContentConfig> ContentConfigs => Set<ContentConfig>();
|
||||||
public DbSet<VendingPaymentRecord> VendingPaymentRecords => Set<VendingPaymentRecord>();
|
public DbSet<VendingPaymentRecord> VendingPaymentRecords => Set<VendingPaymentRecord>();
|
||||||
public DbSet<AdminUser> AdminUsers => Set<AdminUser>();
|
public DbSet<AdminUser> AdminUsers => Set<AdminUser>();
|
||||||
|
public DbSet<TestAccount> TestAccounts => Set<TestAccount>();
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
|
|
@ -115,5 +116,13 @@ public class AppDbContext : DbContext
|
||||||
e.Property(a => a.PasswordHash).HasMaxLength(200);
|
e.Property(a => a.PasswordHash).HasMaxLength(200);
|
||||||
e.HasIndex(a => a.Username).IsUnique();
|
e.HasIndex(a => a.Username).IsUnique();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<TestAccount>(e =>
|
||||||
|
{
|
||||||
|
e.HasKey(t => t.Id);
|
||||||
|
e.Property(t => t.Phone).HasMaxLength(20);
|
||||||
|
e.Property(t => t.Code).HasMaxLength(20);
|
||||||
|
e.HasIndex(t => t.Phone).IsUnique();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<AppDbConte
|
||||||
public AppDbContext CreateDbContext(string[] args)
|
public AppDbContext CreateDbContext(string[] args)
|
||||||
{
|
{
|
||||||
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>();
|
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>();
|
||||||
optionsBuilder.UseSqlServer("Server=localhost;Database=VendingMachineDb;Trusted_Connection=True;TrustServerCertificate=True;");
|
optionsBuilder.UseSqlServer("Server=tcp:192.168.195.15,1433;Database=VendingMachineDb;User Id=sa;Password=Dbt@com@123;TrustServerCertificate=True;Encrypt=False;");
|
||||||
return new AppDbContext(optionsBuilder.Options);
|
return new AppDbContext(optionsBuilder.Options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
461
backend/src/VendingMachine.Infrastructure/Data/Migrations/20260413055438_AddTestAccount.Designer.cs
generated
Normal file
461
backend/src/VendingMachine.Infrastructure/Data/Migrations/20260413055438_AddTestAccount.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,461 @@
|
||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using VendingMachine.Infrastructure.Data;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace VendingMachine.Infrastructure.Data.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20260413055438_AddTestAccount")]
|
||||||
|
partial class AddTestAccount
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.5")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||||
|
|
||||||
|
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("VendingMachine.Domain.Entities.AdminUser", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("Username")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Username")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("AdminUsers");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VendingMachine.Domain.Entities.Banner", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("ImageUrlEn")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("ImageUrlZhCn")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("ImageUrlZhTw")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("LinkType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("nvarchar(20)");
|
||||||
|
|
||||||
|
b.Property<string>("LinkUrl")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<int>("SortOrder")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Banners");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VendingMachine.Domain.Entities.ContentConfig", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("ContentEn")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("ContentZhCn")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("ContentZhTw")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.HasKey("Key");
|
||||||
|
|
||||||
|
b.ToTable("ContentConfigs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VendingMachine.Domain.Entities.CouponTemplate", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<decimal>("DiscountAmount")
|
||||||
|
.HasPrecision(18, 2)
|
||||||
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExpireAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<bool>("IsStamp")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<string>("NameEn")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("NameZhCn")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("NameZhTw")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<int>("PointsCost")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<decimal?>("ThresholdAmount")
|
||||||
|
.HasPrecision(18, 2)
|
||||||
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("CouponTemplates");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VendingMachine.Domain.Entities.HomeEntry", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("ImageUrlEn")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("ImageUrlZhCn")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("ImageUrlZhTw")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("Type")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("nvarchar(20)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("HomeEntries");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VendingMachine.Domain.Entities.MembershipProduct", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("AppleProductId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("Currency")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(10)
|
||||||
|
.HasColumnType("nvarchar(10)");
|
||||||
|
|
||||||
|
b.Property<string>("DescriptionEn")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("DescriptionZhCn")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("DescriptionZhTw")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<int>("DurationDays")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("GoogleProductId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<decimal>("Price")
|
||||||
|
.HasPrecision(18, 2)
|
||||||
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("MembershipProducts");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VendingMachine.Domain.Entities.PointRecord", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<int>("Amount")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("Source")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(12)
|
||||||
|
.HasColumnType("nvarchar(12)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("PointRecords");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VendingMachine.Domain.Entities.PointsConfig", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("ConversionRate")
|
||||||
|
.HasPrecision(18, 4)
|
||||||
|
.HasColumnType("decimal(18,4)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("PointsConfigs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VendingMachine.Domain.Entities.TestAccount", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Code")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("nvarchar(20)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("Phone")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("nvarchar(20)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Phone")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("TestAccounts");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VendingMachine.Domain.Entities.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Uid")
|
||||||
|
.HasMaxLength(12)
|
||||||
|
.HasColumnType("nvarchar(12)");
|
||||||
|
|
||||||
|
b.Property<string>("AreaCode")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(10)
|
||||||
|
.HasColumnType("nvarchar(10)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<bool>("IsMember")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<string>("Language")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(10)
|
||||||
|
.HasColumnType("nvarchar(10)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("MembershipExpireAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<int>("MembershipType")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Nickname")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("Phone")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("nvarchar(20)");
|
||||||
|
|
||||||
|
b.Property<int>("PointsBalance")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("PointsExpireAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.HasKey("Uid");
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VendingMachine.Domain.Entities.UserCoupon", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("CouponTemplateId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExpireAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UsedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(12)
|
||||||
|
.HasColumnType("nvarchar(12)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CouponTemplateId");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("UserCoupons");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VendingMachine.Domain.Entities.VendingPaymentRecord", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("MachineId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<decimal>("PaymentAmount")
|
||||||
|
.HasPrecision(18, 2)
|
||||||
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
|
b.Property<string>("PaymentStatus")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("nvarchar(20)");
|
||||||
|
|
||||||
|
b.Property<string>("TransactionId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<string>("UsedCouponId")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(12)
|
||||||
|
.HasColumnType("nvarchar(12)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("VendingPaymentRecords");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VendingMachine.Domain.Entities.UserCoupon", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VendingMachine.Domain.Entities.CouponTemplate", "CouponTemplate")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CouponTemplateId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("CouponTemplate");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace VendingMachine.Infrastructure.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddTestAccount : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "TestAccounts",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("SqlServer:Identity", "1, 1"),
|
||||||
|
Phone = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: false),
|
||||||
|
Code = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: false),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_TestAccounts", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_TestAccounts_Phone",
|
||||||
|
table: "TestAccounts",
|
||||||
|
column: "Phone",
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "TestAccounts");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -280,6 +280,35 @@ namespace VendingMachine.Infrastructure.Data.Migrations
|
||||||
b.ToTable("PointsConfigs");
|
b.ToTable("PointsConfigs");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VendingMachine.Domain.Entities.TestAccount", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Code")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("nvarchar(20)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("Phone")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("nvarchar(20)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Phone")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("TestAccounts");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("VendingMachine.Domain.Entities.User", b =>
|
modelBuilder.Entity("VendingMachine.Domain.Entities.User", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("Uid")
|
b.Property<string>("Uid")
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,9 @@ public class UserService : IUserService
|
||||||
return ApiResponse<LoginResponse>.Fail("无效的手机号格式");
|
return ApiResponse<LoginResponse>.Fail("无效的手机号格式");
|
||||||
|
|
||||||
// 验证验证码(测试账号跳过验证码校验)
|
// 验证验证码(测试账号跳过验证码校验)
|
||||||
var isTestAccount = request.Phone == "18631081161" && request.Code == "1111";
|
var testAccount = await _db.TestAccounts
|
||||||
|
.FirstOrDefaultAsync(t => t.Phone == request.Phone && t.Code == request.Code);
|
||||||
|
var isTestAccount = testAccount != null;
|
||||||
if (!isTestAccount)
|
if (!isTestAccount)
|
||||||
{
|
{
|
||||||
var key = $"sms:code:{request.AreaCode}{request.Phone}";
|
var key = $"sms:code:{request.AreaCode}{request.Phone}";
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { getStorage, removeStorage, TOKEN_KEY, LOCALE_KEY } from '../utils/stora
|
||||||
// 后端API基础地址
|
// 后端API基础地址
|
||||||
// MuMu模拟器需使用宿主机局域网IP,生产环境替换为正式域名
|
// MuMu模拟器需使用宿主机局域网IP,生产环境替换为正式域名
|
||||||
export const BASE_URL = 'http://192.168.21.7:5082'
|
export const BASE_URL = 'http://192.168.21.7:5082'
|
||||||
|
// export const BASE_URL = 'http://192.168.21.7:5082'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 统一请求封装,自动注入Token和语言请求头,统一处理响应和错误
|
* 统一请求封装,自动注入Token和语言请求头,统一处理响应和错误
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,26 @@
|
||||||
{
|
{
|
||||||
"name": "贩卖机",
|
"name" : "贩卖机",
|
||||||
"appid": "__UNI__19998C7",
|
"appid" : "__UNI__19998C7",
|
||||||
"description": "贩卖机移动端APP",
|
"description" : "贩卖机",
|
||||||
"versionName": "1.0.0",
|
"versionName" : "1.0.0",
|
||||||
"versionCode": "100",
|
"versionCode" : "100",
|
||||||
"transformPx": false,
|
"transformPx" : false,
|
||||||
"app-plus": {
|
"app-plus" : {
|
||||||
"usingComponents": true,
|
"usingComponents" : true,
|
||||||
"nvueStyleCompiler": "uni-app",
|
"nvueStyleCompiler" : "uni-app",
|
||||||
"compilerVersion": 3,
|
"compilerVersion" : 3,
|
||||||
"splashscreen": {
|
"splashscreen" : {
|
||||||
"alwaysShowBeforeRender": true,
|
"alwaysShowBeforeRender" : true,
|
||||||
"waiting": true,
|
"waiting" : true,
|
||||||
"autoclose": true,
|
"autoclose" : true,
|
||||||
"delay": 0
|
"delay" : 0
|
||||||
},
|
},
|
||||||
"modules": {
|
"modules" : {
|
||||||
"Payment": {}
|
"Payment" : {}
|
||||||
},
|
},
|
||||||
"distribute": {
|
"distribute" : {
|
||||||
"android": {
|
"android" : {
|
||||||
"permissions": [
|
"permissions" : [
|
||||||
"<uses-permission android:name=\"android.permission.INTERNET\"/>",
|
"<uses-permission android:name=\"android.permission.INTERNET\"/>",
|
||||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||||
|
|
@ -31,35 +31,35 @@
|
||||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>"
|
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>"
|
||||||
],
|
],
|
||||||
"google": {
|
"google" : {
|
||||||
"pay": true
|
"pay" : true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ios": {
|
"ios" : {
|
||||||
"capabilities": {
|
"capabilities" : {
|
||||||
"entitlements": {
|
"entitlements" : {
|
||||||
"com.apple.developer.in-app-payments": true
|
"com.apple.developer.in-app-payments" : true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sdkConfigs": {
|
"sdkConfigs" : {
|
||||||
"payment": {
|
"payment" : {
|
||||||
"google": {},
|
"google" : {},
|
||||||
"appleiap": {}
|
"appleiap" : {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"quickapp": {},
|
"quickapp" : {},
|
||||||
"mp-weixin": {
|
"mp-weixin" : {
|
||||||
"appid": "",
|
"appid" : "",
|
||||||
"setting": {
|
"setting" : {
|
||||||
"urlCheck": false
|
"urlCheck" : false
|
||||||
},
|
},
|
||||||
"usingComponents": true
|
"usingComponents" : true
|
||||||
},
|
},
|
||||||
"uniStatistics": {
|
"uniStatistics" : {
|
||||||
"enable": false
|
"enable" : false
|
||||||
},
|
},
|
||||||
"vueVersion": "3"
|
"vueVersion" : "3"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,18 +51,21 @@
|
||||||
{
|
{
|
||||||
"path": "pages/agreement/agreement",
|
"path": "pages/agreement/agreement",
|
||||||
"style": {
|
"style": {
|
||||||
|
"navigationStyle": "custom",
|
||||||
"navigationBarTitleText": "用户协议"
|
"navigationBarTitleText": "用户协议"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/privacy/privacy",
|
"path": "pages/privacy/privacy",
|
||||||
"style": {
|
"style": {
|
||||||
|
"navigationStyle": "custom",
|
||||||
"navigationBarTitleText": "隐私政策"
|
"navigationBarTitleText": "隐私政策"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/about/about",
|
"path": "pages/about/about",
|
||||||
"style": {
|
"style": {
|
||||||
|
"navigationStyle": "custom",
|
||||||
"navigationBarTitleText": "关于"
|
"navigationBarTitleText": "关于"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,28 @@
|
||||||
<template>
|
<template>
|
||||||
<view class="about-page">
|
<view class="about-page">
|
||||||
<!-- APP信息 -->
|
<!-- 自定义导航栏 -->
|
||||||
<view class="app-info">
|
<view class="custom-nav" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||||
<image class="logo" src="/static/logo.png" mode="aspectFit" />
|
<view class="nav-inner">
|
||||||
<text class="version">{{ t('about.version', { version: appVersion }) }}</text>
|
<view class="nav-back" @click="goBack">
|
||||||
|
<image class="back-icon" src="/static/ic_back2.png" mode="aspectFit" />
|
||||||
|
</view>
|
||||||
|
<text class="nav-title">{{ t('about.title') }}</text>
|
||||||
|
<view class="nav-placeholder" />
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 注销账号按钮(已登录时显示) -->
|
<view class="page-body" :style="{ paddingTop: (statusBarHeight + 44) + 'px' }">
|
||||||
<view v-if="userStore.isLoggedIn" class="delete-section">
|
<!-- APP信息 -->
|
||||||
<view class="delete-btn" @click="showDeleteConfirm = true">
|
<view class="app-info">
|
||||||
<text class="delete-text">{{ t('about.deleteAccount') }}</text>
|
<image class="logo" src="/static/logo.png" mode="aspectFit" />
|
||||||
|
<text class="version">版本 {{ appVersion }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 注销账号按钮(已登录时显示) -->
|
||||||
|
<view v-if="userStore.isLoggedIn" class="delete-section">
|
||||||
|
<view class="delete-btn" @click="showDeleteConfirm = true">
|
||||||
|
<text class="delete-text">{{ t('about.deleteAccount') }}</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
@ -38,8 +51,25 @@ import { useUserStore } from '../../stores/user.js'
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
// 从manifest读取版本号
|
// 状态栏高度
|
||||||
|
const statusBarHeight = ref(0)
|
||||||
|
try {
|
||||||
|
const sysInfo = uni.getSystemInfoSync()
|
||||||
|
statusBarHeight.value = sysInfo.statusBarHeight || 0
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
function goBack() {
|
||||||
|
uni.navigateBack()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取版本号
|
||||||
const appVersion = ref('1.0.0')
|
const appVersion = ref('1.0.0')
|
||||||
|
try {
|
||||||
|
const accountInfo = uni.getAccountInfoSync?.()
|
||||||
|
if (accountInfo?.miniProgram?.version) {
|
||||||
|
appVersion.value = accountInfo.miniProgram.version
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
const showDeleteConfirm = ref(false)
|
const showDeleteConfirm = ref(false)
|
||||||
|
|
||||||
|
|
@ -65,6 +95,44 @@ async function handleDelete() {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
.custom-nav {
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0; right: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background-color: #DBDBDB;
|
||||||
|
}
|
||||||
|
.nav-inner {
|
||||||
|
height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 16rpx;
|
||||||
|
}
|
||||||
|
.nav-back {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.back-icon {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
}
|
||||||
|
.nav-title {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.nav-placeholder {
|
||||||
|
width: 60rpx;
|
||||||
|
}
|
||||||
|
.page-body {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.app-info {
|
.app-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,23 @@
|
||||||
<template>
|
<template>
|
||||||
<view class="agreement-page">
|
<view class="agreement-page">
|
||||||
<view v-if="loading" class="loading">
|
<!-- 自定义导航栏 -->
|
||||||
<text>{{ t('common.loading') }}</text>
|
<view class="custom-nav" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||||
|
<view class="nav-inner">
|
||||||
|
<view class="nav-back" @click="goBack">
|
||||||
|
<image class="back-icon" src="/static/ic_back2.png" mode="aspectFit" />
|
||||||
|
</view>
|
||||||
|
<text class="nav-title">{{ t('agreement.title') }}</text>
|
||||||
|
<view class="nav-placeholder" />
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-else class="content">
|
|
||||||
<rich-text :nodes="content" />
|
<view class="page-body" :style="{ paddingTop: (statusBarHeight + 44) + 'px' }">
|
||||||
|
<view v-if="loading" class="loading">
|
||||||
|
<text>{{ t('common.loading') }}</text>
|
||||||
|
</view>
|
||||||
|
<view v-else class="content">
|
||||||
|
<rich-text :nodes="content" />
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -16,45 +29,82 @@ import { getAgreement } from '../../api/content.js'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
// 状态栏高度
|
||||||
|
const statusBarHeight = ref(0)
|
||||||
|
try {
|
||||||
|
const sysInfo = uni.getSystemInfoSync()
|
||||||
|
statusBarHeight.value = sysInfo.statusBarHeight || 0
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
const content = ref('')
|
const content = ref('')
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
|
|
||||||
|
function goBack() {
|
||||||
|
uni.navigateBack()
|
||||||
|
}
|
||||||
|
|
||||||
async function loadContent() {
|
async function loadContent() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await getAgreement()
|
const res = await getAgreement()
|
||||||
content.value = res.data?.content ?? res.data ?? ''
|
content.value = res.data?.content ?? res.data ?? ''
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
/* 错误已统一处理 */
|
finally { loading.value = false }
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => { loadContent() })
|
||||||
loadContent()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.agreement-page {
|
.agreement-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
.custom-nav {
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0; right: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background-color: #DBDBDB;
|
||||||
|
}
|
||||||
|
.nav-inner {
|
||||||
|
height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 16rpx;
|
||||||
|
}
|
||||||
|
.nav-back {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.back-icon {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
}
|
||||||
|
.nav-title {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.nav-placeholder {
|
||||||
|
width: 60rpx;
|
||||||
|
}
|
||||||
|
.page-body {
|
||||||
padding: 30rpx;
|
padding: 30rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 60rpx 0;
|
padding: 60rpx 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading text {
|
.loading text {
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #333;
|
color: #333;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<view class="custom-nav" :style="{ paddingTop: statusBarHeight + 'px' }">
|
<view class="custom-nav" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||||
<view class="nav-inner">
|
<view class="nav-inner">
|
||||||
<view class="nav-back" @click="goBack">
|
<view class="nav-back" @click="goBack">
|
||||||
<text class="nav-back-icon">‹</text>
|
<image class="back-icon" src="/static/ic_back2.png" mode="aspectFit" />
|
||||||
</view>
|
</view>
|
||||||
<text class="nav-title">{{ t('points.title') }}</text>
|
<text class="nav-title">{{ t('points.title') }}</text>
|
||||||
<view class="nav-placeholder" />
|
<view class="nav-placeholder" />
|
||||||
|
|
@ -172,9 +172,9 @@ loadRecords()
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
.nav-back-icon {
|
.back-icon {
|
||||||
font-size: 48rpx;
|
width: 40rpx;
|
||||||
color: #333;
|
height: 40rpx;
|
||||||
}
|
}
|
||||||
.nav-title {
|
.nav-title {
|
||||||
font-size: 34rpx;
|
font-size: 34rpx;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,23 @@
|
||||||
<template>
|
<template>
|
||||||
<view class="privacy-page">
|
<view class="privacy-page">
|
||||||
<view v-if="loading" class="loading">
|
<!-- 自定义导航栏 -->
|
||||||
<text>{{ t('common.loading') }}</text>
|
<view class="custom-nav" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||||
|
<view class="nav-inner">
|
||||||
|
<view class="nav-back" @click="goBack">
|
||||||
|
<image class="back-icon" src="/static/ic_back2.png" mode="aspectFit" />
|
||||||
|
</view>
|
||||||
|
<text class="nav-title">{{ t('privacy.title') }}</text>
|
||||||
|
<view class="nav-placeholder" />
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-else class="content">
|
|
||||||
<rich-text :nodes="content" />
|
<view class="page-body" :style="{ paddingTop: (statusBarHeight + 44) + 'px' }">
|
||||||
|
<view v-if="loading" class="loading">
|
||||||
|
<text>{{ t('common.loading') }}</text>
|
||||||
|
</view>
|
||||||
|
<view v-else class="content">
|
||||||
|
<rich-text :nodes="content" />
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -16,45 +29,82 @@ import { getPrivacyPolicy } from '../../api/content.js'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
// 状态栏高度
|
||||||
|
const statusBarHeight = ref(0)
|
||||||
|
try {
|
||||||
|
const sysInfo = uni.getSystemInfoSync()
|
||||||
|
statusBarHeight.value = sysInfo.statusBarHeight || 0
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
const content = ref('')
|
const content = ref('')
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
|
|
||||||
|
function goBack() {
|
||||||
|
uni.navigateBack()
|
||||||
|
}
|
||||||
|
|
||||||
async function loadContent() {
|
async function loadContent() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await getPrivacyPolicy()
|
const res = await getPrivacyPolicy()
|
||||||
content.value = res.data?.content ?? res.data ?? ''
|
content.value = res.data?.content ?? res.data ?? ''
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
/* 错误已统一处理 */
|
finally { loading.value = false }
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => { loadContent() })
|
||||||
loadContent()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.privacy-page {
|
.privacy-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
.custom-nav {
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0; right: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background-color: #DBDBDB;
|
||||||
|
}
|
||||||
|
.nav-inner {
|
||||||
|
height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 16rpx;
|
||||||
|
}
|
||||||
|
.nav-back {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.back-icon {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
}
|
||||||
|
.nav-title {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.nav-placeholder {
|
||||||
|
width: 60rpx;
|
||||||
|
}
|
||||||
|
.page-body {
|
||||||
padding: 30rpx;
|
padding: 30rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 60rpx 0;
|
padding: 60rpx 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading text {
|
.loading text {
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #333;
|
color: #333;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user