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