页面修改

This commit is contained in:
18631081161 2026-04-22 03:01:05 +08:00
parent f2d176b4d9
commit 95ad917368
19 changed files with 92 additions and 50 deletions

View File

@ -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}`)
}

View File

@ -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
View 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')
}

View File

@ -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 {

View File

@ -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 || '',
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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和语言请求头统一处理响应和错误

View File

@ -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;

View File

@ -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;
}
/* 优惠券标题行 */

View File

@ -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"