Merge remote-tracking branch 'origin/huangzhenbao-admin'

This commit is contained in:
not-like-juvenile
2026-03-18 17:14:05 +08:00
676 changed files with 25158 additions and 46646 deletions

View File

@@ -26,7 +26,7 @@
</view>
<view class="table-body">
<view v-for="item in dataList" :key="item.id" class="table-row">
<view v-for="item in pagedList" :key="item.id" class="table-row">
<view class="col col-id"><text>{{ item.id }}</text></view>
<view class="col col-account"><text>{{ item.account }}</text></view>
<view class="col col-desc"><text>{{ item.desc }}</text></view>
@@ -50,27 +50,88 @@
</view>
</view>
<!-- 分页 (模拟) -->
<view class="pagination">
<text class="page-info">共 2 条 20条/页</text>
<view class="page-btns">
<text class="p-btn disabled">˂</text>
<text class="p-btn active">1</text>
<text class="p-btn disabled">˃</text>
</view>
</view>
<CommonPagination
v-if="true"
:total="total"
:loading="false"
:currentPage="currentPage"
:pageSize="pageSize"
:pageSizeOptionLabels="pageSizeOptionLabels"
:pageSizeIndex="pageSizeIndex"
:visiblePages="visiblePages"
:totalPage="totalPage"
:jumpPageInput="jumpPageInput"
@page-size-change="handlePageSizeChange"
@page-change="handlePageChange"
@update:jumpPageInput="(val: string) => { jumpPageInput.value = val }"
@jump-page="handleJumpPage"
/>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import { ref, computed } from 'vue'
import CommonPagination from '@/components/CommonPagination/CommonPagination.uvue'
// ========== MOCK DATA START ==========
// TODO: 接真实接口时替换此处 dataList 为 fetchApiAccountList() 调用
const dataList = ref([
{ id: 9, account: 'ceshi', desc: 'ceshi', addTime: '2024-09-05 11:16:07', lastTime: '暂无', lastIp: '', status: true },
{ id: 10, account: 'tuoluojiang', desc: '', addTime: '2024-09-09 10:12:54', lastTime: '2024-09-10 19:15:01', lastIp: '124.221.49.234', status: true }
{ id: 10, account: 'tuoluojiang', desc: '', addTime: '2024-09-09 10:12:54', lastTime: '2024-09-10 19:15:01', lastIp: '124.221.49.234', status: true },
{ id: 11, account: 'shop_api_01', desc: '商城对外接口', addTime: '2024-10-01 09:00:00', lastTime: '2024-10-15 14:22:11', lastIp: '10.0.0.1', status: true },
{ id: 12, account: 'erp_sync', desc: 'ERP同步接口', addTime: '2024-10-05 10:30:00', lastTime: '2024-10-20 08:10:00', lastIp: '192.168.1.100', status: true },
{ id: 13, account: 'wms_api', desc: '仓储系统接口', addTime: '2024-10-08 14:00:00', lastTime: '暂无', lastIp: '', status: false },
{ id: 14, account: 'oms_push', desc: '订单推送接口', addTime: '2024-10-10 11:20:00', lastTime: '2024-11-01 09:30:00', lastIp: '172.16.0.50', status: true },
{ id: 15, account: 'report_api', desc: '报表数据接口', addTime: '2024-10-12 15:45:00', lastTime: '2024-11-05 17:00:00', lastIp: '10.10.10.1', status: true },
{ id: 16, account: 'crm_sync', desc: 'CRM数据同步', addTime: '2024-10-15 09:00:00', lastTime: '暂无', lastIp: '', status: false },
{ id: 17, account: 'bi_export', desc: 'BI数据导出', addTime: '2024-10-18 10:00:00', lastTime: '2024-11-10 08:20:00', lastIp: '192.168.2.200', status: true },
{ id: 18, account: 'logistics_api', desc: '物流回调接口', addTime: '2024-10-20 14:30:00', lastTime: '2024-11-08 12:00:00', lastIp: '10.0.0.88', status: true },
{ id: 19, account: 'sms_gateway', desc: '短信网关接口', addTime: '2024-10-22 16:00:00', lastTime: '2024-11-12 09:45:00', lastIp: '203.0.113.5', status: true },
{ id: 20, account: 'pay_notify', desc: '支付回调接口', addTime: '2024-10-25 09:30:00', lastTime: '2024-11-15 11:00:00', lastIp: '47.100.200.1', status: true },
{ id: 21, account: 'stock_alert', desc: '库存预警接口', addTime: '2024-10-28 11:00:00', lastTime: '暂无', lastIp: '', status: false },
{ id: 22, account: 'audit_log', desc: '审计日志接口', addTime: '2024-11-01 10:00:00', lastTime: '2024-11-16 14:30:00', lastIp: '10.20.30.40', status: true },
{ id: 23, account: 'open_api_v2', desc: '对外开放接口v2', addTime: '2024-11-05 09:00:00', lastTime: '2024-11-17 10:10:00', lastIp: '120.55.66.77', status: true },
{ id: 24, account: 'internal_bot', desc: '内部机器人接口', addTime: '2024-11-08 13:15:00', lastTime: '2024-11-18 08:00:00', lastIp: '10.0.1.99', status: true },
{ id: 25, account: 'data_clean', desc: '数据清洗任务接口', addTime: '2024-11-10 15:00:00', lastTime: '暂无', lastIp: '', status: false },
{ id: 26, account: 'search_sync', desc: '搜索索引同步', addTime: '2024-11-12 10:30:00', lastTime: '2024-11-19 09:00:00', lastIp: '10.30.40.50', status: true },
{ id: 27, account: 'push_gateway', desc: '消息推送縯道', addTime: '2024-11-15 09:45:00', lastTime: '2024-11-20 11:30:00', lastIp: '47.200.100.5', status: true },
{ id: 28, account: 'cdn_purge', desc: 'CDN刷新接口', addTime: '2024-11-18 14:00:00', lastTime: '2024-11-21 13:00:00', lastIp: '1.2.3.4', status: true }
])
// ========== MOCK DATA END ==========
// ========== PAGINATION STATE ==========
const currentPage = ref(1)
const pageSize = ref(15)
const jumpPageInput = ref('')
const pageSizeOptions = [10, 15, 20, 30, 50]
const pageSizeOptionLabels = computed(() => pageSizeOptions.map((n: number) => `${n}条/页`))
const pageSizeIndex = computed(() => { const idx = pageSizeOptions.indexOf(pageSize.value); return idx >= 0 ? idx : 0 })
const total = computed(() => dataList.value.length)
const totalPage = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)))
const pagedList = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return dataList.value.slice(start, start + pageSize.value)
})
const visiblePages = computed((): number[] => {
const t = totalPage.value; const cur = currentPage.value
if (t <= 7) return Array.from({ length: t }, (_: any, i: number) => i + 1)
if (cur <= 4) return [1, 2, 3, 4, 5, -1, t]
if (cur >= t - 3) return [1, -1, t - 4, t - 3, t - 2, t - 1, t]
return [1, -1, cur - 1, cur, cur + 1, -1, t]
})
const handlePageChange = (p: number) => { currentPage.value = p }
const handlePageSizeChange = (e: any) => {
const idx = Number(e.detail.value)
pageSize.value = pageSizeOptions[idx] ?? pageSizeOptions[0]
currentPage.value = 1
}
const handleJumpPage = () => {
const p = parseInt(jumpPageInput.value)
if (!isNaN(p) && p >= 1 && p <= totalPage.value) currentPage.value = p
}
// ========== END PAGINATION STATE ==========
function addAccount() {
uni.showToast({ title: '添加账号', icon: 'none' })
@@ -103,8 +164,7 @@ function deleteItem(item: any) {
<style scoped lang="scss">
.admin-page {
padding: 24px;
background-color: #f5f7f9;
/* 使用 Layout 的背景和内边距 */
min-height: 100vh;
}
@@ -183,7 +243,7 @@ function deleteItem(item: any) {
.col-last-time { width: 180px; }
.col-last-ip { width: 150px; }
.col-status { width: 100px; justify-content: center; }
.col-action { width: 200px; justify-content: flex-end; }
.col-action { width: 200px; justify-content: flex-end; display: flex; flex-direction: row; }
.status-tag {
width: 60px;

View File

@@ -1,24 +0,0 @@
<template>
<AdminLayout currentPage="data-city-data">
<view class="page">
<view class="header">
<text class="title">城市数据</text>
</view>
<view class="content">
<text class="tip">TODO: 城市数据</text>
</view>
</view>
</AdminLayout>
</template>
<script setup lang="uts">
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
</script>
<style scoped>
.page { padding: 16px; }
.title { font-size: 18px; font-weight: 600; }
.tip { color: #999; margin-top: 8px; display: block; }
</style>

View File

@@ -20,7 +20,7 @@
</view>
<view class="table-body">
<view v-for="item in dataList" :key="item.id" class="table-row">
<view v-for="item in pagedList" :key="item.id" class="table-row">
<view class="col col-id"><text>{{ item.id }}</text></view>
<view class="col col-name">
<text class="expand-icon">˃</text>
@@ -38,13 +38,32 @@
</view>
</view>
</view>
<CommonPagination
v-if="true"
:total="total"
:loading="false"
:currentPage="currentPage"
:pageSize="pageSize"
:pageSizeOptionLabels="pageSizeOptionLabels"
:pageSizeIndex="pageSizeIndex"
:visiblePages="visiblePages"
:totalPage="totalPage"
:jumpPageInput="jumpPageInput"
@page-size-change="handlePageSizeChange"
@page-change="handlePageChange"
@update:jumpPageInput="(val: string) => { jumpPageInput.value = val }"
@jump-page="handleJumpPage"
/>
</view>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import { ref, computed } from 'vue'
import CommonPagination from '@/components/CommonPagination/CommonPagination.uvue'
// ========== MOCK DATA START ==========
// TODO: 接真实接口时替换此处 dataList 为 fetchCityList() 调用
const dataList = ref([
{ id: 1, name: '北京市', parentName: '中国' },
{ id: 2, name: '天津市', parentName: '中国' },
@@ -59,8 +78,61 @@ const dataList = ref([
{ id: 11, name: '浙江省', parentName: '中国' },
{ id: 12, name: '安徽省', parentName: '中国' },
{ id: 13, name: '福建省', parentName: '中国' },
{ id: 14, name: '江西省', parentName: '中国' }
{ id: 14, name: '江西省', parentName: '中国' },
{ id: 15, name: '山东省', parentName: '中国' },
{ id: 16, name: '河南省', parentName: '中国' },
{ id: 17, name: '湖北省', parentName: '中国' },
{ id: 18, name: '湖南省', parentName: '中国' },
{ id: 19, name: '广东省', parentName: '中国' },
{ id: 20, name: '广西壮族自治区', parentName: '中国' },
{ id: 21, name: '海南省', parentName: '中国' },
{ id: 22, name: '重庆市', parentName: '中国' },
{ id: 23, name: '四川省', parentName: '中国' },
{ id: 24, name: '贵州省', parentName: '中国' },
{ id: 25, name: '云南省', parentName: '中国' },
{ id: 26, name: '西藏自治区', parentName: '中国' },
{ id: 27, name: '陕西省', parentName: '中国' },
{ id: 28, name: '甘肃省', parentName: '中国' },
{ id: 29, name: '青海省', parentName: '中国' },
{ id: 30, name: '宁夏回族自治区', parentName: '中国' },
{ id: 31, name: '新疆维吾尔自治区', parentName: '中国' },
{ id: 32, name: '香港特别行政区', parentName: '中国' },
{ id: 33, name: '澳门特别行政区', parentName: '中国' },
{ id: 34, name: '台湾省', parentName: '中国' }
])
// ========== MOCK DATA END ==========
// ========== PAGINATION STATE ==========
const currentPage = ref(1)
const pageSize = ref(15)
const jumpPageInput = ref('')
const pageSizeOptions = [10, 15, 20, 30, 50]
const pageSizeOptionLabels = computed(() => pageSizeOptions.map((n: number) => `${n}条/页`))
const pageSizeIndex = computed(() => { const idx = pageSizeOptions.indexOf(pageSize.value); return idx >= 0 ? idx : 0 })
const total = computed(() => dataList.value.length)
const totalPage = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)))
const pagedList = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return dataList.value.slice(start, start + pageSize.value)
})
const visiblePages = computed((): number[] => {
const t = totalPage.value; const cur = currentPage.value
if (t <= 7) return Array.from({ length: t }, (_: any, i: number) => i + 1)
if (cur <= 4) return [1, 2, 3, 4, 5, -1, t]
if (cur >= t - 3) return [1, -1, t - 4, t - 3, t - 2, t - 1, t]
return [1, -1, cur - 1, cur, cur + 1, -1, t]
})
const handlePageChange = (p: number) => { currentPage.value = p }
const handlePageSizeChange = (e: any) => {
const idx = Number(e.detail.value)
pageSize.value = pageSizeOptions[idx] ?? pageSizeOptions[0]
currentPage.value = 1
}
const handleJumpPage = () => {
const p = parseInt(jumpPageInput.value)
if (!isNaN(p) && p >= 1 && p <= totalPage.value) currentPage.value = p
}
// ========== END PAGINATION STATE ==========
function addProvince() {
uni.showToast({ title: '添加省份', icon: 'none' })
@@ -97,8 +169,7 @@ function deleteItem(item: any) {
<style scoped lang="scss">
.admin-page {
padding: 20px;
background-color: #f5f7f9;
/* 使用 Layout 的背景和内边距 */
min-height: 100vh;
}
@@ -174,7 +245,7 @@ function deleteItem(item: any) {
.col-id { width: 100px; }
.col-name { flex: 2; }
.col-parent { flex: 1; }
.col-action { width: 200px; justify-content: flex-end; }
.col-action { width: 200px; justify-content: flex-end; display: flex; flex-direction: row; }
.expand-icon {
margin-right: 8px;

View File

@@ -1,21 +0,0 @@
<template>
<AdminLayout currentPage="data-clear-data">
<view class="page">
<view class="header">
<text class="title">清除数据</text>
</view>
<view class="content">
<text class="tip">TODO: 清除数据</text>
</view>
</view>
</AdminLayout>
</template>
<script setup lang="uts">
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
</script>
<style scoped>
.page { padding: 16px; }
.title { font-size: 18px; font-weight: 600; }
.tip { color: #999; margin-top: 8px; display: block; }
</style>

View File

@@ -66,8 +66,7 @@ function handleAction(card: any) {
<style scoped lang="scss">
.admin-page {
padding: 24px;
background-color: #f5f7f9;
/* 使用 Layout 的背景和内边距 */
min-height: 100vh;
}

View File

@@ -1,21 +0,0 @@
<template>
<AdminLayout currentPage="data-logistics-company">
<view class="page">
<view class="header">
<text class="title">物流公司</text>
</view>
<view class="content">
<text class="tip">TODO: 物流公司</text>
</view>
</view>
</AdminLayout>
</template>
<script setup lang="uts">
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
</script>
<style scoped>
.page { padding: 16px; }
.title { font-size: 18px; font-weight: 600; }
.tip { color: #999; margin-top: 8px; display: block; }
</style>

View File

@@ -37,7 +37,7 @@
</view>
<view class="table-body">
<view v-for="item in dataList" :key="item.id" class="table-row">
<view v-for="item in pagedList" :key="item.id" class="table-row">
<view class="col col-id"><text>{{ item.id }}</text></view>
<view class="col col-name"><text>{{ item.name }}</text></view>
<view class="col col-code"><text>{{ item.code }}</text></view>
@@ -52,18 +52,33 @@
</view>
</view>
<!-- 分页 (模拟) -->
<view class="pagination">
<!-- 这里可以放分页组件 -->
</view>
<CommonPagination
v-if="true"
:total="total"
:loading="false"
:currentPage="currentPage"
:pageSize="pageSize"
:pageSizeOptionLabels="pageSizeOptionLabels"
:pageSizeIndex="pageSizeIndex"
:visiblePages="visiblePages"
:totalPage="totalPage"
:jumpPageInput="jumpPageInput"
@page-size-change="handlePageSizeChange"
@page-change="handlePageChange"
@update:jumpPageInput="(val: string) => { jumpPageInput.value = val }"
@jump-page="handleJumpPage"
/>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import { ref, computed } from 'vue'
import CommonPagination from '@/components/CommonPagination/CommonPagination.uvue'
// ========== MOCK DATA START ==========
// TODO: 接真实接口时替换此处 dataList 为 fetchLogisticsList() 调用
const dataList = ref([
{ id: 2, name: '顺丰速运', code: 'shunfeng', sort: 0, show: true },
{ id: 3, name: '圆通速递', code: 'yuantong', sort: 0, show: true },
@@ -77,8 +92,48 @@ const dataList = ref([
{ id: 11, name: 'EMS', code: 'ems', sort: 0, show: true },
{ id: 12, name: '德邦物流', code: 'debangwuliu', sort: 0, show: true },
{ id: 13, name: '德邦快递', code: 'debangkuaidi', sort: 0, show: true },
{ id: 14, name: '众邮快递', code: 'zhongyouex', sort: 0, show: true }
{ id: 14, name: '众邮快递', code: 'zhongyouex', sort: 0, show: true },
{ id: 15, name: '安能快运', code: 'annengkuaiyun', sort: 0, show: true },
{ id: 16, name: '壹米滴答', code: 'yimidida', sort: 0, show: false },
{ id: 17, name: '宅急送', code: 'zhaijisong', sort: 0, show: true },
{ id: 18, name: '苏宁物流', code: 'suning', sort: 0, show: true },
{ id: 19, name: '中通快运', code: 'zhongtongkuaiyun', sort: 0, show: true },
{ id: 20, name: '芝麻开门', code: 'zhimakaimen', sort: 0, show: false },
{ id: 21, name: '优速快递', code: 'youshuwuliu', sort: 0, show: true }
])
// ========== MOCK DATA END ==========
// ========== PAGINATION STATE ==========
const currentPage = ref(1)
const pageSize = ref(15)
const jumpPageInput = ref('')
const pageSizeOptions = [10, 15, 20, 30, 50]
const pageSizeOptionLabels = computed(() => pageSizeOptions.map((n: number) => `${n}条/页`))
const pageSizeIndex = computed(() => { const idx = pageSizeOptions.indexOf(pageSize.value); return idx >= 0 ? idx : 0 })
const total = computed(() => dataList.value.length)
const totalPage = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)))
const pagedList = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return dataList.value.slice(start, start + pageSize.value)
})
const visiblePages = computed((): number[] => {
const t = totalPage.value; const cur = currentPage.value
if (t <= 7) return Array.from({ length: t }, (_: any, i: number) => i + 1)
if (cur <= 4) return [1, 2, 3, 4, 5, -1, t]
if (cur >= t - 3) return [1, -1, t - 4, t - 3, t - 2, t - 1, t]
return [1, -1, cur - 1, cur, cur + 1, -1, t]
})
const handlePageChange = (p: number) => { currentPage.value = p }
const handlePageSizeChange = (e: any) => {
const idx = Number(e.detail.value)
pageSize.value = pageSizeOptions[idx] ?? pageSizeOptions[0]
currentPage.value = 1
}
const handleJumpPage = () => {
const p = parseInt(jumpPageInput.value)
if (!isNaN(p) && p >= 1 && p <= totalPage.value) currentPage.value = p
}
// ========== END PAGINATION STATE ==========
function onSearch() {
uni.showToast({ title: '查询中...', icon: 'none' })
@@ -104,8 +159,7 @@ function editItem(item: any) {
<style scoped lang="scss">
.admin-page {
padding: 20px;
background-color: #f5f7f9;
/* 使用 Layout 的背景和内边距 */
min-height: 100vh;
}

View File

@@ -1,268 +0,0 @@
<template>
<view class="admin-page">
<view class="admin-sections">
<!-- 搜索栏 -->
<view class="admin-card filter-card">
<view class="filter-row">
<view class="filter-item">
<text class="label">是否显示:</text>
<view class="filter-select">
<text class="select-placeholder">请选择</text>
<text class="arrow">▼</text>
</view>
</view>
<view class="filter-item">
<text class="label">分类名称:</text>
<input class="filter-input" placeholder="请输入分类名称" />
</view>
<button class="btn primary" @click="onSearch">查询分类</button>
</view>
<view class="filter-row mt-12">
<view class="filter-item">
<text class="label">配置名称:</text>
<input class="filter-input" placeholder="请输入配置名称" />
</view>
<button class="btn primary" @click="onSearchConfig">查询配置</button>
</view>
</view>
<!-- 内容区 -->
<view class="admin-card content-card">
<!-- 操作按钮行 -->
<view class="action-bar">
<button class="btn primary small" @click="onAdd">添加配置分类</button>
</view>
<!-- 表格 -->
<view class="table-container list-table">
<view class="table-header">
<view class="col col-id"><text>ID</text></view>
<view class="col col-name"><text>分类名称</text></view>
<view class="col col-field"><text>分类字段</text></view>
<view class="col col-status"><text>状态</text></view>
<view class="col col-ops"><text>操作</text></view>
</view>
<view class="table-body">
<view v-for="item in dataList" :key="item.id" class="table-row">
<view class="col col-id"><text>{{ item.id }}</text></view>
<view class="col col-name">
<text class="expand-icon" v-if="item.hasChildren">▶</text>
<text>{{ item.name }}</text>
</view>
<view class="col col-field"><text>{{ item.field }}</text></view>
<view class="col col-status">
<switch :checked="item.status" color="#1890ff" scale="0.8" />
</view>
<view class="col col-ops">
<text class="op-link" @click="onConfig(item)">配置列表</text>
<view class="op-divider">|</view>
<text class="op-link" @click="onEdit(item)">编辑</text>
<view class="op-divider">|</view>
<text class="op-link op-danger" @click="onDelete(item)">删除</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
const dataList = ref([
{ id: 9, name: '分销配置', field: 'fenxiao', status: true, hasChildren: true },
{ id: 65, name: '接口设置', field: 'system_serve', status: true, hasChildren: true },
{ id: 69, name: '客服配置', field: 'kefu_config', status: true, hasChildren: true },
{ id: 78, name: '应用配置', field: 'sys_app', status: true, hasChildren: true },
{ id: 100, name: '用户配置', field: 'system_user_config', status: true, hasChildren: true },
{ id: 113, name: '订单配置', field: 'order_config', status: true, hasChildren: true },
{ id: 129, name: '系统配置', field: 'system_config', status: true, hasChildren: true },
{ id: 136, name: '商品配置', field: 'product_config', status: true, hasChildren: true }
])
function onSearch() {
uni.showToast({ title: '查询分类', icon: 'none' })
}
function onSearchConfig() {
uni.showToast({ title: '查询配置', icon: 'none' })
}
function onAdd() {
uni.showToast({ title: '添加配置分类', icon: 'none' })
}
function onConfig(item: any) {
uni.showToast({ title: '配置列表: ' + item.name, icon: 'none' })
}
function onEdit(item: any) {
uni.showToast({ title: '编辑: ' + item.name, icon: 'none' })
}
function onDelete(item: any) {
uni.showModal({
title: '提示',
content: '确定要删除该分类吗?',
success: (res) => {
if (res.confirm) {
uni.showToast({ title: '已删除', icon: 'none' })
}
}
})
}
</script>
<style scoped lang="scss">
.admin-page {
padding: 20px;
background-color: #f5f7f9;
min-height: 100vh;
}
.admin-card {
background-color: #fff;
border-radius: 4px;
padding: 20px;
margin-bottom: 20px;
}
.filter-row {
display: flex;
flex-direction: row;
align-items: center;
}
.mt-12 { margin-top: 12px; }
.filter-item {
display: flex;
flex-direction: row;
align-items: center;
margin-right: 24px;
}
.label {
font-size: 14px;
color: #333;
width: 80px;
}
.filter-select {
width: 200px;
height: 32px;
border: 1px solid #d9d9d9;
border-radius: 2px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 0 10px;
}
.select-placeholder {
color: #bfbfbf;
font-size: 14px;
}
.arrow {
font-size: 10px;
color: #bfbfbf;
}
.filter-input {
width: 200px;
height: 32px;
border: 1px solid #d9d9d9;
border-radius: 2px;
padding: 0 10px;
font-size: 14px;
}
.btn {
height: 32px;
padding: 0 20px;
border-radius: 2px;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border: 1px solid #d9d9d9;
background-color: #fff;
}
.btn.primary {
background-color: #1890ff;
border-color: #1890ff;
color: #fff;
}
.btn.small {
height: 28px;
padding: 0 10px;
font-size: 12px;
}
.action-bar {
margin-bottom: 16px;
}
.table-container {
width: 100%;
border: 1px solid #f0f0f0;
}
.table-header {
display: flex;
flex-direction: row;
background-color: #fafafa;
border-bottom: 1px solid #f0f0f0;
}
.table-row {
display: flex;
flex-direction: row;
border-bottom: 1px solid #f0f0f0;
}
.col {
padding: 12px 16px;
display: flex;
align-items: center;
font-size: 14px;
color: #333;
}
.col-id { width: 80px; }
.col-name { flex: 2; }
.col-field { flex: 2; }
.col-status { width: 100px; justify-content: center; }
.col-ops { flex: 2; justify-content: flex-end; }
.expand-icon {
margin-right: 8px;
font-size: 10px;
color: #999;
}
.op-link {
color: #1890ff;
cursor: pointer;
margin: 0 4px;
}
.op-danger {
color: #ff4d4f;
}
.op-divider {
color: #f0f0f0;
margin: 0 4px;
}
</style>

View File

@@ -0,0 +1,150 @@
<template>
<view class="admin-page category-page">
<view class="admin-sections">
<!-- 筛选区域 (1:1 复刻 CRMEB: 横向排列的过滤器) -->
<view class="admin-card filter-card">
<view class="filter-row">
<view class="filter-item">
<text class="label">是否显示:</text>
<view class="compact-select-wrapper">
<text class="select-text">请选择</text>
<text class="arrow">▼</text>
</view>
</view>
<view class="filter-item">
<text class="label">分类名称:</text>
<input class="filter-input" placeholder="请输入分类名称" />
</view>
<button class="btn-search">查询分类</button>
</view>
<view class="filter-row second-row">
<view class="filter-item">
<text class="label">配置名称:</text>
<input class="filter-input" placeholder="请输入配置名称" />
</view>
<button class="btn-search">查询配置</button>
</view>
</view>
<!-- 表格板块 -->
<view class="admin-card table-card">
<view class="table-header-actions">
<view class="btn-add">添加配置分类</view>
</view>
<!-- 模拟表格 (CRMEB风格: 线条简约, 蓝调表头) -->
<view class="table-container">
<view class="table-thead">
<view class="th cell-id">ID</view>
<view class="th cell-name">分类名称</view>
<view class="th cell-field">分类字段</view>
<view class="th cell-status">状态</view>
<view class="th cell-ops">操作</view>
</view>
<view class="table-tbody">
<view v-for="item in categoryList" :key="item.id" class="tr">
<view class="td cell-id">{{ item.id }}</view>
<view class="td cell-name">
<text class="tree-icon">▶</text>
<text>{{ item.name }}</text>
</view>
<view class="td cell-field">{{ item.field }}</view>
<view class="td cell-status">
<view class="toggle-switch" :class="{ active: item.status }" @click="item.status = !item.status">
<view class="switch-ball"></view>
</view>
</view>
<view class="td cell-ops">
<text class="op-link">配置列表</text>
<view class="divider"></view>
<text class="op-link">编辑</text>
<view class="divider"></view>
<text class="op-link delete">删除</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
const categoryList = ref([
{ id: 9, name: '分销配置', field: 'fenxiao', status: true },
{ id: 65, name: '接口设置', field: 'system_serve', status: true },
{ id: 69, name: '客服配置', field: 'kefu_config', status: true },
{ id: 78, name: '应用配置', field: 'sys_app', status: true },
{ id: 100, name: '用户配置', field: 'system_user_config', status: true },
{ id: 113, name: '订单配置', field: 'order_config', status: true },
{ id: 129, name: '系统配置', field: 'system_config', status: true },
{ id: 136, name: '商品配置', field: 'product_config', status: true }
])
</script>
<style scoped>
.category-page { padding: 0; background-color: transparent; }
.filter-card {
margin-bottom: var(--admin-section-gap);
padding: var(--admin-card-padding);
box-shadow: 0 1px 4px rgba(0,21,41,0.08);
border-radius: 8px;
}
.filter-row { display: flex; flex-direction: row; align-items: center; }
.second-row { margin-top: 15px; border-top: 1px dashed #eee; padding-top: 15px; }
.filter-item { display: flex; flex-direction: row; align-items: center; margin-right: 30px; }
.filter-item .label { font-size: 14px; color: #515a6e; margin-right: 10px; }
.compact-select-wrapper {
display: flex; flex-direction: row; align-items: center;
border: 1px solid #dcdee2; width: 180px; height: 32px;
padding: 0 10px; border-radius: 4px; justify-content: space-between;
}
.select-text { font-size: 13px; color: #c5c8ce; }
.arrow { font-size: 10px; color: #808695; }
.filter-input { border: 1px solid #dcdee2; width: 220px; height: 32px; padding: 0 10px; border-radius: 4px; font-size: 13px; }
.btn-search { background-color: #2d8cf0; color: #fff; font-size: 12px; height: 32px; line-height: 32px; padding: 0 15px; border-radius: 4px; margin-right: 10px; border: none; }
.table-card {
padding: var(--admin-card-padding);
box-shadow: 0 1px 4px rgba(0,21,41,0.08);
border-radius: 8px;
}
.btn-add { display: inline-block; background-color: #2d8cf0; color: #fff; font-size: 13px; height: 32px; line-height: 32px; padding: 0 15px; border-radius: 4px; border: none; margin-bottom: 15px; cursor: pointer; }
/* 表格样式 (1:1 复刻 CRMEB 极简蓝风格) */
.table-container { width: 100%; border: 1px solid #e8eaec; border-radius: 4px; overflow: hidden; }
.table-thead { display: flex; flex-direction: row; background-color: #f8f9fa; border-bottom: 1px solid #e8eaec; }
.th { padding: 12px 15px; font-size: 14px; color: #515a6e; font-weight: bold; text-align: left; }
.tr { display: flex; flex-direction: row; border-bottom: 1px solid #e8eaec; align-items: center; background-color: #fff; }
.tr:last-child { border-bottom: none; }
.td { padding: 12px 15px; font-size: 14px; color: #515a6e; }
.cell-id { width: 80px; }
.cell-name { flex: 2; }
.cell-field { flex: 2; }
.cell-status { width: 120px; }
.cell-ops { width: 260px; display: flex; flex-direction: row; align-items: center; justify-content: flex-end; }
.tree-icon { font-size: 12px; color: #808695; margin-right: 8px; }
/* Toggle Switch (复刻) */
.toggle-switch { width: 44px; height: 22px; background-color: #ccc; border-radius: 11px; position: relative; cursor: pointer; transition: all .2s; }
.toggle-switch.active { background-color: #2d8cf0; }
.switch-ball { position: absolute; width: 18px; height: 18px; background-color: #fff; border-radius: 50%; left: 2px; top: 2px; transition: all .2s; }
.toggle-switch.active .switch-ball { transform: translateX(22px); }
.op-link { color: #2d8cf0; font-size: 13px; cursor: pointer; margin: 0 5px; }
.op-link.delete { color: #ed4014; }
.divider { width: 1px; height: 12px; background-color: #e8eaec; margin: 0 5px; }
</style>

View File

@@ -1,4 +1,4 @@
<template>
<template>
<view class="admin-page">
<view class="admin-sections">
<!-- 搜索栏 -->
@@ -12,14 +12,14 @@
</view>
</view>
<!-- 内容区 -->
<view class="admin-card content-card">
<!-- 操作按钮行 -->
<view class="action-bar">
<button class="btn primary small" @click="onAdd">添加数据组</button>
</view>
<!-- 内容区 -->
<view class="admin-card content-card">
<!-- 操作按钮行 -->
<view class="action-bar">
<button class="btn primary small" @click="onAdd">添加数据组</button>
</view>
<!-- 表格 -->
<!-- 表格 -->
<view class="table-container">
<view class="table-header">
<view class="col col-id"><text>ID</text></view>
@@ -30,7 +30,7 @@
</view>
<view class="table-body">
<view v-for="item in dataList" :key="item.id" class="table-row">
<view v-for="item in pagedList" :key="item.id" class="table-row">
<view class="col col-id"><text>{{ item.id }}</text></view>
<view class="col col-key"><text>{{ item.key }}</text></view>
<view class="col col-name"><text>{{ item.name }}</text></view>
@@ -45,13 +45,30 @@
</view>
</view>
</view>
<CommonPagination
v-if="total > 0"
:total="total"
:loading="false"
:currentPage="currentPage"
:pageSize="pageSize"
:pageSizeOptionLabels="pageSizeOptionLabels"
:pageSizeIndex="pageSizeIndex"
:visiblePages="visiblePages"
:totalPage="totalPage"
:jumpPageInput="jumpPageInput"
@page-size-change="handlePageSizeChange"
@page-change="handlePageChange"
@update:jumpPageInput="(val: string) => { jumpPageInput.value = val }"
@jump-page="handleJumpPage"
/>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import { ref, computed } from 'vue'
import CommonPagination from '@/components/CommonPagination/CommonPagination.uvue'
const dataList = ref([
{ id: 49, key: 'routine_seckill_time', name: '秒杀时间段', desc: '秒杀时间段' },
@@ -66,8 +83,48 @@ const dataList = ref([
{ id: 65, key: 'admin_login_slide', name: '后台登录页面幻灯片', desc: '后台登录页面幻灯片' },
{ id: 66, key: 'uni_app_link', name: '前端页面链接', desc: '前端页面链接' },
{ id: 67, key: 'combination_banner', name: '拼团列表轮播图', desc: '拼团列表轮播图' },
{ id: 68, key: 'integral_shop_banner', name: '积分商城轮播图', desc: '积分商城轮播图' }
{ id: 68, key: 'integral_shop_banner', name: '积分商城轮播图', desc: '积分商城轮播图' },
{ id: 70, key: 'live_share_img', name: '直播分享图片', desc: '直播分享图片' },
{ id: 71, key: 'live_home_banner', name: '直播首页轮播图', desc: '直播首页轮播图' },
{ id: 72, key: 'user_welfare', name: '会员权益配置', desc: '会员权益配置' },
{ id: 73, key: 'presale_banner', name: '预售轮播图配置', desc: '预售轮播图配置' },
{ id: 74, key: 'share_image', name: '分享海报图片', desc: '分享海报图片' },
{ id: 75, key: 'coupon_banner', name: '优惠券列表轮播图', desc: '优惠券列表轮播图' },
{ id: 76, key: 'vip_privilege', name: 'VIP特权配置', desc: 'VIP特权配置' }
])
// ========== MOCK DATA END ==========
// ========== PAGINATION STATE ==========
const currentPage = ref(1)
const pageSize = ref(15)
const jumpPageInput = ref('')
const pageSizeOptions = [10, 15, 20, 30, 50]
const pageSizeOptionLabels = computed(() => pageSizeOptions.map((n: number) => `${n}条/页`))
const pageSizeIndex = computed(() => { const idx = pageSizeOptions.indexOf(pageSize.value); return idx >= 0 ? idx : 0 })
const total = computed(() => dataList.value.length)
const totalPage = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)))
const pagedList = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return dataList.value.slice(start, start + pageSize.value)
})
const visiblePages = computed((): number[] => {
const t = totalPage.value; const cur = currentPage.value
if (t <= 7) return Array.from({ length: t }, (_: any, i: number) => i + 1)
if (cur <= 4) return [1, 2, 3, 4, 5, -1, t]
if (cur >= t - 3) return [1, -1, t - 4, t - 3, t - 2, t - 1, t]
return [1, -1, cur - 1, cur, cur + 1, -1, t]
})
const handlePageChange = (p: number) => { currentPage.value = p }
const handlePageSizeChange = (e: any) => {
const idx = Number(e.detail.value)
pageSize.value = pageSizeOptions[idx] ?? pageSizeOptions[0]
currentPage.value = 1
}
const handleJumpPage = () => {
const p = parseInt(jumpPageInput.value)
if (!isNaN(p) && p >= 1 && p <= totalPage.value) currentPage.value = p
}
// ========== END PAGINATION STATE ==========
function onSearch() {
uni.showToast({ title: '查询中...', icon: 'none' })
@@ -100,16 +157,17 @@ function onDelete(item: any) {
<style scoped lang="scss">
.admin-page {
padding: 20px;
background-color: #f5f7f9;
min-height: 100vh;
/* 使用 Layout 的背景和内边距 */
padding: 0;
background-color: transparent;
min-height: auto;
}
.admin-card {
background-color: #fff;
border-radius: 4px;
padding: 20px;
margin-bottom: 20px;
padding: var(--admin-card-padding);
margin-bottom: var(--admin-section-gap);
}
.filter-row {
@@ -202,7 +260,7 @@ function onDelete(item: any) {
.col-key { flex: 2; }
.col-name { flex: 2; }
.col-desc { flex: 2; }
.col-ops { flex: 2; justify-content: flex-end; }
.col-ops { flex: 2; justify-content: flex-end; flex-direction: row;}
.op-link {
color: #1890ff;

View File

@@ -1,192 +0,0 @@
<template>
<view class="admin-page">
<view class="admin-sections">
<!-- 顶部通知 -->
<view class="admin-card alert-card">
<view class="alert-content">
<text class="alert-title">启动定时任务两种方式:</text>
<text class="alert-desc">1、使用命令行启动php think timer start --d; 如果更改了执行周期、编辑是否开启、删除定时任务需要重新启动下定时任务确保生效;</text>
<text class="alert-desc">2、使用接口触发定时任务建议每分钟调用一次接口地址 https://v5.crmeb.net/api/crontab/run</text>
</view>
</view>
<!-- 操作卡片 -->
<view class="admin-card content-card">
<view class="tabs-row">
<view class="tab-item active"><text>系统任务</text></view>
<view class="tab-item"><text>自定义任务</text></view>
</view>
<!-- 表格 -->
<view class="table-container list-table mt-16">
<view class="table-header">
<view class="col col-title"><text>标题</text></view>
<view class="col col-desc"><text>任务说明</text></view>
<view class="col col-cycle"><text>执行周期</text></view>
<view class="col col-status"><text>是否开启</text></view>
<view class="col col-ops"><text>操作</text></view>
</view>
<view class="table-body">
<view v-for="item in dataList" :key="item.id" class="table-row">
<view class="col col-title"><text>{{ item.title }}</text></view>
<view class="col col-desc"><text>{{ item.desc }}</text></view>
<view class="col col-cycle"><text>{{ item.cycle }}</text></view>
<view class="col col-status">
<switch :checked="item.status" color="#1890ff" scale="0.8" />
</view>
<view class="col col-ops">
<text class="op-link" @click="onEdit(item)">编辑</text>
</view>
</view>
</view>
</view>
<!-- 分页 -->
<view class="pagination">
<text class="page-info">共 10 条 15条/页</text>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
const dataList = ref([
{ id: 1, title: '自动开具/冲红电子发票', desc: '每隔10分钟执行自动开具/冲红电子发票', cycle: '每隔10分钟执行一次', status: true },
{ id: 2, title: '清除昨日海报', desc: '每天0时30分0秒执行一次清除昨日海报', cycle: '每天0时30分0秒执行一次', status: true },
{ id: 3, title: '订单商品自动好评', desc: '每隔5分钟执行订单到期商品好评', cycle: '每隔5分钟执行一次', status: true },
{ id: 4, title: '预售商品到期自动下架', desc: '每隔5分钟执行预售商品到期下架', cycle: '每隔5分钟执行一次', status: true },
{ id: 5, title: '订单自动收货', desc: '每隔5分钟执行订单到期自动收货', cycle: '每隔5分钟执行一次', status: true },
{ id: 6, title: '自动更新直播间状态', desc: '每隔3分钟执行更新直播间状态', cycle: '每隔3分钟执行一次', status: true },
{ id: 7, title: '自动更新直播商品状态', desc: '每隔3分钟执行更新直播商品状态', cycle: '每隔3分钟执行一次', status: true },
{ id: 8, title: '到期自动解绑上级', desc: '每隔1分钟执行到期的绑定关系的解除', cycle: '每隔1分钟执行一次', status: true },
{ id: 9, title: '拼团到期订单处理', desc: '每隔1分钟拼团到期后的操作', cycle: '每隔1分钟执行一次', status: true },
{ id: 10, title: '未支付自动取消订单', desc: '每隔30秒执行自动取消到期未支付的订单', cycle: '每隔30秒执行一次', status: true }
])
function onEdit(item: any) {
uni.showToast({ title: '编辑: ' + item.title, icon: 'none' })
}
</script>
<style scoped lang="scss">
.admin-page {
padding: 20px;
background-color: #f5f7f9;
min-height: 100vh;
}
.admin-card {
background-color: #fff;
border-radius: 4px;
padding: 20px;
margin-bottom: 20px;
}
.alert-card {
background-color: #fffbe6;
border: 1px solid #ffe58f;
}
.alert-content {
padding: 8px;
}
.alert-title {
font-size: 14px;
color: #faad14;
margin-bottom: 8px;
font-weight: bold;
}
.alert-desc {
font-size: 13px;
color: #faad14;
line-height: 20px;
margin-bottom: 4px;
}
.tabs-row {
display: flex;
flex-direction: row;
border-bottom: 1px solid #f0f0f0;
margin-bottom: 24px;
}
.tab-item {
padding: 12px 20px;
font-size: 14px;
color: #333;
cursor: pointer;
position: relative;
}
.tab-item.active {
color: #1890ff;
}
.tab-item.active::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
right: 0;
height: 2px;
background-color: #1890ff;
}
.table-container {
width: 100%;
border: 1px solid #f0f0f0;
}
.table-header {
display: flex;
flex-direction: row;
background-color: #fafafa;
border-bottom: 1px solid #f0f0f0;
}
.table-row {
display: flex;
flex-direction: row;
border-bottom: 1px solid #f0f0f0;
}
.col {
padding: 12px 16px;
display: flex;
align-items: center;
font-size: 14px;
color: #333;
}
.col-title { flex: 2; }
.col-desc { flex: 3; }
.col-cycle { flex: 2; }
.col-status { width: 100px; justify-content: center; }
.col-ops { width: 80px; justify-content: center; }
.op-link {
color: #1890ff;
cursor: pointer;
}
.pagination {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
.page-info {
font-size: 14px;
color: #999;
}
.mt-16 { margin-top: 16px; }
</style>

View File

@@ -0,0 +1,249 @@
<template>
<view class="admin-page">
<view class="admin-sections">
<!-- 顶部通知 -->
<view class="admin-card alert-card">
<view class="alert-content">
<text class="alert-title">启动定时任务两种方式:</text>
<text class="alert-desc">1、使用命令行启动php think timer start --d; 如果更改了执行周期、编辑是否开启、删除定时任务需要重新启动下定时任务确保生效;</text>
<text class="alert-desc">2、使用接口触发定时任务建议每分钟调用一次接口地址 https://v5.crmeb.net/api/crontab/run</text>
</view>
</view>
<!-- 操作卡片 -->
<view class="admin-card content-card">
<view class="tabs-row">
<view class="tab-item active"><text>系统任务</text></view>
<view class="tab-item"><text>自定义任务</text></view>
</view>
<!-- 表格 -->
<view class="table-container list-table mt-16">
<view class="table-header">
<view class="col col-title"><text>标题</text></view>
<view class="col col-desc"><text>任务说明</text></view>
<view class="col col-cycle"><text>执行周期</text></view>
<view class="col col-status"><text>是否开启</text></view>
<view class="col col-ops"><text>操作</text></view>
</view>
<view class="table-body">
<view v-for="item in pagedList" :key="item.id" class="table-row">
<view class="col col-title"><text>{{ item.title }}</text></view>
<view class="col col-desc"><text>{{ item.desc }}</text></view>
<view class="col col-cycle"><text>{{ item.cycle }}</text></view>
<view class="col col-status">
<switch :checked="item.status" color="#1890ff" scale="0.8" />
</view>
<view class="col col-ops">
<text class="op-link" @click="onEdit(item)">编辑</text>
</view>
</view>
</view>
</view>
<CommonPagination
v-if="total > 0"
:total="total"
:loading="false"
:currentPage="currentPage"
:pageSize="pageSize"
:pageSizeOptionLabels="pageSizeOptionLabels"
:pageSizeIndex="pageSizeIndex"
:visiblePages="visiblePages"
:totalPage="totalPage"
:jumpPageInput="jumpPageInput"
@page-size-change="handlePageSizeChange"
@page-change="handlePageChange"
@update:jumpPageInput="(val: string) => { jumpPageInput.value = val }"
@jump-page="handleJumpPage"
/>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, computed } from 'vue'
import CommonPagination from '@/components/CommonPagination/CommonPagination.uvue'
const dataList = ref([
{ id: 1, title: '自动开具/冲红电子发票', desc: '每隔10分钟执行自动开具/冲红电子发票', cycle: '每隔10分钟执行一次', status: true },
{ id: 2, title: '清除昨日海报', desc: '每天0时30分0秒执行一次清除昨日海报', cycle: '每天0时30分0秒执行一次', status: true },
{ id: 3, title: '订单商品自动好评', desc: '每隔5分钟执行订单到期商品好评', cycle: '每隔5分钟执行一次', status: true },
{ id: 4, title: '预售商品到期自动下架', desc: '每隔5分钟执行预售商品到期下架', cycle: '每隔5分钟执行一次', status: true },
{ id: 5, title: '订单自动收货', desc: '每隔5分钟执行订单到期自动收货', cycle: '每隔5分钟执行一次', status: true },
{ id: 6, title: '自动更新直播间状态', desc: '每隔3分钟执行更新直播间状态', cycle: '每隔3分钟执行一次', status: true },
{ id: 7, title: '自动更新直播商品状态', desc: '每隔3分钟执行更新直播商品状态', cycle: '每隔3分钟执行一次', status: true },
{ id: 8, title: '到期自动解绑上级', desc: '每隔1分钟执行到期的绑定关系的解除', cycle: '每隔1分钟执行一次', status: true },
{ id: 9, title: '拼团到期订单处理', desc: '每隔1分钟拼团到期后的操作', cycle: '每隔1分钟执行一次', status: true },
{ id: 10, title: '未支付自动取消订单', desc: '每隆30秒执行自动取消到期未支付的订单', cycle: '每隆30秒执行一次', status: true },
{ id: 11, title: '积分过期处理', desc: '每天凅晑59分0秒执行清除已过期积分', cycle: '每天凅晑59分执行一次', status: true },
{ id: 12, title: '余额到期处理', desc: '每天中午1时执行一次余额到期检查', cycle: '每天中午1时执行一次', status: false },
{ id: 13, title: '优惠券过期处理', desc: '每隔1小时检查并更新过期优惠券状态', cycle: '每隔1小时执行一次', status: true },
{ id: 14, title: '会员等级更新', desc: '每天凅昹3时执行会员等级自动更新', cycle: '每天凅昹3时执行一次', status: true },
{ id: 15, title: '统计数据更新', desc: '每天凅昹4时执行一次经营数据汇总', cycle: '每天凅昹4时执行一次', status: true },
{ id: 16, title: '清理缓存数据', desc: '每天凅晑30分执行一次过期缓存清除', cycle: '每天凅晑30分执行一次', status: true },
{ id: 17, title: '商品库存预警', desc: '每隆15分钟检查库存不足预警商品', cycle: '每隆15分钟执行一次', status: false },
{ id: 18, title: '短信队列发送', desc: '每隔1分钟处理短信队列', cycle: '每隔1分钟执行一次', status: true },
{ id: 19, title: '邮件队列发送', desc: '每隔2分钟处理邮件发送队列', cycle: '每隔2分钟执行一次', status: true },
{ id: 20, title: '每日对账任务', desc: '每天凅晑22时执行一次财务对账', cycle: '每天凅晑22时执行一次', status: true }
])
// ========== MOCK DATA END ==========
// ========== PAGINATION STATE ==========
const currentPage = ref(1)
const pageSize = ref(15)
const jumpPageInput = ref('')
const pageSizeOptions = [10, 15, 20, 30, 50]
const pageSizeOptionLabels = computed(() => pageSizeOptions.map((n: number) => `${n}条/页`))
const pageSizeIndex = computed(() => { const idx = pageSizeOptions.indexOf(pageSize.value); return idx >= 0 ? idx : 0 })
const total = computed(() => dataList.value.length)
const totalPage = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)))
const pagedList = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return dataList.value.slice(start, start + pageSize.value)
})
const visiblePages = computed((): number[] => {
const t = totalPage.value; const cur = currentPage.value
if (t <= 7) return Array.from({ length: t }, (_: any, i: number) => i + 1)
if (cur <= 4) return [1, 2, 3, 4, 5, -1, t]
if (cur >= t - 3) return [1, -1, t - 4, t - 3, t - 2, t - 1, t]
return [1, -1, cur - 1, cur, cur + 1, -1, t]
})
const handlePageChange = (p: number) => { currentPage.value = p }
const handlePageSizeChange = (e: any) => {
const idx = Number(e.detail.value)
pageSize.value = pageSizeOptions[idx] ?? pageSizeOptions[0]
currentPage.value = 1
}
const handleJumpPage = () => {
const p = parseInt(jumpPageInput.value)
if (!isNaN(p) && p >= 1 && p <= totalPage.value) currentPage.value = p
}
// ========== END PAGINATION STATE ==========
function onEdit(item: any) {
uni.showToast({ title: '编辑: ' + item.title, icon: 'none' })
}
</script>
<style scoped lang="scss">
.admin-page {
/* 使用 Layout 的背景和内边距 */
padding: 0;
background-color: transparent;
min-height: auto;
}
.admin-card {
background-color: #fff;
border-radius: 4px;
padding: 20px;
margin-bottom: 20px;
}
.alert-card {
background-color: #fffbe6;
border: 1px solid #ffe58f;
}
.alert-content {
padding: 8px;
}
.alert-title {
font-size: 14px;
color: #faad14;
margin-bottom: 8px;
font-weight: bold;
}
.alert-desc {
font-size: 13px;
color: #faad14;
line-height: 20px;
margin-bottom: 4px;
}
.tabs-row {
display: flex;
flex-direction: row;
border-bottom: 1px solid #f0f0f0;
margin-bottom: 24px;
}
.tab-item {
padding: 12px 20px;
font-size: 14px;
color: #333;
cursor: pointer;
position: relative;
}
.tab-item.active {
color: #1890ff;
}
.tab-item.active::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
right: 0;
height: 2px;
background-color: #1890ff;
}
.table-container {
width: 100%;
border: 1px solid #f0f0f0;
}
.table-header {
display: flex;
flex-direction: row;
background-color: #fafafa;
border-bottom: 1px solid #f0f0f0;
}
.table-row {
display: flex;
flex-direction: row;
border-bottom: 1px solid #f0f0f0;
}
.col {
padding: 12px 16px;
display: flex;
align-items: center;
font-size: 14px;
color: #333;
}
.col-title { flex: 2; }
.col-desc { flex: 3; }
.col-cycle { flex: 2; }
.col-status { width: 100px; justify-content: center; }
.col-ops { width: 80px; justify-content: center; }
.op-link {
color: #1890ff;
cursor: pointer;
}
.pagination {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
.page-info {
font-size: 14px;
color: #999;
}
.mt-16 { margin-top: 16px; }
</style>

View File

@@ -1,23 +1,23 @@
<template>
<view class="admin-page">
<view class="admin-sections">
<!-- 提示语 -->
<view class="admin-card alert-card">
<text class="alert-title">自定义事件说明:</text>
<text class="alert-desc">1、新增的事件会在对应的事件类型相关的流程中触发选择用户登录则在用户登录时执行代码。</text>
<text class="alert-desc">2、可以使用对应事件类型中的参数$data['nickname'], $data['phone']等。</text>
<text class="alert-desc">3、调用类的时请写入完整路径\think\facade\Db、\app\services\other\CacheServices::class等。</text>
</view>
<!-- 内容区 -->
<view class="admin-card content-card">
<!-- 操作按钮行 -->
<view class="action-bar">
<button class="btn primary small" @click="onAdd">新增系统事件</button>
<template>
<view class="admin-page">
<view class="admin-sections">
<!-- 提示语 -->
<view class="admin-card alert-card">
<text class="alert-title">自定义事件说明:</text>
<text class="alert-desc">1、新增的事件会在对应的事件类型相关的流程中触发选择用户登录则在用户登录时执行代码。</text>
<text class="alert-desc">2、可以使用对应事件类型中的参数$data['nickname'], $data['phone']等。</text>
<text class="alert-desc">3、调用类的时请写入完整路径\think\facade\Db、\app\services\other\CacheServices::class等。</text>
</view>
<!-- 表格(暂无数据) -->
<view class="table-container list-table">
<!-- 内容区 -->
<view class="admin-card content-card">
<!-- 操作按钮行 -->
<view class="action-bar">
<button class="btn primary small" @click="onAdd">新增系统事件</button>
</view>
<!-- 表格(暂无数据) -->
<view class="table-container list-table">
<view class="table-header">
<view class="col col-id"><text>编号</text></view>
<view class="col col-name"><text>事件名</text></view>
@@ -39,6 +39,7 @@
</template>
<script setup lang="uts">
function onAdd() {
uni.showToast({ title: '新增系统事件', icon: 'none' })
}
@@ -46,9 +47,10 @@ function onAdd() {
<style scoped lang="scss">
.admin-page {
padding: 20px;
background-color: #f5f7f9;
min-height: 100vh;
/* 使用 Layout 的背景和内边距 */
padding: 0;
background-color: transparent;
min-height: auto;
}
.admin-card {

View File

@@ -1,23 +1,23 @@
<template>
<template>
<view class="admin-page">
<view class="admin-sections">
<view class="admin-card content-card">
<view class="form-container">
<view class="form-item">
<view class="form-label">模块配置:</view>
<view class="form-content">
<label class="checkbox-item">
<checkbox color="#1890ff" checked />
<text class="checkbox-label">秒杀</text>
</label>
<label class="checkbox-item">
<checkbox color="#1890ff" checked />
<text class="checkbox-label">砍价</text>
</label>
<label class="checkbox-item">
<checkbox color="#1890ff" checked />
<text class="checkbox-label">拼团</text>
</label>
<view class="admin-sections">
<view class="admin-card content-card">
<view class="form-container">
<view class="form-item">
<view class="form-label">模块配置:</view>
<view class="form-content">
<label class="checkbox-item">
<checkbox color="#1890ff" checked />
<text class="checkbox-label">秒杀</text>
</label>
<label class="checkbox-item">
<checkbox color="#1890ff" checked />
<text class="checkbox-label">砍价</text>
</label>
<label class="checkbox-item">
<checkbox color="#1890ff" checked />
<text class="checkbox-label">拼团</text>
</label>
</view>
</view>
<view class="form-tip">
@@ -40,9 +40,10 @@ function onSubmit() {
<style scoped lang="scss">
.admin-page {
padding: 20px;
background-color: #f5f7f9;
min-height: 100vh;
/* 使用 Layout 的背景和内边距 */
padding: 0;
background-color: transparent;
min-height: auto;
}
.admin-card {

View File

@@ -1,23 +1,23 @@
<template>
<template>
<view class="admin-page">
<view class="admin-sections">
<!-- 搜索栏 -->
<view class="admin-card filter-card">
<view class="filter-row">
<view class="filter-item">
<text class="label">规则状态:</text>
<view class="filter-select">
<text class="select-placeholder">请选择</text>
<text class="arrow">▼</text>
<view class="admin-sections">
<!-- 搜索栏 -->
<view class="admin-card filter-card">
<view class="filter-row">
<view class="filter-item">
<text class="label">规则状态:</text>
<view class="filter-select">
<text class="select-placeholder">请选择</text>
<text class="arrow">▼</text>
</view>
</view>
<view class="filter-item">
<text class="label">按钮名称:</text>
<input class="filter-input" placeholder="请输入按钮名称" />
</view>
<button class="btn primary" @click="onSearch">查询</button>
</view>
<view class="filter-item">
<text class="label">按钮名称:</text>
<input class="filter-input" placeholder="请输入按钮名称" />
</view>
<button class="btn primary" @click="onSearch">查询</button>
</view>
</view>
<!-- 内容区 -->
<view class="admin-card content-card">
@@ -120,9 +120,10 @@ function onDelete(item: any) {
<style scoped lang="scss">
.admin-page {
padding: 20px;
background-color: #f5f7f9;
min-height: 100vh;
/* 使用 Layout 的背景和内边距 */
padding: 0;
background-color: transparent;
min-height: auto;
}
.admin-card {
@@ -236,7 +237,7 @@ function onDelete(item: any) {
.col-route { flex: 3; }
.col-status { width: 100px; justify-content: center; }
.col-memo { flex: 1; }
.col-ops { flex: 3; justify-content: flex-end; }
.col-ops { flex: 3; justify-content: flex-end; display: flex; flex-direction: row; }
.expand-icon {
margin-right: 8px;

View File

@@ -1,21 +0,0 @@
<template>
<view class="admin-page">
<view class="page">
<view class="header">
<text class="title">代码生成</text>
</view>
<view class="content">
<text class="tip">TODO: 代码生成</text>
</view>
</view>
</view>
</template>
<script setup lang="uts">
</script>
<style scoped>
.admin-page {
padding: 20px;
}
</style>

View File

@@ -1,4 +1,4 @@
<template>
<template>
<view class="admin-page data-dictionary">
<view class="admin-sections">
<view class="admin-card">
@@ -23,7 +23,7 @@
<view class="table-cell action-cell" style="flex: 2;">操作</view>
</view>
<view class="table-body">
<view class="table-row" v-for="(item, index) in dictionaryList" :key="index">
<view class="table-row" v-for="(item, index) in pagedList" :key="index">
<view class="table-cell" style="flex: 0 0 60px;">{{ item.id }}</view>
<view class="table-cell" style="flex: 2;">{{ item.name }}</view>
<view class="table-cell" style="flex: 2;">{{ item.tag }}</view>
@@ -38,10 +38,27 @@
</view>
</view>
</view>
<CommonPagination
v-if="total > 0"
:total="total"
:loading="false"
:currentPage="currentPage"
:pageSize="pageSize"
:pageSizeOptionLabels="pageSizeOptionLabels"
:pageSizeIndex="pageSizeIndex"
:visiblePages="visiblePages"
:totalPage="totalPage"
:jumpPageInput="jumpPageInput"
@page-size-change="handlePageSizeChange"
@page-change="handlePageChange"
@update:jumpPageInput="(val: string) => { jumpPageInput.value = val }"
@jump-page="handleJumpPage"
/>
</view>
</view></template>
<script setup lang="uts">
import { ref } from 'vue'
import { ref, computed } from 'vue'
import CommonPagination from '@/components/CommonPagination/CommonPagination.uvue'
const searchQuery = ref('')
const dictionaryList = ref([
@@ -54,8 +71,50 @@ const dictionaryList = ref([
{ id: 16, name: '用户管理模块', tag: '123', type: '多级', add_time: '2024-10-22 09:30:46' },
{ id: 18, name: '类型', tag: '1', type: '多级', add_time: '2024-12-11 13:48:01' },
{ id: 19, name: '店铺', tag: 'shop', type: '多级', add_time: '2024-12-11 22:23:07' },
{ id: 20, name: 'ce', tag: 'zzz', type: '一级', add_time: '2024-12-14 12:19:17' }
{ id: 20, name: 'ce', tag: 'zzz', type: '一级', add_time: '2024-12-14 12:19:17' },
{ id: 21, name: '语言', tag: 'language', type: '多级', add_time: '2025-01-05 10:00:00' },
{ id: 22, name: '地区', tag: 'region', type: '多级', add_time: '2025-01-10 11:30:00' },
{ id: 23, name: '货币类型', tag: 'currency', type: '一级', add_time: '2025-01-15 09:00:00' },
{ id: 24, name: '支付方式', tag: 'payment_type', type: '一级', add_time: '2025-01-20 14:00:00' },
{ id: 25, name: '订单状态', tag: 'order_status', type: '一级', add_time: '2025-01-25 10:30:00' },
{ id: 26, name: '物流公司', tag: 'express_company', type: '一级', add_time: '2025-02-01 08:00:00' },
{ id: 27, name: '售后类型', tag: 'after_sale_type', type: '多级', add_time: '2025-02-05 15:00:00' },
{ id: 28, name: '套餐类型', tag: 'package_type', type: '多级', add_time: '2025-02-10 09:30:00' },
{ id: 29, name: '签到规则', tag: 'sign_rule', type: '一级', add_time: '2025-02-15 11:00:00' }
])
// ========== MOCK DATA END ==========
// ========== PAGINATION STATE ==========
const currentPage = ref(1)
const pageSize = ref(15)
const jumpPageInput = ref('')
const pageSizeOptions = [10, 15, 20, 30, 50]
const pageSizeOptionLabels = computed(() => pageSizeOptions.map((n: number) => `${n}条/页`))
const pageSizeIndex = computed(() => { const idx = pageSizeOptions.indexOf(pageSize.value); return idx >= 0 ? idx : 0 })
const total = computed(() => dictionaryList.value.length)
const totalPage = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)))
const pagedList = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return dictionaryList.value.slice(start, start + pageSize.value)
})
const visiblePages = computed((): number[] => {
const t = totalPage.value; const cur = currentPage.value
if (t <= 7) return Array.from({ length: t }, (_: any, i: number) => i + 1)
if (cur <= 4) return [1, 2, 3, 4, 5, -1, t]
if (cur >= t - 3) return [1, -1, t - 4, t - 3, t - 2, t - 1, t]
return [1, -1, cur - 1, cur, cur + 1, -1, t]
})
const handlePageChange = (p: number) => { currentPage.value = p }
const handlePageSizeChange = (e: any) => {
const idx = Number(e.detail.value)
pageSize.value = pageSizeOptions[idx] ?? pageSizeOptions[0]
currentPage.value = 1
}
const handleJumpPage = () => {
const p = parseInt(jumpPageInput.value)
if (!isNaN(p) && p >= 1 && p <= totalPage.value) currentPage.value = p
}
// ========== END PAGINATION STATE ==========
function handleSearch() {
uni.showToast({ title: '搜索: ' + searchQuery.value, icon: 'none' })
@@ -88,8 +147,7 @@ function handleDelete(item: any) {
<style scoped>
.admin-page {
padding: 20px;
background-color: #f5f7f9;
/* 使用 Layout 的背景和内边距 */
min-height: 100vh;
}
@@ -181,6 +239,8 @@ function handleDelete(item: any) {
font-size: 14px;
color: #666;
padding: 0 10px;
display: flex;
flex-direction: row;
}
.action-link {

View File

@@ -1,146 +0,0 @@
<template>
<view class="admin-page">
<view class="admin-sections">
<view class="admin-card">
<view class="page-header">
<text class="page-title">数据库管理</text>
<view class="header-btns">
<button class="btn btn-primary btn-sm" @click="handleExport">备份数据库</button>
</view>
</view>
<!-- 数据表格 -->
<view class="admin-table">
<view class="thead">
<view class="th col-name">表名</view>
<view class="th col-engine">引擎</view>
<view class="th col-rows">行数</view>
<view class="th col-size">数据大小</view>
<view class="th col-index">索引大小</view>
<view class="th col-comment">备注</view>
<view class="th col-op">操作</view>
</view>
<view class="tbody">
<view v-for="(item, index) in tableData" :key="index" class="tr">
<view class="td col-name">{{ item.name }}</view>
<view class="td col-engine">{{ item.engine }}</view>
<view class="td col-rows">{{ item.rows }}</view>
<view class="td col-size">{{ item.dataSize }}</view>
<view class="td col-index">{{ item.indexSize }}</view>
<view class="td col-comment">{{ item.comment }}</view>
<view class="td col-op">
<text class="op-link" @click="handleOptimize(item.name)">优化</text>
<text class="op-link" @click="handleRepair(item.name)">修复</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
const tableData = ref([
{ name: 'eb_system_admin', engine: 'InnoDB', rows: 5, dataSize: '16.00 KB', indexSize: '16.00 KB', comment: '后台管理员表' },
{ name: 'eb_user', engine: 'InnoDB', rows: 1250, dataSize: '320.00 KB', indexSize: '156.00 KB', comment: '用户表' },
{ name: 'eb_store_product', engine: 'InnoDB', rows: 86, dataSize: '1.20 MB', indexSize: '64.00 KB', comment: '商品表' },
{ name: 'eb_store_order', engine: 'InnoDB', rows: 4521, dataSize: '2.50 MB', indexSize: '512.00 KB', comment: '订单表' },
{ name: 'eb_system_config', engine: 'InnoDB', rows: 156, dataSize: '48.00 KB', indexSize: '16.00 KB', comment: '系统配置表' }
])
function handleExport() {
uni.showLoading({ title: '正在备份...' })
setTimeout(() => {
uni.hideLoading()
uni.showToast({ title: '备份成功' })
}, 1500)
}
function handleOptimize(name: string) {
uni.showToast({ title: '优化表: ' + name, icon: 'none' })
}
function handleRepair(name: string) {
uni.showToast({ title: '修复表: ' + name, icon: 'none' })
}
</script>
<style scoped>
.admin-page {
padding: 20px;
}
.admin-card {
background-color: #fff;
padding: 20px;
border-radius: 4px;
}
.page-header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.page-title {
font-size: 16px;
font-weight: bold;
}
.btn-sm {
height: 32px;
line-height: 32px;
font-size: 14px;
padding: 0 15px;
}
.btn-primary {
background-color: #1890ff;
color: #fff;
}
/* 表格样式 */
.admin-table {
border: 1px solid #e8eaec;
}
.thead {
display: flex;
flex-direction: row;
background-color: #f8f8f9;
border-bottom: 1px solid #e8eaec;
}
.tbody {
display: flex;
flex-direction: column;
}
.tr {
display: flex;
flex-direction: row;
border-bottom: 1px solid #e8eaec;
}
.tr:last-child {
border-bottom: none;
}
.th, .td {
padding: 12px 10px;
font-size: 14px;
color: #515a6e;
display: flex;
align-items: center;
}
.th {
font-weight: bold;
}
.col-name { flex: 2; }
.col-engine { flex: 1; }
.col-rows { flex: 1; }
.col-size { flex: 1; }
.col-index { flex: 1; }
.col-comment { flex: 2; }
.col-op { width: 120px; justify-content: space-around; }
.op-link {
color: #1890ff;
cursor: pointer;
}
</style>

View File

@@ -0,0 +1,213 @@
<template>
<view class="admin-page">
<view class="admin-sections">
<view class="admin-card">
<view class="page-header">
<text class="page-title">数据库管理</text>
<view class="header-btns">
<button class="btn btn-primary btn-sm" @click="handleExport">备份数据库</button>
</view>
</view>
<!-- 数据表格 -->
<view class="admin-table">
<view class="thead">
<view class="th col-name">表名</view>
<view class="th col-engine">引擎</view>
<view class="th col-rows">行数</view>
<view class="th col-size">数据大小</view>
<view class="th col-index">索引大小</view>
<view class="th col-comment">备注</view>
<view class="th col-op">操作</view>
</view>
<view class="tbody">
<view v-for="(item, index) in pagedList" :key="index" class="tr">
<view class="td col-name">{{ item.name }}</view>
<view class="td col-engine">{{ item.engine }}</view>
<view class="td col-rows">{{ item.rows }}</view>
<view class="td col-size">{{ item.dataSize }}</view>
<view class="td col-index">{{ item.indexSize }}</view>
<view class="td col-comment">{{ item.comment }}</view>
<view class="td col-op">
<text class="op-link" @click="handleOptimize(item.name)">优化</text>
<text class="op-link" @click="handleRepair(item.name)">修复</text>
</view>
</view>
</view>
</view>
<CommonPagination
v-if="true"
:total="total"
:loading="false"
:currentPage="currentPage"
:pageSize="pageSize"
:pageSizeOptionLabels="pageSizeOptionLabels"
:pageSizeIndex="pageSizeIndex"
:visiblePages="visiblePages"
:totalPage="totalPage"
:jumpPageInput="jumpPageInput"
@page-size-change="handlePageSizeChange"
@page-change="handlePageChange"
@update:jumpPageInput="(val: string) => { jumpPageInput.value = val }"
@jump-page="handleJumpPage"
/>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, computed } from 'vue'
import CommonPagination from '@/components/CommonPagination/CommonPagination.uvue'
// ========== MOCK DATA START ==========
// TODO: 接真实接口时替换此处 tableData 为 fetchTableList() 调用
const tableData = ref([
{ name: 'eb_system_admin', engine: 'InnoDB', rows: 5, dataSize: '16.00 KB', indexSize: '16.00 KB', comment: '后台管理员表' },
{ name: 'eb_user', engine: 'InnoDB', rows: 1250, dataSize: '320.00 KB', indexSize: '156.00 KB', comment: '用户表' },
{ name: 'eb_store_product', engine: 'InnoDB', rows: 86, dataSize: '1.20 MB', indexSize: '64.00 KB', comment: '商品表' },
{ name: 'eb_store_order', engine: 'InnoDB', rows: 4521, dataSize: '2.50 MB', indexSize: '512.00 KB', comment: '订单表' },
{ name: 'eb_system_config', engine: 'InnoDB', rows: 156, dataSize: '48.00 KB', indexSize: '16.00 KB', comment: '系统配置表' },
{ name: 'eb_store_category', engine: 'InnoDB', rows: 42, dataSize: '32.00 KB', indexSize: '16.00 KB', comment: '商品分类表' },
{ name: 'eb_user_address', engine: 'InnoDB', rows: 3210, dataSize: '256.00 KB', indexSize: '64.00 KB', comment: '用户收货地址表' },
{ name: 'eb_store_cart', engine: 'InnoDB', rows: 8820, dataSize: '1.80 MB', indexSize: '256.00 KB', comment: '购物车表' },
{ name: 'eb_coupon', engine: 'InnoDB', rows: 312, dataSize: '96.00 KB', indexSize: '32.00 KB', comment: '优惠券表' },
{ name: 'eb_coupon_user', engine: 'InnoDB', rows: 12500, dataSize: '3.20 MB', indexSize: '512.00 KB', comment: '用户优惠券表' },
{ name: 'eb_store_order_product', engine: 'InnoDB', rows: 9800, dataSize: '2.10 MB', indexSize: '320.00 KB', comment: '订单商品明细表' },
{ name: 'eb_system_admin_log', engine: 'InnoDB', rows: 25000, dataSize: '8.00 MB', indexSize: '1.20 MB', comment: '管理员操作日志表' },
{ name: 'eb_user_integral', engine: 'InnoDB', rows: 5600, dataSize: '640.00 KB', indexSize: '128.00 KB', comment: '用户积分记录表' },
{ name: 'eb_seckill', engine: 'InnoDB', rows: 18, dataSize: '16.00 KB', indexSize: '16.00 KB', comment: '秒杀活动表' },
{ name: 'eb_bargain', engine: 'InnoDB', rows: 24, dataSize: '16.00 KB', indexSize: '16.00 KB', comment: '研价活动表' },
{ name: 'eb_combination', engine: 'InnoDB', rows: 36, dataSize: '32.00 KB', indexSize: '16.00 KB', comment: '拼团活动表' },
{ name: 'eb_agent', engine: 'InnoDB', rows: 890, dataSize: '128.00 KB', indexSize: '32.00 KB', comment: '代理商表' },
{ name: 'eb_spread_commission', engine: 'InnoDB', rows: 18700, dataSize: '4.50 MB', indexSize: '768.00 KB', comment: '佣金流水表' },
{ name: 'eb_wechat_media', engine: 'InnoDB', rows: 210, dataSize: '2.50 MB', indexSize: '64.00 KB', comment: '微信素材库表' }
])
// ========== MOCK DATA END ==========
// ========== PAGINATION STATE ==========
const currentPage = ref(1)
const pageSize = ref(15)
const jumpPageInput = ref('')
const pageSizeOptions = [10, 15, 20, 30, 50]
const pageSizeOptionLabels = computed(() => pageSizeOptions.map((n: number) => `${n}条/页`))
const pageSizeIndex = computed(() => { const idx = pageSizeOptions.indexOf(pageSize.value); return idx >= 0 ? idx : 0 })
const total = computed(() => tableData.value.length)
const totalPage = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)))
const pagedList = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return tableData.value.slice(start, start + pageSize.value)
})
const visiblePages = computed((): number[] => {
const t = totalPage.value; const cur = currentPage.value
if (t <= 7) return Array.from({ length: t }, (_: any, i: number) => i + 1)
if (cur <= 4) return [1, 2, 3, 4, 5, -1, t]
if (cur >= t - 3) return [1, -1, t - 4, t - 3, t - 2, t - 1, t]
return [1, -1, cur - 1, cur, cur + 1, -1, t]
})
const handlePageChange = (p: number) => { currentPage.value = p }
const handlePageSizeChange = (e: any) => {
const idx = Number(e.detail.value)
pageSize.value = pageSizeOptions[idx] ?? pageSizeOptions[0]
currentPage.value = 1
}
const handleJumpPage = () => {
const p = parseInt(jumpPageInput.value)
if (!isNaN(p) && p >= 1 && p <= totalPage.value) currentPage.value = p
}
// ========== END PAGINATION STATE ==========
function handleExport() {
uni.showLoading({ title: '正在备份...' })
setTimeout(() => {
uni.hideLoading()
uni.showToast({ title: '备份成功' })
}, 1500)
}
function handleOptimize(name: string) {
uni.showToast({ title: '优化表: ' + name, icon: 'none' })
}
function handleRepair(name: string) {
uni.showToast({ title: '修复表: ' + name, icon: 'none' })
}
</script>
<style scoped>
.admin-page {
/* 使用 Layout 的背景和内边距 */
min-height: 100vh;
}
.admin-card {
background-color: #fff;
padding: var(--admin-card-padding);
border-radius: 4px;
}
.page-header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.page-title {
font-size: 16px;
font-weight: bold;
}
.btn-sm {
height: 32px;
line-height: 32px;
font-size: 14px;
padding: 0 15px;
}
.btn-primary {
background-color: #1890ff;
color: #fff;
}
/* 表格样式 */
.admin-table {
border: 1px solid #e8eaec;
}
.thead {
display: flex;
flex-direction: row;
background-color: #f8f8f9;
border-bottom: 1px solid #e8eaec;
}
.tbody {
display: flex;
flex-direction: column;
}
.tr {
display: flex;
flex-direction: row;
border-bottom: 1px solid #e8eaec;
}
.tr:last-child {
border-bottom: none;
}
.th, .td {
padding: 12px 10px;
font-size: 14px;
color: #515a6e;
display: flex;
align-items: center;
}
.th {
font-weight: bold;
}
.col-name { flex: 2; }
.col-engine { flex: 1; }
.col-rows { flex: 1; }
.col-size { flex: 1; }
.col-index { flex: 1; }
.col-comment { flex: 2; }
.col-op { width: 120px; justify-content: space-around; display: flex; flex-direction: row; }
.op-link {
color: #1890ff;
cursor: pointer;
}
</style>

View File

@@ -1,4 +1,4 @@
<template>
<template>
<view class="admin-page file-login-container">
<view class="login-card">
<view class="login-header">
@@ -54,12 +54,12 @@ const handleLogin = () => {
<style scoped>
.admin-page {
/* 使用 Layout 的背景和内边距 */
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: calc(100vh - 120px);
background-color: #f5f7f9;
}
.login-card {

View File

@@ -1,4 +1,4 @@
<template>
<template>
<view class="admin-page api-management">
<!-- 左侧分类树 -->
<view class="sidebar-tree">

View File

@@ -1,57 +0,0 @@
<template>
<view class="admin-maintain-config">
<view class="page-header border-shadow">
<text class="page-title">开发配置 - 配置分类</text>
<view class="btn-primary"><text class="btn-txt">+ 添加配置</text></view>
</view>
<view class="table-container border-shadow">
<view class="table-header">
<text class="th col-id">ID</text>
<text class="th col-name">标题</text>
<text class="th col-key">分类Key</text>
<text class="th col-type">类型</text>
<text class="th col-status">状态</text>
<text class="th col-op">操作</text>
</view>
<view class="table-body">
<view class="table-row" v-for="item in 5" :key="item">
<text class="td col-id">{{ item }}</text>
<text class="td col-name">基础配置 {{ item }}</text>
<text class="td col-key">basic_conf_{{ item }}</text>
<text class="td col-type">系统配置</text>
<view class="td col-status"><text class="status-tag success">显</text></view>
<view class="td col-op">
<text class="btn-link">查看</text>
<text class="btn-link">编辑</text>
</view>
</view>
</view>
</view>
</view>
</template>
<style scoped lang="scss">
.admin-maintain-config { padding: 20px; }
.border-shadow { background-color: #fff; border-radius: 4px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05); }
.page-header { padding: 20px 24px; margin-bottom: 20px; display: flex; flex-direction: row; justify-content: space-between; align-items: center; }
.page-title { font-size: 16px; font-weight: 600; color: #303133; }
.btn-primary { padding: 8px 16px; background-color: #1890ff; border-radius: 4px; }
.btn-txt { color: #fff; font-size: 14px; }
.table-header { display: flex; flex-direction: row; background-color: #f8f9fb; border-bottom: 1px solid #ebeef5; }
.th { padding: 12px 10px; font-size: 14px; color: #909399; text-align: center; font-weight: 500; }
.table-row { display: flex; flex-direction: row; border-bottom: 1px solid #ebeef5; align-items: center; }
.td { padding: 15px 10px; font-size: 13px; color: #606266; text-align: center; }
.col-id { width: 80px; }
.col-name { flex: 1; text-align: left; justify-content: flex-start; }
.col-key { width: 150px; }
.col-type { width: 120px; }
.col-status { width: 80px; }
.col-op { width: 150px; }
.status-tag { padding: 2px 6px; border-radius: 3px; font-size: 11px; }
.success { background-color: #f0f9eb; color: #67c23a; border: 1px solid #e1f3d8; }
.btn-link { color: #1890ff; margin: 0 8px; font-size: 13px; cursor: pointer; }
</style>

View File

@@ -1,13 +0,0 @@
<template>
<AdminLayout currentPage="external-account">
<view class="page">
<view class="header">
<text class="title">账号管理</text>
</view>
<view class="content">
<text class="tip">TODO: 账号管理</text>
</view>
</view>
</AdminLayout>
</template>

View File

@@ -1,12 +0,0 @@
<template>
<AdminLayout currentPage="i18n-language-detail">
<view class="page">
<view class="header">
<text class="title">语言详情</text>
</view>
<view class="content">
<text class="tip">TODO: 语言详情</text>
</view>
</view>
</AdminLayout>
</template>

View File

@@ -1,12 +0,0 @@
<template>
<AdminLayout currentPage="i18n-language-list">
<view class="page">
<view class="header">
<text class="title">语言列表</text>
</view>
<view class="content">
<text class="tip">TODO: 语言列表</text>
</view>
</view>
</AdminLayout>
</template>

View File

@@ -1,12 +0,0 @@
<template>
<AdminLayout currentPage="i18n-region-list">
<view class="page">
<view class="header">
<text class="title">地区列表</text>
</view>
<view class="content">
<text class="tip">TODO: 地区列表</text>
</view>
</view>
</AdminLayout>
</template>

View File

@@ -1,12 +0,0 @@
<template>
<AdminLayout currentPage="i18n-translate-config">
<view class="page">
<view class="header">
<text class="title">翻译配置</text>
</view>
<view class="content">
<text class="tip">TODO: 翻译配置</text>
</view>
</view>
</AdminLayout>
</template>

View File

@@ -57,8 +57,7 @@ function onSubmit() {
<style scoped lang="scss">
.admin-page {
padding: 24px;
background-color: #f5f7f9;
/* 使用 Layout 的背景和内边距 */
min-height: 100vh;
}

View File

@@ -56,7 +56,7 @@
</view>
<view class="table-body">
<view v-for="item in dataList" :key="item.id" class="table-row">
<view v-for="item in pagedList" :key="item.id" class="table-row">
<view class="col col-id"><text>{{ item.id }}</text></view>
<view class="col col-origin"><text>{{ item.origin }}</text></view>
<view class="col col-translation"><text>{{ item.translation }}</text></view>
@@ -70,14 +70,33 @@
</view>
</view>
</view>
<CommonPagination
v-if="true"
:total="total"
:loading="false"
:currentPage="currentPage"
:pageSize="pageSize"
:pageSizeOptionLabels="pageSizeOptionLabels"
:pageSizeIndex="pageSizeIndex"
:visiblePages="visiblePages"
:totalPage="totalPage"
:jumpPageInput="jumpPageInput"
@page-size-change="handlePageSizeChange"
@page-change="handlePageChange"
@update:jumpPageInput="(val: string) => { jumpPageInput.value = val }"
@jump-page="handleJumpPage"
/>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import { ref, computed } from 'vue'
import CommonPagination from '@/components/CommonPagination/CommonPagination.uvue'
// ========== MOCK DATA START ==========
// TODO: 接真实接口时替换此处 dataList 为 fetchTranslationList() 调用
const dataList = ref([
{ id: 55493, origin: '秒杀活动', translation: '秒杀活动', code: '秒杀活动', type: '中文(zh-CN)' },
{ id: 55483, origin: '哥哥我', translation: '', code: '哥哥我', type: '中文(zh-CN)' },
@@ -88,8 +107,50 @@ const dataList = ref([
{ id: 25181, origin: '支付剩余时间', translation: '支付剩余时间', code: '支付剩余时间', type: '中文(zh-CN)' },
{ id: 24711, origin: '全部已读', translation: '全部已读', code: '全部已读', type: '中文(zh-CN)' },
{ id: 24701, origin: '获得拼团团长佣金', translation: '获得拼团团长佣金', code: '获得拼团团长佣金', type: '中文(zh-CN)' },
{ id: 24691, origin: '获得事业部推广订单佣金', translation: '获得事业部推广订单佣金', code: '获得事业部推广订单佣金', type: '中文(zh-CN)' }
{ id: 24691, origin: '获得事业部推广订单佣金', translation: '获得事业部推广订单佣金', code: '获得事业部推广订单佣金', type: '中文(zh-CN)' },
{ id: 24681, origin: '返回默认地址', translation: '返回默认地址', code: '返回默认地址', type: '中文(zh-CN)' },
{ id: 24671, origin: '我的购物车', translation: '我的购物车', code: '我的购物车', type: '中文(zh-CN)' },
{ id: 24661, origin: '监控山东', translation: '监控山东', code: '监控山东', type: '中文(zh-CN)' },
{ id: 24651, origin: '商品评价', translation: '商品评价', code: '商品评价', type: '中文(zh-CN)' },
{ id: 24641, origin: '手机号已绑定', translation: '手机号已绑定', code: '手机号已绑定', type: '中文(zh-CN)' },
{ id: 24631, origin: '订单列表', translation: '订单列表', code: '订单列表', type: '中文(zh-CN)' },
{ id: 24621, origin: '购买商品', translation: '购买商品', code: '购买商品', type: '中文(zh-CN)' },
{ id: 24611, origin: '会员中心', translation: '会员中心', code: '会员中心', type: '中文(zh-CN)' },
{ id: 24601, origin: '就此评价', translation: '就此评价', code: '就此评价', type: '中文(zh-CN)' }
])
// ========== MOCK DATA END ==========
// ========== PAGINATION STATE ==========
const currentPage = ref(1)
const pageSize = ref(15)
const jumpPageInput = ref('')
const pageSizeOptions = [10, 15, 20, 30, 50]
const pageSizeOptionLabels = computed(() => pageSizeOptions.map((n: number) => `${n}条/页`))
const pageSizeIndex = computed(() => { const idx = pageSizeOptions.indexOf(pageSize.value); return idx >= 0 ? idx : 0 })
const total = computed(() => dataList.value.length)
const totalPage = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)))
const pagedList = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return dataList.value.slice(start, start + pageSize.value)
})
const visiblePages = computed((): number[] => {
const t = totalPage.value; const cur = currentPage.value
if (t <= 7) return Array.from({ length: t }, (_: any, i: number) => i + 1)
if (cur <= 4) return [1, 2, 3, 4, 5, -1, t]
if (cur >= t - 3) return [1, -1, t - 4, t - 3, t - 2, t - 1, t]
return [1, -1, cur - 1, cur, cur + 1, -1, t]
})
const handlePageChange = (p: number) => { currentPage.value = p }
const handlePageSizeChange = (e: any) => {
const idx = Number(e.detail.value)
pageSize.value = pageSizeOptions[idx] ?? pageSizeOptions[0]
currentPage.value = 1
}
const handleJumpPage = () => {
const p = parseInt(jumpPageInput.value)
if (!isNaN(p) && p >= 1 && p <= totalPage.value) currentPage.value = p
}
// ========== END PAGINATION STATE ==========
function onSearch() {
uni.showToast({ title: '搜索中...', icon: 'none' })
@@ -118,8 +179,7 @@ function deleteWord(item: any) {
<style scoped lang="scss">
.admin-page {
padding: 24px;
background-color: #f5f7f9;
/* 使用 Layout 的背景和内边距 */
min-height: 100vh;
}
@@ -256,7 +316,7 @@ function deleteWord(item: any) {
.col-translation { flex: 2; }
.col-code { flex: 2; }
.col-type { width: 150px; }
.col-action { width: 150px; justify-content: flex-end; }
.col-action { width: 150px; justify-content: flex-end;display: flex; flex-direction: row; }
.action-btn {
color: #1890ff;

View File

@@ -18,7 +18,7 @@
</view>
<view class="table-body">
<view v-for="item in dataList" :key="item.id" class="table-row">
<view v-for="item in pagedList" :key="item.id" class="table-row">
<view class="col col-id"><text>{{ item.id }}</text></view>
<view class="col col-name">
<text>{{ item.name }}</text>
@@ -41,17 +41,33 @@
</view>
<!-- 分页 -->
<view class="pagination">
<text class="page-info">共 10 条 15条/页</text>
</view>
<CommonPagination
v-if="true"
:total="total"
:loading="false"
:currentPage="currentPage"
:pageSize="pageSize"
:pageSizeOptionLabels="pageSizeOptionLabels"
:pageSizeIndex="pageSizeIndex"
:visiblePages="visiblePages"
:totalPage="totalPage"
:jumpPageInput="jumpPageInput"
@page-size-change="handlePageSizeChange"
@page-change="handlePageChange"
@update:jumpPageInput="(val: string) => { jumpPageInput.value = val }"
@jump-page="handleJumpPage"
/>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import { ref, computed } from 'vue'
import CommonPagination from '@/components/CommonPagination/CommonPagination.uvue'
// ========== MOCK DATA START ==========
// TODO: 接真实接口时替换此处 dataList 为 fetchLanguageList() 调用
const dataList = ref([
{ id: 1, name: '中文', code: 'zh-CN', status: true, isDefault: true },
{ id: 2, name: 'English', code: 'en-US', status: true, isDefault: false },
@@ -62,8 +78,51 @@ const dataList = ref([
{ id: 7, name: '한국어', code: 'ko-KR', status: true, isDefault: false },
{ id: 8, name: 'Монгол', code: 'mn-MN', status: true, isDefault: false },
{ id: 9, name: 'ภาษาไทย', code: 'th-TH', status: true, isDefault: false },
{ id: 10, name: 'ViệtName', code: 'vi-VN', status: true, isDefault: false }
{ id: 10, name: 'ViệtName', code: 'vi-VN', status: true, isDefault: false },
{ id: 11, name: '한국어', code: 'ko-KR', status: true, isDefault: false },
{ id: 12, name: 'Deutsch', code: 'de-DE', status: false, isDefault: false },
{ id: 13, name: 'Español', code: 'es-ES', status: false, isDefault: false },
{ id: 14, name: 'Português', code: 'pt-PT', status: false, isDefault: false },
{ id: 15, name: 'русский', code: 'ru-RU', status: false, isDefault: false },
{ id: 16, name: 'Italiano', code: 'it-IT', status: true, isDefault: false },
{ id: 17, name: 'Bahasa Indonesia', code: 'id-ID', status: false, isDefault: false },
{ id: 18, name: 'Bahasa Melayu', code: 'ms-MY', status: false, isDefault: false },
{ id: 19, name: 'Türkçe', code: 'tr-TR', status: false, isDefault: false },
{ id: 20, name: 'Nederlands', code: 'nl-NL', status: false, isDefault: false }
])
// ========== MOCK DATA END ==========
// ========== PAGINATION STATE ==========
const currentPage = ref(1)
const pageSize = ref(15)
const jumpPageInput = ref('')
const pageSizeOptions = [10, 15, 20, 30, 50]
const pageSizeOptionLabels = computed(() => pageSizeOptions.map((n: number) => `${n}条/页`))
const pageSizeIndex = computed(() => { const idx = pageSizeOptions.indexOf(pageSize.value); return idx >= 0 ? idx : 0 })
const total = computed(() => dataList.value.length)
const totalPage = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)))
const pagedList = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return dataList.value.slice(start, start + pageSize.value)
})
const visiblePages = computed((): number[] => {
const t = totalPage.value; const cur = currentPage.value
if (t <= 7) return Array.from({ length: t }, (_: any, i: number) => i + 1)
if (cur <= 4) return [1, 2, 3, 4, 5, -1, t]
if (cur >= t - 3) return [1, -1, t - 4, t - 3, t - 2, t - 1, t]
return [1, -1, cur - 1, cur, cur + 1, -1, t]
})
const handlePageChange = (p: number) => { currentPage.value = p }
const handlePageSizeChange = (e: any) => {
const idx = Number(e.detail.value)
pageSize.value = pageSizeOptions[idx] ?? pageSizeOptions[0]
currentPage.value = 1
}
const handleJumpPage = () => {
const p = parseInt(jumpPageInput.value)
if (!isNaN(p) && p >= 1 && p <= totalPage.value) currentPage.value = p
}
// ========== END PAGINATION STATE ==========
function addLanguage() {
uni.showToast({ title: '添加语言', icon: 'none' })
@@ -92,8 +151,7 @@ function deleteItem(item: any) {
<style scoped lang="scss">
.admin-page {
padding: 24px;
background-color: #f5f7f9;
/* 使用 Layout 的背景和内边距 */
min-height: 100vh;
}
@@ -154,7 +212,7 @@ function deleteItem(item: any) {
.col-name { flex: 1; position: relative; }
.col-code { flex: 1; }
.col-status { width: 120px; justify-content: center; }
.col-action { width: 150px; justify-content: flex-end; }
.col-action { width: 150px; justify-content: flex-end;display: flex; flex-direction: row; }
.default-badge {
background-color: #e6f7ff;

View File

@@ -29,7 +29,7 @@
</view>
<view class="table-body">
<view v-for="item in dataList" :key="item.id" class="table-row">
<view v-for="item in pagedList" :key="item.id" class="table-row">
<view class="col col-id"><text>{{ item.id }}</text></view>
<view class="col col-code"><text>{{ item.code }}</text></view>
<view class="col col-desc"><text>{{ item.desc }}</text></view>
@@ -42,14 +42,33 @@
</view>
</view>
</view>
<CommonPagination
v-if="true"
:total="total"
:loading="false"
:currentPage="currentPage"
:pageSize="pageSize"
:pageSizeOptionLabels="pageSizeOptionLabels"
:pageSizeIndex="pageSizeIndex"
:visiblePages="visiblePages"
:totalPage="totalPage"
:jumpPageInput="jumpPageInput"
@page-size-change="handlePageSizeChange"
@page-change="handlePageChange"
@update:jumpPageInput="(val: string) => { jumpPageInput.value = val }"
@jump-page="handleJumpPage"
/>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import { ref, computed } from 'vue'
import CommonPagination from '@/components/CommonPagination/CommonPagination.uvue'
// ========== MOCK DATA START ==========
// TODO: 接真实接口时替换此处 dataList 为 fetchRegionList() 调用
const dataList = ref([
{ id: 346, code: 'zh-HK', desc: '中文(繁体, 香港特别行政区)', lang: '' },
{ id: 344, code: 'zh-MO', desc: '中文(繁体, 澳门特别行政区)', lang: '' },
@@ -60,8 +79,50 @@ const dataList = ref([
{ id: 337, code: 'en-GB', desc: '英语(英国)', lang: '' },
{ id: 336, code: 'en-IN', desc: '英语(印度)', lang: '' },
{ id: 335, code: 'en-JM', desc: '英语(牙买加)', lang: '' },
{ id: 334, code: 'en-NZ', desc: '英语(新西兰)', lang: '' }
{ id: 334, code: 'en-NZ', desc: '英语(新西兰)', lang: '' },
{ id: 333, code: 'en-AU', desc: '英语(澳大利亚)', lang: '' },
{ id: 332, code: 'en-CA', desc: '英语(加拿大)', lang: '' },
{ id: 331, code: 'fr-FR', desc: '法语(法国)', lang: '' },
{ id: 330, code: 'fr-CA', desc: '法语(加拿大)', lang: '' },
{ id: 329, code: 'de-DE', desc: '德语(德国)', lang: '' },
{ id: 328, code: 'de-AT', desc: '德语(奥地利)', lang: '' },
{ id: 327, code: 'ja-JP', desc: '日语(日本)', lang: '日本语(ja-JP)' },
{ id: 326, code: 'ko-KR', desc: '韩语(韩国)', lang: '' },
{ id: 325, code: 'es-ES', desc: '西班牙语(西班牙)', lang: '' }
])
// ========== MOCK DATA END ==========
// ========== PAGINATION STATE ==========
const currentPage = ref(1)
const pageSize = ref(15)
const jumpPageInput = ref('')
const pageSizeOptions = [10, 15, 20, 30, 50]
const pageSizeOptionLabels = computed(() => pageSizeOptions.map((n: number) => `${n}条/页`))
const pageSizeIndex = computed(() => { const idx = pageSizeOptions.indexOf(pageSize.value); return idx >= 0 ? idx : 0 })
const total = computed(() => dataList.value.length)
const totalPage = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)))
const pagedList = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return dataList.value.slice(start, start + pageSize.value)
})
const visiblePages = computed((): number[] => {
const t = totalPage.value; const cur = currentPage.value
if (t <= 7) return Array.from({ length: t }, (_: any, i: number) => i + 1)
if (cur <= 4) return [1, 2, 3, 4, 5, -1, t]
if (cur >= t - 3) return [1, -1, t - 4, t - 3, t - 2, t - 1, t]
return [1, -1, cur - 1, cur, cur + 1, -1, t]
})
const handlePageChange = (p: number) => { currentPage.value = p }
const handlePageSizeChange = (e: any) => {
const idx = Number(e.detail.value)
pageSize.value = pageSizeOptions[idx] ?? pageSizeOptions[0]
currentPage.value = 1
}
const handleJumpPage = () => {
const p = parseInt(jumpPageInput.value)
if (!isNaN(p) && p >= 1 && p <= totalPage.value) currentPage.value = p
}
// ========== END PAGINATION STATE ==========
function onSearch() {
uni.showToast({ title: '搜索中...', icon: 'none' })
@@ -90,8 +151,7 @@ function deleteItem(item: any) {
<style scoped lang="scss">
.admin-page {
padding: 24px;
background-color: #f5f7f9;
/* 使用 Layout 的背景和内边距 */
min-height: 100vh;
}
@@ -173,7 +233,7 @@ function deleteItem(item: any) {
.col-code { flex: 1; }
.col-desc { flex: 2; }
.col-lang { flex: 1; }
.col-action { width: 150px; justify-content: flex-end; }
.col-action { width: 150px; justify-content: flex-end; display: flex; flex-direction: row; }
.action-btn {
color: #1890ff;

View File

@@ -1,22 +1,22 @@
<template>
<template>
<view class="admin-page">
<view class="admin-sections">
<!-- 提示栏 -->
<view class="alert-info">
<text class="alert-text">温馨提示:检查更新需要授权码,请先授权后再检查更新!</text>
</view>
<!-- 选项卡 -->
<view class="admin-tabs">
<view
v-for="tab in tabs"
:key="tab.value"
:class="['tab-item', activeTab === tab.value ? 'active' : '']"
@click="activeTab = tab.value"
>
<text class="tab-label">{{ tab.label }}</text>
<view class="admin-sections">
<!-- 提示栏 -->
<view class="alert-info">
<text class="alert-text">温馨提示:检查更新需要授权码,请先授权后再检查更新!</text>
</view>
<!-- 选项卡 -->
<view class="admin-tabs">
<view
v-for="tab in tabs"
:key="tab.value"
:class="['tab-item', activeTab === tab.value ? 'active' : '']"
@click="activeTab = tab.value"
>
<text class="tab-label">{{ tab.label }}</text>
</view>
</view>
</view>
<!-- 升级内容 -->
<view v-if="activeTab === 'upgrade'" class="admin-card">
@@ -55,7 +55,6 @@
</view>
</view>
</view>
</view>
</view>
</template>
@@ -86,9 +85,10 @@ function checkUpdates() {
<style scoped lang="scss">
.admin-page {
padding: 20px;
background-color: #f5f7f9;
min-height: 100vh;
/* 使用 Layout 的背景和内边距 */
padding: 0;
background-color: transparent;
min-height: auto;
}
.alert-info {

View File

@@ -1,15 +1,15 @@
<template>
<template>
<view class="admin-page">
<view class="admin-sections">
<view class="admin-grid-2">
<!-- 清除缓存 -->
<view class="admin-card cache-card">
<view class="cache-header">
<text class="cache-title">清除缓存</text>
<text class="cache-desc">清除系统的所有缓存</text>
<view class="admin-sections">
<view class="admin-grid-2">
<!-- 清除缓存 -->
<view class="admin-card cache-card">
<view class="cache-header">
<text class="cache-title">清除缓存</text>
<text class="cache-desc">清除系统的所有缓存</text>
</view>
<button class="btn primary full" @click="onClearCache">立即清除</button>
</view>
<button class="btn primary full" @click="onClearCache">立即清除</button>
</view>
<!-- 清除日志 -->
<view class="admin-card cache-card">
@@ -25,6 +25,7 @@
</template>
<script setup lang="uts">
function onClearCache() {
uni.showModal({
title: '提示',
@@ -52,9 +53,8 @@ function onClearLog() {
<style scoped lang="scss">
.admin-page {
padding: 20px;
background-color: #f5f7f9;
min-height: 100vh;
/* 使用 Layout 的背景和内边距 */
min-height: auto;
}
.admin-grid-2 {

View File

@@ -1,4 +1,4 @@
<template>
<template>
<view class="admin-page">
<view class="admin-sections">
<!-- 搜索栏 -->
@@ -46,7 +46,7 @@
</view>
<view class="table-body">
<view v-for="item in dataList" :key="item.id" class="table-row">
<view v-for="item in pagedList" :key="item.id" class="table-row">
<view class="col col-id"><text>{{ item.id }}</text></view>
<view class="col col-user"><text>{{ item.user }}</text></view>
<view class="col col-action"><text>{{ item.action }}</text></view>
@@ -58,17 +58,30 @@
</view>
</view>
<!-- 分页 -->
<view class="pagination">
<text class="page-info">共 583387 条 15条/页</text>
</view>
<CommonPagination
v-if="total > 0"
:total="total"
:loading="false"
:currentPage="currentPage"
:pageSize="pageSize"
:pageSizeOptionLabels="pageSizeOptionLabels"
:pageSizeIndex="pageSizeIndex"
:visiblePages="visiblePages"
:totalPage="totalPage"
:jumpPageInput="jumpPageInput"
@page-size-change="handlePageSizeChange"
@page-change="handlePageChange"
@update:jumpPageInput="(val: string) => { jumpPageInput.value = val }"
@jump-page="handleJumpPage"
/>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import { ref, computed } from 'vue'
import CommonPagination from '@/components/CommonPagination/CommonPagination.uvue'
const dataList = ref([
{ id: 585387, user: '5 / demo', action: '系统日志', link: 'system/log', ip: '223.104.72.77', type: 'system', time: '2026-02-11 18:49' },
@@ -81,8 +94,49 @@ const dataList = ref([
{ id: 585380, user: '5 / demo', action: '定时任务类型', link: 'system/crontab/mark', ip: '223.104.72.77', type: 'system', time: '2026-02-11 18:47' },
{ id: 585379, user: '5 / demo', action: '定时任务列表', link: 'system/crontab/list', ip: '223.104.72.77', type: 'system', time: '2026-02-11 18:47' },
{ id: 585378, user: '5 / demo', action: '保存组合数据', link: 'setting/group', ip: '223.104.72.77', type: 'system', time: '2026-02-11 18:47' },
{ id: 585377, user: '5 / demo', action: '保存系统配置分类', link: 'setting/config_class', ip: '223.104.72.77', type: 'system', time: '2026-02-11 18:47' }
{ id: 585377, user: '5 / demo', action: '保存系统配置分类', link: 'setting/config_class', ip: '223.104.72.77', type: 'system', time: '2026-02-11 18:47' },
{ id: 585376, user: '5 / demo', action: '神识别相册管理', link: 'system/gallery', ip: '115.29.168.1', type: 'system', time: '2026-02-11 18:45' },
{ id: 585375, user: '3 / admin', action: '订单列表', link: 'order/list', ip: '116.228.88.5', type: 'system', time: '2026-02-11 18:43' },
{ id: 585374, user: '3 / admin', action: '商品列表', link: 'product/list', ip: '116.228.88.5', type: 'system', time: '2026-02-11 18:42' },
{ id: 585373, user: '3 / admin', action: '用户列表', link: 'user/list', ip: '116.228.88.5', type: 'system', time: '2026-02-11 18:41' },
{ id: 585372, user: '5 / demo', action: '分销分红记录', link: 'distribution/brokerage', ip: '223.104.72.77', type: 'system', time: '2026-02-11 18:40' },
{ id: 585371, user: '5 / demo', action: '财务提现列表', link: 'finance/withdraw', ip: '223.104.72.77', type: 'system', time: '2026-02-11 18:39' },
{ id: 585370, user: '3 / admin', action: '商品分类列表', link: 'product/category', ip: '116.228.88.5', type: 'system', time: '2026-02-11 18:38' },
{ id: 585369, user: '1 / 超级管理员', action: '系统监控', link: 'system/monitor', ip: '127.0.0.1', type: 'system', time: '2026-02-11 18:37' }
])
// ========== MOCK DATA END ==========
// ========== PAGINATION STATE ==========
const currentPage = ref(1)
const pageSize = ref(15)
const jumpPageInput = ref('')
const pageSizeOptions = [10, 15, 20, 30, 50]
const pageSizeOptionLabels = computed(() => pageSizeOptions.map((n: number) => `${n}条/页`))
const pageSizeIndex = computed(() => { const idx = pageSizeOptions.indexOf(pageSize.value); return idx >= 0 ? idx : 0 })
const total = computed(() => dataList.value.length)
const totalPage = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)))
const pagedList = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return dataList.value.slice(start, start + pageSize.value)
})
const visiblePages = computed((): number[] => {
const t = totalPage.value; const cur = currentPage.value
if (t <= 7) return Array.from({ length: t }, (_: any, i: number) => i + 1)
if (cur <= 4) return [1, 2, 3, 4, 5, -1, t]
if (cur >= t - 3) return [1, -1, t - 4, t - 3, t - 2, t - 1, t]
return [1, -1, cur - 1, cur, cur + 1, -1, t]
})
const handlePageChange = (p: number) => { currentPage.value = p }
const handlePageSizeChange = (e: any) => {
const idx = Number(e.detail.value)
pageSize.value = pageSizeOptions[idx] ?? pageSizeOptions[0]
currentPage.value = 1
}
const handleJumpPage = () => {
const p = parseInt(jumpPageInput.value)
if (!isNaN(p) && p >= 1 && p <= totalPage.value) currentPage.value = p
}
// ========== END PAGINATION STATE ==========
function onSearch() {
uni.showToast({ title: '搜索中...', icon: 'none' })
@@ -91,8 +145,7 @@ function onSearch() {
<style scoped lang="scss">
.admin-page {
padding: 20px;
background-color: #f5f7f9;
/* 使用 Layout 的背景和内边距 */
min-height: 100vh;
}

View File

@@ -1,11 +1,11 @@
<template>
<view class="admin-page">
<view class="admin-sections">
<!-- 商业授权 -->
<view class="admin-card info-section">
<view class="section-header">
<text class="section-title">商业授权</text>
</view>
<view class="admin-sections">
<!-- 商业授权 -->
<view class="admin-card info-section">
<view class="section-header">
<text class="section-title">商业授权</text>
</view>
<view class="table-container header-table">
<view class="table-header">
<view class="col col-title"><text>产品证书编号</text></view>
@@ -107,9 +107,8 @@ function gotoOfficial() {
<style scoped lang="scss">
.admin-page {
padding: 24px;
background-color: #f5f7f9;
min-height: 100vh;
/* 使用 Layout 的背景和内边距 */
min-height: auto;
}
.admin-card {

View File

@@ -1,17 +0,0 @@
<template>
<AdminLayout currentPage="system-info">
<view class="page">
<view class="header">
<text class="title">系统信息</text>
</view>
<view class="content">
<text class="tip">TODO: 系统信息</text>
</view>
</view>
</AdminLayout>
</template>
<script setup lang="uts">
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
</script> <style scoped> .page { padding: 16px; } .title { font-size: 18px; font-weight: 600; } .tip { color: #999; margin-top: 8px; display: block; } </style>