页面修改
This commit is contained in:
parent
f2d176b4d9
commit
95ad917368
|
|
@ -22,3 +22,8 @@ export function setUserMembership(uid: string, data: {
|
|||
export function updateUserPoints(uid: string, points: number) {
|
||||
return request.put(`/user/${uid}/points`, { points })
|
||||
}
|
||||
|
||||
// 删除用户
|
||||
export function deleteUser(uid: string) {
|
||||
return request.delete(`/user/${uid}`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import ElementPlus from 'element-plus'
|
||||
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
|
||||
import 'element-plus/dist/index.css'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
|
@ -9,6 +10,6 @@ const app = createApp(App)
|
|||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
app.use(ElementPlus, { locale: undefined }) // 后续可配置中文语言包
|
||||
app.use(ElementPlus, { locale: zhCn })
|
||||
|
||||
app.mount('#app')
|
||||
|
|
|
|||
13
admin/src/utils/date.ts
Normal file
13
admin/src/utils/date.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* 格式化日期字符串为本地时间显示
|
||||
* 后端存储的是 UTC 时间,需要转换为本地时区显示
|
||||
*/
|
||||
export function formatDate(dateStr: string | null | undefined): string {
|
||||
if (!dateStr) return '-'
|
||||
// 如果时间字符串没有时区标识,视为 UTC 时间,补上 Z
|
||||
let str = dateStr
|
||||
if (!str.endsWith('Z') && !str.includes('+') && !/\d{2}:\d{2}$/.test(str.slice(-6))) {
|
||||
str += 'Z'
|
||||
}
|
||||
return new Date(str).toLocaleString('zh-CN')
|
||||
}
|
||||
|
|
@ -128,6 +128,7 @@
|
|||
import { ref, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getCoupons, createCoupon, updateCoupon, toggleCouponActive } from '@/api/coupon'
|
||||
import { formatDate } from '@/utils/date'
|
||||
|
||||
// 优惠券数据类型
|
||||
interface CouponItem {
|
||||
|
|
@ -165,12 +166,6 @@ const defaultForm = () => ({
|
|||
})
|
||||
const form = ref(defaultForm())
|
||||
|
||||
// 格式化日期
|
||||
function formatDate(dateStr: string) {
|
||||
if (!dateStr) return '-'
|
||||
return new Date(dateStr).toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
// 加载优惠券列表
|
||||
async function loadCoupons() {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -57,9 +57,6 @@
|
|||
<el-form-item label="描述(英文)">
|
||||
<el-input v-model="form.descriptionEn" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
<el-form-item label="宣传图">
|
||||
<ImageUpload v-model="form.promotionImageUrl" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Google 商品 ID">
|
||||
<el-input v-model="form.googleProductId" placeholder="Google Play 商品 ID" />
|
||||
</el-form-item>
|
||||
|
|
@ -78,7 +75,6 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import ImageUpload from '@/components/ImageUpload.vue'
|
||||
import { getMembershipProducts, createMembershipProduct, updateMembershipProduct } from '@/api/membership'
|
||||
|
||||
// 会员商品数据类型
|
||||
|
|
@ -91,7 +87,6 @@ interface ProductItem {
|
|||
descriptionZhCn: string
|
||||
descriptionZhTw: string
|
||||
descriptionEn: string
|
||||
promotionImageUrl: string
|
||||
googleProductId: string
|
||||
appleProductId: string
|
||||
}
|
||||
|
|
@ -110,7 +105,6 @@ const defaultForm = () => ({
|
|||
descriptionZhCn: '',
|
||||
descriptionZhTw: '',
|
||||
descriptionEn: '',
|
||||
promotionImageUrl: '',
|
||||
googleProductId: '',
|
||||
appleProductId: '',
|
||||
})
|
||||
|
|
@ -146,7 +140,6 @@ function handleEdit(row: ProductItem) {
|
|||
descriptionZhCn: row.descriptionZhCn || '',
|
||||
descriptionZhTw: row.descriptionZhTw || '',
|
||||
descriptionEn: row.descriptionEn || '',
|
||||
promotionImageUrl: row.promotionImageUrl || '',
|
||||
googleProductId: row.googleProductId || '',
|
||||
appleProductId: row.appleProductId || '',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@
|
|||
import { ref, onMounted, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getPointsConfig, updatePointsConfig, getPointsRecords } from '@/api/points'
|
||||
import { formatDate } from '@/utils/date'
|
||||
|
||||
const activeTab = ref('config')
|
||||
|
||||
|
|
@ -113,11 +114,6 @@ const currentPage = ref(1)
|
|||
const pageSize = 20
|
||||
const total = ref(0)
|
||||
|
||||
function formatDate(dateStr: string) {
|
||||
if (!dateStr) return '-'
|
||||
return new Date(dateStr).toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
async function loadRecords() {
|
||||
loadingRecords.value = true
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { formatDate } from '@/utils/date'
|
||||
import ImageUpload from '@/components/ImageUpload.vue'
|
||||
import { getStampCoupons, createStampCoupon, updateStampCoupon, toggleStampActive, getStampBanner, updateStampBanner } from '@/api/stamp'
|
||||
|
||||
|
|
@ -172,12 +173,6 @@ const defaultForm = () => ({
|
|||
})
|
||||
const form = ref(defaultForm())
|
||||
|
||||
// 格式化日期
|
||||
function formatDate(dateStr: string) {
|
||||
if (!dateStr) return '-'
|
||||
return new Date(dateStr).toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
// 加载 Banner 配置
|
||||
async function loadBanner() {
|
||||
try {
|
||||
|
|
@ -246,7 +241,11 @@ function handleEdit(row: StampItem) {
|
|||
// 上架/下架
|
||||
async function handleToggleActive(row: StampItem) {
|
||||
try {
|
||||
await toggleStampActive(row.id)
|
||||
const res: any = await toggleStampActive(row.id)
|
||||
if (res.success === false) {
|
||||
ElMessage.error(res.message || '操作失败')
|
||||
return
|
||||
}
|
||||
ElMessage.success(row.isActive ? '已下架' : '已上架')
|
||||
await loadStamps()
|
||||
} catch {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ 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'
|
||||
import { formatDate } from '@/utils/date'
|
||||
|
||||
interface TestAccount {
|
||||
phone: string
|
||||
|
|
@ -57,12 +58,6 @@ 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
|
||||
|
|
|
|||
|
|
@ -30,11 +30,12 @@
|
|||
<el-table-column label="注册时间" width="170" align="center">
|
||||
<template #default="{ row }">{{ formatDate(row.createdAt) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="260" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="handleDetail(row.uid)">详情</el-button>
|
||||
<el-button v-if="row.isMember" size="small" type="danger" @click="handleCancelMembership(row)">取消会员</el-button>
|
||||
<el-button v-else size="small" type="success" @click="openMembershipDialog(row)">设为会员</el-button>
|
||||
<el-button size="small" type="danger" @click="handleDeleteUser(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
|
@ -141,7 +142,8 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { getUsers, getUserDetail, setUserMembership, updateUserPoints } from '@/api/user'
|
||||
import { formatDate } from '@/utils/date'
|
||||
import { getUsers, getUserDetail, setUserMembership, updateUserPoints, deleteUser } from '@/api/user'
|
||||
|
||||
// 用户列表项
|
||||
interface UserItem {
|
||||
|
|
@ -176,12 +178,6 @@ const searchText = ref('')
|
|||
const detailVisible = ref(false)
|
||||
const userDetail = ref<UserDetailData | null>(null)
|
||||
|
||||
// 格式化日期
|
||||
function formatDate(dateStr: string) {
|
||||
if (!dateStr) return '-'
|
||||
return new Date(dateStr).toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
// 加载用户列表
|
||||
async function loadUsers() {
|
||||
try {
|
||||
|
|
@ -261,6 +257,22 @@ async function handleCancelMembership(row: UserItem) {
|
|||
}
|
||||
}
|
||||
|
||||
// 删除用户
|
||||
async function handleDeleteUser(row: UserItem) {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除用户 ${row.nickname || row.uid}?删除后该用户将无法登录。`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
await deleteUser(row.uid)
|
||||
ElMessage.success('用户已删除')
|
||||
await loadUsers()
|
||||
} catch {
|
||||
// 用户取消或错误
|
||||
}
|
||||
}
|
||||
|
||||
// 编辑积分相关
|
||||
const editPointsVisible = ref(false)
|
||||
const editPointsValue = ref(0)
|
||||
|
|
|
|||
|
|
@ -126,6 +126,22 @@ public class AdminUserController : ControllerBase
|
|||
return Ok(new { success = true, message = "积分已更新", data = new { pointsBalance = user.PointsBalance } });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除用户(软删除)
|
||||
/// </summary>
|
||||
[HttpDelete("{uid}")]
|
||||
public async Task<IActionResult> DeleteUser(string uid)
|
||||
{
|
||||
var user = await _db.Users.FirstOrDefaultAsync(u => u.Uid == uid);
|
||||
if (user == null)
|
||||
return NotFound(new { success = false, message = "用户不存在" });
|
||||
|
||||
user.IsDeleted = true;
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
return Ok(new { success = true, message = "用户已删除" });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户详情
|
||||
/// </summary>
|
||||
|
|
@ -162,6 +178,7 @@ public class AdminUserController : ControllerBase
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
|
|
@ -2,8 +2,8 @@ import { getStorage, removeStorage, TOKEN_KEY, LOCALE_KEY } from '../utils/stora
|
|||
|
||||
// 后端API基础地址
|
||||
// MuMu模拟器需使用宿主机局域网IP,生产环境替换为正式域名
|
||||
// export const BASE_URL = 'http://192.168.21.9:5082'
|
||||
export const BASE_URL = 'https://api.tty.shhmkjgs.cn'
|
||||
export const BASE_URL = 'http://192.168.21.9:5082'
|
||||
// export const BASE_URL = 'https://api.tty.shhmkjgs.cn'
|
||||
|
||||
/**
|
||||
* 统一请求封装,自动注入Token和语言请求头,统一处理响应和错误
|
||||
|
|
|
|||
|
|
@ -149,7 +149,6 @@ async function handleDelete() {
|
|||
.logo-wrapper {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border: 2rpx solid #ccc;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
|
|
|||
|
|
@ -39,12 +39,22 @@
|
|||
<!-- 功能入口(节日印花 + 会员二维码) -->
|
||||
<view class="entry-row">
|
||||
<view class="entry-card" @click="onEntryClick({ key: 'stamps' })">
|
||||
<image class="entry-icon" :src="getEntryImage('stamp') ? resolveImageUrl(getEntryImage('stamp')) : '/static/ic_stamp.png'" mode="aspectFit" />
|
||||
<text class="entry-label">{{ t('home.stamps') }}</text>
|
||||
<template v-if="getEntryImage('stamp')">
|
||||
<image class="entry-card-img" :src="resolveImageUrl(getEntryImage('stamp'))" mode="aspectFill" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<image class="entry-icon" src="/static/ic_stamp.png" mode="aspectFit" />
|
||||
<text class="entry-label">{{ t('home.stamps') }}</text>
|
||||
</template>
|
||||
</view>
|
||||
<view class="entry-card" @click="onEntryClick({ key: 'qrcode' })">
|
||||
<image class="entry-icon" :src="getEntryImage('qrcode') ? resolveImageUrl(getEntryImage('qrcode')) : '/static/ic_qrcode.png'" mode="aspectFit" />
|
||||
<text class="entry-label">{{ t('home.qrcode') }}</text>
|
||||
<template v-if="getEntryImage('qrcode')">
|
||||
<image class="entry-card-img" :src="resolveImageUrl(getEntryImage('qrcode'))" mode="aspectFill" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<image class="entry-icon" src="/static/ic_qrcode.png" mode="aspectFit" />
|
||||
<text class="entry-label">{{ t('home.qrcode') }}</text>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
|
@ -358,7 +368,6 @@ export default {
|
|||
/* 成为会员入口 */
|
||||
.membership-entry {
|
||||
margin: 24rpx 24rpx 0;
|
||||
background: linear-gradient(135deg, #4a5d4a, #3d4f3d);
|
||||
border-radius: 20rpx;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
|
|
@ -429,22 +438,30 @@ export default {
|
|||
flex: 1;
|
||||
background-color: #ffffff;
|
||||
border-radius: 20rpx;
|
||||
padding: 36rpx 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.entry-card-img {
|
||||
width: 100%;
|
||||
height: 200rpx;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.entry-icon {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
margin-top: 36rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.entry-label {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
margin-bottom: 36rpx;
|
||||
}
|
||||
|
||||
/* 优惠券标题行 */
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
<!-- 底部悬浮按钮区域 -->
|
||||
<view class="fixed-bottom">
|
||||
<view v-for="product in products" :key="product.productId" class="bottom-btn-wrap">
|
||||
<template v-if="product.type === 'monthly'">
|
||||
<template v-if="product.type === 'monthly' && !isSubscribed">
|
||||
<view
|
||||
v-if="!isMember || membershipType !== 'monthly'"
|
||||
class="bottom-btn monthly-btn"
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user