feat(ui): redesign all admin console pages

- 新增 AdminLayout 侧边栏布局组件(深色#001529侧边栏+粉色#E6508C高亮)
- 登录页:深蓝渐变背景+🌸标识+粉色登录按钮
- 仪表盘:4个统计卡片+实时护理数据表+快捷操作
- 设备管理:搜索栏+设备列表表格+状态标签+分页
- 设备详情:返回链接+信息网格+护理记录表
- 用户管理:用户列表+头像+订阅状态标签+分页
- 用户详情:用户卡片+统计数据+基本信息+护理记录
- 订阅管理:统计行+标签页筛选+订阅列表表格
- 护理记录:日期范围+搜索+模式标签+记录表格
- 操作日志:时间线列表+彩色操作者+类型标签
- 系统设置:基础配置+订阅配置+功能开关+保存按钮
这个提交包含在:
Guoguo
2026-04-22 22:06:11 +08:00
父节点 aa4b215c8b
当前提交 7fc17b958a
修改 11 个文件,包含 2669 行新增562 行删除
+250 -41
查看文件
@@ -1,44 +1,58 @@
<template>
<view class="container">
<view class="toolbar">
<view class="filter-row">
<u-input v-model="filters.type" placeholder="操作类型" class="filter-input" />
<u-input v-model="filters.device_id" placeholder="设备ID" class="filter-input" />
<u-button type="primary" size="small" @click="onSearch">查询</u-button>
<AdminLayout currentPage="/pages/log/index">
<view class="page-card">
<view class="page-header">
<text class="page-title">📝 操作日志</text>
<view class="header-actions">
<input
class="search-input"
v-model="filters.type"
placeholder="搜索操作类型"
placeholder-class="input-placeholder"
/>
<button class="btn-primary btn-sm" @click="onSearch">搜索</button>
<button class="btn-default btn-sm">导出</button>
</view>
</view>
<view class="log-list">
<view class="log-item" v-for="item in logs" :key="item.id">
<view class="log-left">
<view class="log-dot"></view>
<view class="log-line"></view>
</view>
<view class="log-content">
<view class="log-header">
<text class="log-time">{{ formatDate(item.created_at) }}</text>
<text :class="logTypeBadge(item.operator_type)">{{ logTypeText(item.operator_type) }}</text>
</view>
<view class="log-body">
<text class="log-actor" :class="logActorClass(item.operator_type)">{{ item.operator_type === 1 ? '管理员' : '系统' }}</text>
<text class="log-text"> </text>
<text class="log-target">{{ item.target_type }} {{ item.target_id || '' }}</text>
<text class="log-text"> 执行了 </text>
<text class="log-action">{{ item.action }}</text>
</view>
</view>
</view>
</view>
<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 class="table-wrap">
<u-table>
<u-tr>
<u-th>操作者</u-th>
<u-th>对象类型</u-th>
<u-th>对象ID</u-th>
<u-th>操作</u-th>
<u-th>时间</u-th>
</u-tr>
<u-tr v-for="item in logs" :key="item.id">
<u-td>{{ item.operator_type === 1 ? '管理员' : '系统' }}</u-td>
<u-td>{{ item.target_type }}</u-td>
<u-td>{{ item.target_id || '-' }}</u-td>
<u-td>{{ item.action }}</u-td>
<u-td>{{ formatDate(item.created_at) }}</u-td>
</u-tr>
</u-table>
</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>
</view>
</AdminLayout>
</template>
<script>
import { get } from '../../utils/request'
import AdminLayout from '../../components/AdminLayout.vue'
export default {
components: { AdminLayout },
data() {
return {
logs: [],
@@ -75,6 +89,21 @@ export default {
this.page = p
this.loadLogs()
},
logTypeText(type) {
if (type === 1) return '管理操作'
if (type === 2) return '系统事件'
return '用户操作'
},
logTypeBadge(type) {
if (type === 1) return 'badge badge-pink'
if (type === 2) return 'badge badge-blue'
return 'badge badge-green'
},
logActorClass(type) {
if (type === 1) return 'actor-admin'
if (type === 2) return 'actor-system'
return 'actor-user'
},
formatDate(d) {
return d ? d.slice(0, 19).replace('T', ' ') : '-'
}
@@ -83,30 +112,210 @@ export default {
</script>
<style scoped>
.container {
padding: 16px;
.page-card {
background: #fff;
border-radius: 8px;
padding: 20px;
}
.filter-row {
.page-header {
display: flex;
gap: 8px;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.filter-input {
width: 120px;
.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;
}
.log-list {
margin-top: 8px;
}
.log-item {
display: flex;
min-height: 60px;
}
.log-left {
display: flex;
flex-direction: column;
align-items: center;
width: 20px;
flex-shrink: 0;
padding-top: 4px;
}
.log-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: #d9d9d9;
flex-shrink: 0;
}
.log-line {
width: 1px;
flex: 1;
background: #f0f0f0;
}
.log-content {
flex: 1;
padding-bottom: 16px;
margin-left: 12px;
}
.log-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 6px;
}
.log-time {
font-size: 13px;
color: #999;
}
.badge {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.badge-pink {
background: #fff0f6;
color: #E6508C;
}
.badge-blue {
background: #e6f7ff;
color: #1890ff;
}
.badge-green {
background: #f6ffed;
color: #52c41a;
}
.log-body {
font-size: 14px;
line-height: 22px;
}
.log-actor {
font-weight: 600;
}
.actor-admin {
color: #E6508C;
}
.actor-system {
color: #1890ff;
}
.actor-user {
color: #52c41a;
}
.log-text {
color: #666;
}
.log-target {
color: #333;
font-weight: 500;
}
.log-action {
color: #333;
font-weight: 500;
}
.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>