refactor: convert admin console to SPA with dynamic component switching
- Create shell page (pages/admin/index.vue) with AdminLayout + keep-alive - Convert 9 pages to view components (views/*.vue) - AdminLayout emits navigate events instead of uni.redirectTo - Sidebar navigation no longer causes full page reload - List views cached with keep-alive, detail views re-mount fresh - Fix: add name property to 5 cached views for keep-alive matching - Fix: add navigationStyle custom to prevent double nav bar - Fix: remove duplicate mounted() in RecordView/LogView
这个提交包含在:
@@ -6,10 +6,10 @@
|
|||||||
<view class="sidebar-menu">
|
<view class="sidebar-menu">
|
||||||
<view
|
<view
|
||||||
v-for="item in menuItems"
|
v-for="item in menuItems"
|
||||||
:key="item.path"
|
:key="item.view"
|
||||||
class="sidebar-item"
|
class="sidebar-item"
|
||||||
:class="{ active: currentPage === item.path }"
|
:class="{ active: currentPage === item.view }"
|
||||||
@click="navigate(item.path)"
|
@click="navigate(item.view)"
|
||||||
>
|
>
|
||||||
<text class="sidebar-icon">{{ item.icon }}</text>
|
<text class="sidebar-icon">{{ item.icon }}</text>
|
||||||
<text>{{ item.label }}</text>
|
<text>{{ item.label }}</text>
|
||||||
@@ -41,32 +41,32 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
menuItems: [
|
menuItems: [
|
||||||
{ icon: '📊', label: '仪表盘', path: '/pages/dashboard/index' },
|
{ icon: '📊', label: '仪表盘', view: 'dashboard' },
|
||||||
{ icon: '📱', label: '设备管理', path: '/pages/device/index' },
|
{ icon: '📱', label: '设备管理', view: 'device' },
|
||||||
{ icon: '👥', label: '用户管理', path: '/pages/user/index' },
|
{ icon: '👥', label: '用户管理', view: 'user' },
|
||||||
{ icon: '💳', label: '订阅管理', path: '/pages/subscription/index' },
|
{ icon: '💳', label: '订阅管理', view: 'subscription' },
|
||||||
{ icon: '📋', label: '护理记录', path: '/pages/record/index' },
|
{ icon: '📋', label: '护理记录', view: 'record' },
|
||||||
{ icon: '📝', label: '操作日志', path: '/pages/log/index' },
|
{ icon: '📝', label: '操作日志', view: 'log' },
|
||||||
{ icon: '⚙️', label: '系统设置', path: '/pages/settings/index' }
|
{ icon: '⚙️', label: '系统设置', view: 'settings' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
pageTitle() {
|
pageTitle() {
|
||||||
const item = this.menuItems.find(m => m.path === this.currentPage)
|
var item = this.menuItems.find(m => m.view === this.currentPage)
|
||||||
return item ? `${item.icon} ${item.label}` : '光子美容仪管理后台'
|
return item ? item.icon + ' ' + item.label : '光子美容仪管理后台'
|
||||||
},
|
},
|
||||||
adminName() {
|
adminName() {
|
||||||
const store = useUserStore()
|
var store = useUserStore()
|
||||||
return (store.adminInfo && store.adminInfo.real_name) || '管理员'
|
return (store.adminInfo && store.adminInfo.real_name) || '管理员'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
navigate(path) {
|
navigate(view) {
|
||||||
uni.redirectTo({ url: path })
|
this.$emit('navigate', view)
|
||||||
},
|
},
|
||||||
onLogout() {
|
onLogout() {
|
||||||
const store = useUserStore()
|
var store = useUserStore()
|
||||||
store.clearToken()
|
store.clearToken()
|
||||||
uni.reLaunch({ url: '/pages/login/index' })
|
uni.reLaunch({ url: '/pages/login/index' })
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-34
@@ -5,40 +5,8 @@
|
|||||||
"style": { "navigationBarTitleText": "管理员登录", "navigationStyle": "custom" }
|
"style": { "navigationBarTitleText": "管理员登录", "navigationStyle": "custom" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/dashboard/index",
|
"path": "pages/admin/index",
|
||||||
"style": { "navigationBarTitleText": "仪表盘" }
|
"style": { "navigationBarTitleText": "管理后台", "navigationStyle": "custom" }
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "pages/device/index",
|
|
||||||
"style": { "navigationBarTitleText": "设备管理" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "pages/device-detail/index",
|
|
||||||
"style": { "navigationBarTitleText": "设备详情" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "pages/user/index",
|
|
||||||
"style": { "navigationBarTitleText": "用户管理" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "pages/user-detail/index",
|
|
||||||
"style": { "navigationBarTitleText": "用户详情" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "pages/subscription/index",
|
|
||||||
"style": { "navigationBarTitleText": "订阅管理" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "pages/record/index",
|
|
||||||
"style": { "navigationBarTitleText": "护理记录" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "pages/log/index",
|
|
||||||
"style": { "navigationBarTitleText": "操作日志" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "pages/settings/index",
|
|
||||||
"style": { "navigationBarTitleText": "系统设置" }
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"globalStyle": {
|
"globalStyle": {
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<template>
|
||||||
|
<AdminLayout :currentPage="currentView" @navigate="onNavigate">
|
||||||
|
<keep-alive :include="keepAliveViews">
|
||||||
|
<component :is="currentComponent" :key="viewKey" v-bind="viewProps" @navigate="onNavigate" />
|
||||||
|
</keep-alive>
|
||||||
|
</AdminLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import AdminLayout from '../../components/AdminLayout.vue'
|
||||||
|
import DashboardView from '../../views/DashboardView.vue'
|
||||||
|
import DeviceListView from '../../views/DeviceListView.vue'
|
||||||
|
import DeviceDetailView from '../../views/DeviceDetailView.vue'
|
||||||
|
import UserListView from '../../views/UserListView.vue'
|
||||||
|
import UserDetailView from '../../views/UserDetailView.vue'
|
||||||
|
import SubscriptionView from '../../views/SubscriptionView.vue'
|
||||||
|
import RecordView from '../../views/RecordView.vue'
|
||||||
|
import LogView from '../../views/LogView.vue'
|
||||||
|
import SettingsView from '../../views/SettingsView.vue'
|
||||||
|
|
||||||
|
var VIEW_MAP = {
|
||||||
|
dashboard: DashboardView,
|
||||||
|
device: DeviceListView,
|
||||||
|
'device-detail': DeviceDetailView,
|
||||||
|
user: UserListView,
|
||||||
|
'user-detail': UserDetailView,
|
||||||
|
subscription: SubscriptionView,
|
||||||
|
record: RecordView,
|
||||||
|
log: LogView,
|
||||||
|
settings: SettingsView
|
||||||
|
}
|
||||||
|
|
||||||
|
// List views that should be kept alive (preserve state when navigating to detail and back)
|
||||||
|
var KEEP_ALIVE_VIEWS = ['DeviceListView', 'UserListView', 'SubscriptionView', 'RecordView', 'LogView']
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
AdminLayout,
|
||||||
|
DashboardView,
|
||||||
|
DeviceListView,
|
||||||
|
DeviceDetailView,
|
||||||
|
UserListView,
|
||||||
|
UserDetailView,
|
||||||
|
SubscriptionView,
|
||||||
|
RecordView,
|
||||||
|
LogView,
|
||||||
|
SettingsView
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentView: 'dashboard',
|
||||||
|
viewProps: {},
|
||||||
|
keepAliveViews: KEEP_ALIVE_VIEWS
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentComponent() {
|
||||||
|
return VIEW_MAP[this.currentView] || DashboardView
|
||||||
|
},
|
||||||
|
viewKey() {
|
||||||
|
// Detail views get a unique key so they always re-mount with fresh data
|
||||||
|
if (this.currentView === 'device-detail') {
|
||||||
|
return 'device-detail-' + (this.viewProps.device_id || '')
|
||||||
|
}
|
||||||
|
if (this.currentView === 'user-detail') {
|
||||||
|
return 'user-detail-' + (this.viewProps.user_id || '')
|
||||||
|
}
|
||||||
|
return this.currentView
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onNavigate(view, props) {
|
||||||
|
this.currentView = view
|
||||||
|
this.viewProps = props || {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -57,7 +57,7 @@ export default {
|
|||||||
try {
|
try {
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
await userStore.login(this.form)
|
await userStore.login(this.form)
|
||||||
uni.reLaunch({ url: '/pages/dashboard/index' })
|
uni.reLaunch({ url: '/pages/admin/index' })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
uni.showToast({ title: e.message || '登录失败', icon: 'none' })
|
uni.showToast({ title: e.message || '登录失败', icon: 'none' })
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
+11
-16
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<AdminLayout currentPage="/pages/dashboard/index">
|
<view>
|
||||||
<view class="stat-grid">
|
<view class="stat-grid">
|
||||||
<view class="stat-card">
|
<view class="stat-card">
|
||||||
<view class="stat-icon blue">📱</view>
|
<view class="stat-icon blue">📱</view>
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
<view class="card table-card">
|
<view class="card table-card">
|
||||||
<view class="card-header">
|
<view class="card-header">
|
||||||
<text class="card-title">实时护理数据</text>
|
<text class="card-title">实时护理数据</text>
|
||||||
<text class="card-extra" @click="onNav('/pages/record/index')">查看全部 ›</text>
|
<text class="card-extra" @click="$emit('navigate', 'record')">查看全部 ›</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="data-table">
|
<view class="data-table">
|
||||||
<view class="t-header">
|
<view class="t-header">
|
||||||
@@ -82,22 +82,22 @@
|
|||||||
<view class="card quick-card">
|
<view class="card quick-card">
|
||||||
<view class="card-title">快捷操作</view>
|
<view class="card-title">快捷操作</view>
|
||||||
<view class="quick-list">
|
<view class="quick-list">
|
||||||
<view class="quick-item" @click="onNav('/pages/device/index')">
|
<view class="quick-item" @click="$emit('navigate', 'device')">
|
||||||
<view class="quick-icon" style="background:#E6508C">📱</view>
|
<view class="quick-icon" style="background:#E6508C">📱</view>
|
||||||
<text class="quick-label">设备管理</text>
|
<text class="quick-label">设备管理</text>
|
||||||
<text class="quick-arrow">›</text>
|
<text class="quick-arrow">›</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="quick-item" @click="onNav('/pages/user/index')">
|
<view class="quick-item" @click="$emit('navigate', 'user')">
|
||||||
<view class="quick-icon" style="background:#E6508C">👥</view>
|
<view class="quick-icon" style="background:#E6508C">👥</view>
|
||||||
<text class="quick-label">用户管理</text>
|
<text class="quick-label">用户管理</text>
|
||||||
<text class="quick-arrow">›</text>
|
<text class="quick-arrow">›</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="quick-item" @click="onNav('/pages/subscription/index')">
|
<view class="quick-item" @click="$emit('navigate', 'subscription')">
|
||||||
<view class="quick-icon" style="background:#E6508C">💳</view>
|
<view class="quick-icon" style="background:#E6508C">💳</view>
|
||||||
<text class="quick-label">订阅管理</text>
|
<text class="quick-label">订阅管理</text>
|
||||||
<text class="quick-arrow">›</text>
|
<text class="quick-arrow">›</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="quick-item" @click="onNav('/pages/record/index')">
|
<view class="quick-item" @click="$emit('navigate', 'record')">
|
||||||
<view class="quick-icon" style="background:#E6508C">📋</view>
|
<view class="quick-icon" style="background:#E6508C">📋</view>
|
||||||
<text class="quick-label">数据报表</text>
|
<text class="quick-label">数据报表</text>
|
||||||
<text class="quick-arrow">›</text>
|
<text class="quick-arrow">›</text>
|
||||||
@@ -105,16 +105,14 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</AdminLayout>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { get } from '../../utils/request'
|
import { get } from '../utils/request'
|
||||||
import { formatDate as formatDateUtil } from '../../utils/format'
|
import { formatDate as formatDateUtil } from '../utils/format'
|
||||||
import AdminLayout from '../../components/AdminLayout.vue'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { AdminLayout },
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
stats: {},
|
stats: {},
|
||||||
@@ -122,7 +120,7 @@ export default {
|
|||||||
recentTreatments: []
|
recentTreatments: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onShow() {
|
mounted() {
|
||||||
this.loadDashboard()
|
this.loadDashboard()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -141,10 +139,7 @@ export default {
|
|||||||
this.recentTreatments = []
|
this.recentTreatments = []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
formatDate: formatDateUtil,
|
formatDate: formatDateUtil
|
||||||
onNav(path) {
|
|
||||||
uni.redirectTo({ url: path })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
+20
-15
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<AdminLayout currentPage="/pages/device/index">
|
<view>
|
||||||
<view class="back-link" @click="goBack">‹ 返回设备列表</view>
|
<view class="back-link" @click="$emit('navigate', 'device')">‹ 返回设备列表</view>
|
||||||
|
|
||||||
<view class="page-card">
|
<view class="page-card">
|
||||||
<view class="page-header">
|
<view class="page-header">
|
||||||
@@ -97,16 +97,17 @@
|
|||||||
<button class="btn-primary" @click="onCommand(4)">查询状态</button>
|
<button class="btn-primary" @click="onCommand(4)">查询状态</button>
|
||||||
<button class="btn-default" @click="onViewLogs">查看完整日志</button>
|
<button class="btn-default" @click="onViewLogs">查看完整日志</button>
|
||||||
</view>
|
</view>
|
||||||
</AdminLayout>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { get, post } from '../../utils/request'
|
import { get, post } from '../utils/request'
|
||||||
import { formatDate as formatDateUtil } from '../../utils/format'
|
import { formatDate as formatDateUtil } from '../utils/format'
|
||||||
import AdminLayout from '../../components/AdminLayout.vue'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { AdminLayout },
|
props: {
|
||||||
|
device_id: { type: String, default: '' }
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
device: null,
|
device: null,
|
||||||
@@ -114,9 +115,16 @@ export default {
|
|||||||
statusMap: { 1: '未激活', 2: '在线', 3: '离线', 4: '故障' }
|
statusMap: { 1: '未激活', 2: '在线', 3: '离线', 4: '故障' }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad(options) {
|
watch: {
|
||||||
this.deviceId = options.device_id || ''
|
device_id: {
|
||||||
this.loadDevice()
|
immediate: true,
|
||||||
|
handler(val) {
|
||||||
|
if (val) {
|
||||||
|
this.deviceId = val
|
||||||
|
this.loadDevice()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async loadDevice() {
|
async loadDevice() {
|
||||||
@@ -126,9 +134,6 @@ export default {
|
|||||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
goBack() {
|
|
||||||
uni.navigateBack()
|
|
||||||
},
|
|
||||||
async onCommand(type) {
|
async onCommand(type) {
|
||||||
try {
|
try {
|
||||||
await post('/api/v1/admin/devices/' + this.deviceId + '/command', { opcode: type })
|
await post('/api/v1/admin/devices/' + this.deviceId + '/command', { opcode: type })
|
||||||
@@ -155,7 +160,7 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
onViewLogs() {
|
onViewLogs() {
|
||||||
uni.redirectTo({ url: '/pages/log/index?device_id=' + this.deviceId })
|
this.$emit('navigate', 'log', { device_id: this.deviceId })
|
||||||
},
|
},
|
||||||
onCheckUpdate() {
|
onCheckUpdate() {
|
||||||
uni.showToast({ title: '正在检查更新...', icon: 'none' })
|
uni.showToast({ title: '正在检查更新...', icon: 'none' })
|
||||||
@@ -166,7 +171,7 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@import '../../styles/common.css';
|
@import '../styles/common.css';
|
||||||
|
|
||||||
.back-link {
|
.back-link {
|
||||||
color: #E6508C;
|
color: #E6508C;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<AdminLayout currentPage="/pages/device/index">
|
<view>
|
||||||
<view class="toolbar">
|
<view class="toolbar">
|
||||||
<view class="header-actions">
|
<view class="header-actions">
|
||||||
<input
|
<input
|
||||||
@@ -56,23 +56,23 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="batch-hint">最多 500 个设备</view>
|
<view class="batch-hint">最多 500 个设备</view>
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
</AdminLayout>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { get, post } from '../../utils/request'
|
import { get, post } from '../utils/request'
|
||||||
import { exportCSV } from '../../utils/export'
|
import { exportCSV } from '../utils/export'
|
||||||
import { formatDateShort } from '../../utils/format'
|
import { formatDateShort } from '../utils/format'
|
||||||
import { listMixin } from '../../utils/useList'
|
import { listMixin } from '../utils/useList'
|
||||||
import AdminLayout from '../../components/AdminLayout.vue'
|
import DataTable from '../components/DataTable.vue'
|
||||||
import DataTable from '../../components/DataTable.vue'
|
import ConfirmModal from '../components/ConfirmModal.vue'
|
||||||
import ConfirmModal from '../../components/ConfirmModal.vue'
|
|
||||||
|
|
||||||
var STATUS_MAP = { 1: '未激活', 2: '在线', 3: '离线', 4: '故障' }
|
var STATUS_MAP = { 1: '未激活', 2: '在线', 3: '离线', 4: '故障' }
|
||||||
var STATUS_BADGE = { 2: 'badge badge-success', 3: 'badge badge-warning', 1: 'badge badge-blue', 4: 'badge badge-error' }
|
var STATUS_BADGE = { 2: 'badge badge-success', 3: 'badge badge-warning', 1: 'badge badge-blue', 4: 'badge badge-error' }
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { AdminLayout, DataTable, ConfirmModal },
|
name: 'DeviceListView',
|
||||||
|
components: { DataTable, ConfirmModal },
|
||||||
mixins: [listMixin('/api/v1/admin/devices')],
|
mixins: [listMixin('/api/v1/admin/devices')],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -93,7 +93,7 @@ export default {
|
|||||||
batchImporting: false
|
batchImporting: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onShow() {
|
mounted() {
|
||||||
this.reload()
|
this.reload()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -101,7 +101,7 @@ export default {
|
|||||||
this.loadList({ keyword: this.keyword })
|
this.loadList({ keyword: this.keyword })
|
||||||
},
|
},
|
||||||
onDetail(deviceId) {
|
onDetail(deviceId) {
|
||||||
uni.navigateTo({ url: '/pages/device-detail/index?device_id=' + deviceId })
|
this.$emit('navigate', 'device-detail', { device_id: deviceId })
|
||||||
},
|
},
|
||||||
onCreateDevice() {
|
onCreateDevice() {
|
||||||
var self = this
|
var self = this
|
||||||
@@ -186,7 +186,7 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@import '../../styles/common.css';
|
@import '../styles/common.css';
|
||||||
|
|
||||||
.batch-hint {
|
.batch-hint {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<AdminLayout currentPage="/pages/log/index">
|
<view>
|
||||||
<view class="toolbar">
|
<view class="toolbar">
|
||||||
<view class="header-actions">
|
<view class="header-actions">
|
||||||
<input
|
<input
|
||||||
@@ -35,17 +35,19 @@
|
|||||||
<text class="page-info">共 {{ totalPages }} 页</text>
|
<text class="page-info">共 {{ totalPages }} 页</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</AdminLayout>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { get } from '../../utils/request'
|
import { get } from '../utils/request'
|
||||||
import { exportCSV } from '../../utils/export'
|
import { exportCSV } from '../utils/export'
|
||||||
import { formatDate as formatDateUtil } from '../../utils/format'
|
import { formatDate as formatDateUtil } from '../utils/format'
|
||||||
import AdminLayout from '../../components/AdminLayout.vue'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { AdminLayout },
|
name: 'LogView',
|
||||||
|
props: {
|
||||||
|
device_id: { type: String, default: '' }
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
logs: [],
|
logs: [],
|
||||||
@@ -60,14 +62,15 @@ export default {
|
|||||||
return Math.ceil(this.total / this.pageSize) || 1
|
return Math.ceil(this.total / this.pageSize) || 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad(options) {
|
watch: {
|
||||||
if (options && options.device_id) {
|
device_id: {
|
||||||
this.filters.device_id = options.device_id
|
immediate: true,
|
||||||
|
handler(val) {
|
||||||
|
this.filters.device_id = val || ''
|
||||||
|
this.loadLogs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onShow() {
|
|
||||||
this.loadLogs()
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
async loadLogs() {
|
async loadLogs() {
|
||||||
try {
|
try {
|
||||||
@@ -131,7 +134,7 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@import '../../styles/common.css';
|
@import '../styles/common.css';
|
||||||
|
|
||||||
.log-list {
|
.log-list {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<AdminLayout currentPage="/pages/record/index">
|
<view>
|
||||||
<view class="toolbar">
|
<view class="toolbar">
|
||||||
<view class="header-actions">
|
<view class="header-actions">
|
||||||
<input class="search-input date-input" v-model="dateFrom" placeholder="开始日期" />
|
<input class="search-input date-input" v-model="dateFrom" placeholder="开始日期" />
|
||||||
@@ -58,21 +58,23 @@
|
|||||||
<text class="page-info">共 {{ totalPages }} 页</text>
|
<text class="page-info">共 {{ totalPages }} 页</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</AdminLayout>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { get } from '../../utils/request'
|
import { get } from '../utils/request'
|
||||||
import { exportCSV } from '../../utils/export'
|
import { exportCSV } from '../utils/export'
|
||||||
import { formatDate as formatDateUtil } from '../../utils/format'
|
import { formatDate as formatDateUtil } from '../utils/format'
|
||||||
import AdminLayout from '../../components/AdminLayout.vue'
|
|
||||||
|
|
||||||
var REGION_MAP = {
|
var REGION_MAP = {
|
||||||
1: '左脸', 2: '右脸', 4: '额头', 8: '下巴', 16: '鼻', 32: '左眼', 64: '右眼'
|
1: '左脸', 2: '右脸', 4: '额头', 8: '下巴', 16: '鼻', 32: '左眼', 64: '右眼'
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { AdminLayout },
|
name: 'RecordView',
|
||||||
|
props: {
|
||||||
|
user_id: { type: String, default: '' }
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
records: [],
|
records: [],
|
||||||
@@ -89,8 +91,16 @@ export default {
|
|||||||
return Math.ceil(this.total / this.pageSize) || 1
|
return Math.ceil(this.total / this.pageSize) || 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onShow() {
|
watch: {
|
||||||
this.loadRecords()
|
user_id: {
|
||||||
|
immediate: true,
|
||||||
|
handler(val) {
|
||||||
|
if (val) {
|
||||||
|
this.keyword = val
|
||||||
|
}
|
||||||
|
this.loadRecords()
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async loadRecords() {
|
async loadRecords() {
|
||||||
@@ -147,7 +157,6 @@ export default {
|
|||||||
async onExport() {
|
async onExport() {
|
||||||
try {
|
try {
|
||||||
const data = await get('/api/v1/admin/records', { page: 1, page_size: 9999, keyword: this.keyword, date_from: this.dateFrom, date_to: this.dateTo })
|
const data = await get('/api/v1/admin/records', { page: 1, page_size: 9999, keyword: this.keyword, date_from: this.dateFrom, date_to: this.dateTo })
|
||||||
var self = this
|
|
||||||
var REGION_MAP = { 1: '左脸', 2: '右脸', 4: '额头', 8: '下巴', 16: '鼻', 32: '左眼', 64: '右眼' }
|
var REGION_MAP = { 1: '左脸', 2: '右脸', 4: '额头', 8: '下巴', 16: '鼻', 32: '左眼', 64: '右眼' }
|
||||||
const records = data.records || []
|
const records = data.records || []
|
||||||
exportCSV('records_' + new Date().toISOString().slice(0, 10) + '.csv',
|
exportCSV('records_' + new Date().toISOString().slice(0, 10) + '.csv',
|
||||||
@@ -169,7 +178,7 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@import '../../styles/common.css';
|
@import '../styles/common.css';
|
||||||
|
|
||||||
.search-input {
|
.search-input {
|
||||||
width: 160px;
|
width: 160px;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<AdminLayout currentPage="/pages/settings/index">
|
<view>
|
||||||
<view class="page-card">
|
<view class="page-card">
|
||||||
<view class="section-card">
|
<view class="section-card">
|
||||||
<view class="section-title">修改密码</view>
|
<view class="section-title">修改密码</view>
|
||||||
@@ -122,15 +122,13 @@
|
|||||||
<button class="btn-primary" @click="onSave">保存设置</button>
|
<button class="btn-primary" @click="onSave">保存设置</button>
|
||||||
<button class="btn-default" @click="onReset">重置</button>
|
<button class="btn-default" @click="onReset">重置</button>
|
||||||
</view>
|
</view>
|
||||||
</AdminLayout>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { get, post } from '../../utils/request'
|
import { get, post } from '../utils/request'
|
||||||
import AdminLayout from '../../components/AdminLayout.vue'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { AdminLayout },
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
settings: {
|
settings: {
|
||||||
@@ -149,7 +147,7 @@ export default {
|
|||||||
passwordForm: { old_password: '', new_password: '', confirm_password: '' }
|
passwordForm: { old_password: '', new_password: '', confirm_password: '' }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onShow() {
|
mounted() {
|
||||||
this.loadSettings()
|
this.loadSettings()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
+13
-13
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<AdminLayout currentPage="/pages/subscription/index">
|
<view>
|
||||||
<view class="toolbar">
|
<view class="toolbar">
|
||||||
<view class="header-actions">
|
<view class="header-actions">
|
||||||
<button class="btn-primary btn-sm" @click="showCreate = true">创建订阅</button>
|
<button class="btn-primary btn-sm" @click="showCreate = true">创建订阅</button>
|
||||||
@@ -86,24 +86,24 @@
|
|||||||
<input class="form-input" v-model="createForm.days" placeholder="订阅天数" type="number" />
|
<input class="form-input" v-model="createForm.days" placeholder="订阅天数" type="number" />
|
||||||
</view>
|
</view>
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
</AdminLayout>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { get, post } from '../../utils/request'
|
import { get, post } from '../utils/request'
|
||||||
import { exportCSV } from '../../utils/export'
|
import { exportCSV } from '../utils/export'
|
||||||
import { formatDateShort } from '../../utils/format'
|
import { formatDateShort } from '../utils/format'
|
||||||
import { listMixin } from '../../utils/useList'
|
import { listMixin } from '../utils/useList'
|
||||||
import AdminLayout from '../../components/AdminLayout.vue'
|
import DataTable from '../components/DataTable.vue'
|
||||||
import DataTable from '../../components/DataTable.vue'
|
import ConfirmModal from '../components/ConfirmModal.vue'
|
||||||
import ConfirmModal from '../../components/ConfirmModal.vue'
|
|
||||||
|
|
||||||
var PLAN_MAP = { monthly: '月卡', quarterly: '季卡', yearly: '年卡', trial: '试用' }
|
var PLAN_MAP = { monthly: '月卡', quarterly: '季卡', yearly: '年卡', trial: '试用' }
|
||||||
var STATUS_TEXT = { 1: '生效中', 2: '已过期', 3: '已取消' }
|
var STATUS_TEXT = { 1: '生效中', 2: '已过期', 3: '已取消' }
|
||||||
var STATUS_BADGE = { 1: 'badge badge-success', 2: 'badge badge-error', 3: 'badge badge-default' }
|
var STATUS_BADGE = { 1: 'badge badge-success', 2: 'badge badge-error', 3: 'badge badge-default' }
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { AdminLayout, DataTable, ConfirmModal },
|
name: 'SubscriptionView',
|
||||||
|
components: { DataTable, ConfirmModal },
|
||||||
mixins: [listMixin('/api/v1/admin/subscriptions')],
|
mixins: [listMixin('/api/v1/admin/subscriptions')],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -123,7 +123,7 @@ export default {
|
|||||||
createForm: { user_id: '', plan: 'monthly', days: 30 }
|
createForm: { user_id: '', plan: 'monthly', days: 30 }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onShow() {
|
mounted() {
|
||||||
this.reload()
|
this.reload()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -157,7 +157,7 @@ export default {
|
|||||||
},
|
},
|
||||||
onDetail(item) {
|
onDetail(item) {
|
||||||
if (item.user_id) {
|
if (item.user_id) {
|
||||||
uni.navigateTo({ url: '/pages/user-detail/index?user_id=' + item.user_id })
|
this.$emit('navigate', 'user-detail', { user_id: String(item.user_id) })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onExtend(item) {
|
onExtend(item) {
|
||||||
@@ -232,7 +232,7 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@import '../../styles/common.css';
|
@import '../styles/common.css';
|
||||||
|
|
||||||
.stats-row {
|
.stats-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
+20
-16
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<AdminLayout currentPage="/pages/user/index">
|
<view>
|
||||||
<view class="back-link" @click="goBack">‹ 返回用户列表</view>
|
<view class="back-link" @click="$emit('navigate', 'user')">‹ 返回用户列表</view>
|
||||||
|
|
||||||
<view class="page-card" v-if="user">
|
<view class="page-card" v-if="user">
|
||||||
<view class="page-header">
|
<view class="page-header">
|
||||||
@@ -77,25 +77,33 @@
|
|||||||
<text class="view-all-link" @click="goRecords">查看全部{{ user.treatment_count }}条记录 ›</text>
|
<text class="view-all-link" @click="goRecords">查看全部{{ user.treatment_count }}条记录 ›</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</AdminLayout>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { get } from '../../utils/request'
|
import { get } from '../utils/request'
|
||||||
import { formatDateShort } from '../../utils/format'
|
import { formatDateShort } from '../utils/format'
|
||||||
import AdminLayout from '../../components/AdminLayout.vue'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { AdminLayout },
|
props: {
|
||||||
|
user_id: { type: String, default: '' }
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
user: null,
|
user: null,
|
||||||
userId: ''
|
userId: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad(options) {
|
watch: {
|
||||||
this.userId = options.user_id || ''
|
user_id: {
|
||||||
this.loadUser()
|
immediate: true,
|
||||||
|
handler(val) {
|
||||||
|
if (val) {
|
||||||
|
this.userId = val
|
||||||
|
this.loadUser()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async loadUser() {
|
async loadUser() {
|
||||||
@@ -105,11 +113,8 @@ export default {
|
|||||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
goBack() {
|
|
||||||
uni.navigateBack()
|
|
||||||
},
|
|
||||||
goRecords() {
|
goRecords() {
|
||||||
uni.navigateTo({ url: '/pages/record/index?user_id=' + this.userId })
|
this.$emit('navigate', 'record', { user_id: this.userId })
|
||||||
},
|
},
|
||||||
subStatusText(status) {
|
subStatusText(status) {
|
||||||
return status === 1 ? '已订阅' : '未订阅'
|
return status === 1 ? '已订阅' : '未订阅'
|
||||||
@@ -124,7 +129,7 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@import '../../styles/common.css';
|
@import '../styles/common.css';
|
||||||
|
|
||||||
.back-link {
|
.back-link {
|
||||||
color: #E6508C;
|
color: #E6508C;
|
||||||
@@ -260,5 +265,4 @@ export default {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<AdminLayout currentPage="/pages/user/index">
|
<view>
|
||||||
<view class="toolbar">
|
<view class="toolbar">
|
||||||
<view class="header-actions">
|
<view class="header-actions">
|
||||||
<input
|
<input
|
||||||
@@ -57,17 +57,16 @@
|
|||||||
<text class="page-info">共 {{ totalPages }} 页</text>
|
<text class="page-info">共 {{ totalPages }} 页</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</AdminLayout>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { get } from '../../utils/request'
|
import { get } from '../utils/request'
|
||||||
import { exportCSV } from '../../utils/export'
|
import { exportCSV } from '../utils/export'
|
||||||
import { formatDateShort } from '../../utils/format'
|
import { formatDateShort } from '../utils/format'
|
||||||
import AdminLayout from '../../components/AdminLayout.vue'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { AdminLayout },
|
name: 'UserListView',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
users: [],
|
users: [],
|
||||||
@@ -82,7 +81,7 @@ export default {
|
|||||||
return Math.ceil(this.total / this.pageSize) || 1
|
return Math.ceil(this.total / this.pageSize) || 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onShow() {
|
mounted() {
|
||||||
this.loadUsers()
|
this.loadUsers()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -104,10 +103,10 @@ export default {
|
|||||||
this.loadUsers()
|
this.loadUsers()
|
||||||
},
|
},
|
||||||
onDetail(userId) {
|
onDetail(userId) {
|
||||||
uni.navigateTo({ url: '/pages/user-detail/index?user_id=' + userId })
|
this.$emit('navigate', 'user-detail', { user_id: userId })
|
||||||
},
|
},
|
||||||
onViewRecords(userId) {
|
onViewRecords(userId) {
|
||||||
uni.navigateTo({ url: '/pages/record/index?user_id=' + userId })
|
this.$emit('navigate', 'record', { user_id: userId })
|
||||||
},
|
},
|
||||||
subStatusText(status) {
|
subStatusText(status) {
|
||||||
return status === 1 ? '已订阅' : '未订阅'
|
return status === 1 ? '已订阅' : '未订阅'
|
||||||
@@ -136,7 +135,7 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@import '../../styles/common.css';
|
@import '../styles/common.css';
|
||||||
|
|
||||||
.user-cell {
|
.user-cell {
|
||||||
display: flex;
|
display: flex;
|
||||||
在新工单中引用
屏蔽一个用户