feat(ui): redesign all admin console pages
- 新增 AdminLayout 侧边栏布局组件(深色#001529侧边栏+粉色#E6508C高亮)
- 登录页:深蓝渐变背景+🌸标识+粉色登录按钮
- 仪表盘:4个统计卡片+实时护理数据表+快捷操作
- 设备管理:搜索栏+设备列表表格+状态标签+分页
- 设备详情:返回链接+信息网格+护理记录表
- 用户管理:用户列表+头像+订阅状态标签+分页
- 用户详情:用户卡片+统计数据+基本信息+护理记录
- 订阅管理:统计行+标签页筛选+订阅列表表格
- 护理记录:日期范围+搜索+模式标签+记录表格
- 操作日志:时间线列表+彩色操作者+类型标签
- 系统设置:基础配置+订阅配置+功能开关+保存按钮
这个提交包含在:
@@ -1,40 +1,67 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="toolbar">
|
||||
<u-search v-model="keyword" placeholder="搜索用户昵称" @search="onSearch" @clear="onSearch" />
|
||||
</view>
|
||||
<AdminLayout currentPage="/pages/user/index">
|
||||
<view class="page-card">
|
||||
<view class="page-header">
|
||||
<text class="page-title">👥 用户管理</text>
|
||||
<view class="header-actions">
|
||||
<input
|
||||
class="search-input"
|
||||
v-model="keyword"
|
||||
placeholder="搜索用户昵称"
|
||||
placeholder-class="input-placeholder"
|
||||
@confirm="onSearch"
|
||||
/>
|
||||
<button class="btn-primary btn-sm" @click="onSearch">搜索</button>
|
||||
<button class="btn-default btn-sm">导出用户</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="table-wrap">
|
||||
<u-table>
|
||||
<u-tr>
|
||||
<u-th>用户ID</u-th>
|
||||
<u-th>昵称</u-th>
|
||||
<u-th>设备数</u-th>
|
||||
<u-th>订阅到期</u-th>
|
||||
<u-th>注册时间</u-th>
|
||||
</u-tr>
|
||||
<u-tr v-for="item in users" :key="item.user_id" @click="onDetail(item.user_id)">
|
||||
<u-td>{{ item.user_id }}</u-td>
|
||||
<u-td>{{ item.nickname || '-' }}</u-td>
|
||||
<u-td>{{ item.device_count || 0 }}</u-td>
|
||||
<u-td>{{ formatDate(item.subscription_expire) }}</u-td>
|
||||
<u-td>{{ formatDate(item.created_at) }}</u-td>
|
||||
</u-tr>
|
||||
</u-table>
|
||||
</view>
|
||||
<view class="data-table">
|
||||
<view class="t-header">
|
||||
<view class="t-row">
|
||||
<text class="t-th flex2">用户</text>
|
||||
<text class="t-th flex2">手机号</text>
|
||||
<text class="t-th flex1">绑定设备</text>
|
||||
<text class="t-th flex1">护理次数</text>
|
||||
<text class="t-th flex1">订阅状态</text>
|
||||
<text class="t-th flex2">注册时间</text>
|
||||
<text class="t-th flex1">操作</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="t-body">
|
||||
<view class="t-row" v-for="item in users" :key="item.user_id">
|
||||
<text class="t-td flex2">👤 {{ item.nickname || '-' }}</text>
|
||||
<text class="t-td flex2">{{ item.phone || '-' }}</text>
|
||||
<text class="t-td flex1">{{ item.device_count || 0 }}</text>
|
||||
<text class="t-td flex1">{{ item.treatment_count || 0 }}</text>
|
||||
<text class="t-td flex1">
|
||||
<text :class="subBadge(item.subscription_status)">{{ subStatusText(item.subscription_status) }}</text>
|
||||
</text>
|
||||
<text class="t-td flex2">{{ formatDate(item.created_at) }}</text>
|
||||
<text class="t-td flex1">
|
||||
<text class="action-link" @click="onDetail(item.user_id)">详情</text>
|
||||
<text class="action-link" @click="onDetail(item.user_id)">记录</text>
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="pagination">
|
||||
<u-button size="small" :disabled="page <= 1" @click="onPage(page - 1)">上一页</u-button>
|
||||
<text class="page-info">第 {{ page }} 页 / 共 {{ totalPages }} 页</text>
|
||||
<u-button size="small" :disabled="page >= totalPages" @click="onPage(page + 1)">下一页</u-button>
|
||||
<view class="pagination">
|
||||
<button class="btn-page" :disabled="page <= 1" @click="onPage(page - 1)">‹</button>
|
||||
<button class="btn-page active">{{ page }}</button>
|
||||
<button class="btn-page" :disabled="page >= totalPages" @click="onPage(page + 1)">›</button>
|
||||
<text class="page-info">共 {{ totalPages }} 页</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { get } from '../../utils/request'
|
||||
import AdminLayout from '../../components/AdminLayout.vue'
|
||||
|
||||
export default {
|
||||
components: { AdminLayout },
|
||||
data() {
|
||||
return {
|
||||
users: [],
|
||||
@@ -55,7 +82,7 @@ export default {
|
||||
methods: {
|
||||
async loadUsers() {
|
||||
try {
|
||||
const data = await get('/api/v1/admin/users', { page: this.page, page_size: this.pageSize })
|
||||
const data = await get('/api/v1/admin/users', { page: this.page, page_size: this.pageSize, keyword: this.keyword })
|
||||
this.users = data.records || []
|
||||
this.total = data.total || 0
|
||||
} catch (e) {}
|
||||
@@ -71,6 +98,19 @@ export default {
|
||||
onDetail(userId) {
|
||||
uni.navigateTo({ url: '/pages/user-detail/index?user_id=' + userId })
|
||||
},
|
||||
subStatusText(status) {
|
||||
const map = { yearly: '年卡', monthly: '月卡', trial: '试用中', none: '未订阅' }
|
||||
return map[status] || '未订阅'
|
||||
},
|
||||
subBadge(status) {
|
||||
const map = {
|
||||
yearly: 'badge badge-success',
|
||||
monthly: 'badge badge-success',
|
||||
trial: 'badge badge-warning',
|
||||
none: 'badge badge-default'
|
||||
}
|
||||
return map[status] || 'badge badge-default'
|
||||
},
|
||||
formatDate(d) {
|
||||
return d ? d.slice(0, 10) : '-'
|
||||
}
|
||||
@@ -79,24 +119,166 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
padding: 16px;
|
||||
.page-card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
margin-bottom: 16px;
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 220px;
|
||||
height: 32px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.input-placeholder {
|
||||
color: #bfbfbf;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #E6508C;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-default {
|
||||
background: #fff;
|
||||
color: #333;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
height: 32px;
|
||||
padding: 0 16px;
|
||||
font-size: 14px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.t-header {
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.t-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.t-th {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.t-td {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.flex1 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.flex2 {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
background: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.badge-warning {
|
||||
background: #fffbe6;
|
||||
color: #faad14;
|
||||
}
|
||||
|
||||
.badge-default {
|
||||
background: #f5f5f5;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.action-link {
|
||||
color: #E6508C;
|
||||
font-size: 14px;
|
||||
margin-right: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
margin-top: 16px;
|
||||
gap: 8px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.btn-page {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-page.active {
|
||||
background: #E6508C;
|
||||
color: #fff;
|
||||
border-color: #E6508C;
|
||||
}
|
||||
|
||||
.btn-page[disabled] {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.page-info {
|
||||
font-size: 13px;
|
||||
color: #999999;
|
||||
color: #999;
|
||||
margin-left: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
在新工单中引用
屏蔽一个用户