feat(ui): redesign all admin console pages
- 新增 AdminLayout 侧边栏布局组件(深色#001529侧边栏+粉色#E6508C高亮)
- 登录页:深蓝渐变背景+🌸标识+粉色登录按钮
- 仪表盘:4个统计卡片+实时护理数据表+快捷操作
- 设备管理:搜索栏+设备列表表格+状态标签+分页
- 设备详情:返回链接+信息网格+护理记录表
- 用户管理:用户列表+头像+订阅状态标签+分页
- 用户详情:用户卡片+统计数据+基本信息+护理记录
- 订阅管理:统计行+标签页筛选+订阅列表表格
- 护理记录:日期范围+搜索+模式标签+记录表格
- 操作日志:时间线列表+彩色操作者+类型标签
- 系统设置:基础配置+订阅配置+功能开关+保存按钮
这个提交包含在:
@@ -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>
|
||||
|
||||
在新工单中引用
屏蔽一个用户