Compare commits
No commits in common. "4d1e125840bb3c7e4eff783ae02711a00c06c39b" and "9837d9fb41e503160fea34b8313c9a48443895aa" have entirely different histories.
4d1e125840
...
9837d9fb41
@ -145,7 +145,6 @@ export const MENU_LABELS: MenuLabel[] = [
|
|||||||
{ url: 'sys/dict-type', label: '字典类型', icon: 'Collection', path: '/sys/dict', sort: 412 },
|
{ url: 'sys/dict-type', label: '字典类型', icon: 'Collection', path: '/sys/dict', sort: 412 },
|
||||||
{ url: 'sys/notice', label: '通知公告', icon: 'Bell', path: '/sys/notice', sort: 413 },
|
{ url: 'sys/notice', label: '通知公告', icon: 'Bell', path: '/sys/notice', sort: 413 },
|
||||||
{ url: 'sys/params', label: '参数配置', icon: 'Setting', path: '/sys/params', sort: 414 },
|
{ url: 'sys/params', label: '参数配置', icon: 'Setting', path: '/sys/params', sort: 414 },
|
||||||
{ url: 'sys/theme-config', label: '主题配置', icon: 'Brush', path: '/sys/theme-config', sort: 415 },
|
|
||||||
{ url: 'message/mail-template', label: '邮件模板', icon: 'Document', path: '/message/mail-template', sort: 803 },
|
{ url: 'message/mail-template', label: '邮件模板', icon: 'Document', path: '/message/mail-template', sort: 803 },
|
||||||
{ url: 'message/mail-log', label: '邮件日志', icon: 'List', path: '/message/mail-log', sort: 804 },
|
{ url: 'message/mail-log', label: '邮件日志', icon: 'List', path: '/message/mail-log', sort: 804 },
|
||||||
{ url: 'message/sms-log', label: '短信日志', icon: 'List', path: '/message/sms-log', sort: 805 },
|
{ url: 'message/sms-log', label: '短信日志', icon: 'List', path: '/message/sms-log', sort: 805 },
|
||||||
|
|||||||
@ -1,256 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
/**
|
|
||||||
* 价格公式系统变量管理 (运营后台)
|
|
||||||
*
|
|
||||||
* 走 /supplier/supplierGoodPriceFormulaVariable/ API
|
|
||||||
* 功能: 变量 CRUD + 快速改值 (改后 SKU 公式价格自动变化)
|
|
||||||
* 字段: varKey/varName/varUnit/currentValue/sourceType/sourceCode/formula/sort/status
|
|
||||||
*/
|
|
||||||
import { onMounted, ref, reactive } from 'vue'
|
|
||||||
import { httpGet, httpPost } from '@/utils/request'
|
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
||||||
import DataTable from '@/components/DataTable/DataTable.vue'
|
|
||||||
import PageHeader from '@/components/PageHeader/PageHeader.vue'
|
|
||||||
|
|
||||||
interface Variable {
|
|
||||||
id: string
|
|
||||||
varKey: string
|
|
||||||
varName: string
|
|
||||||
varUnit: string
|
|
||||||
currentValue: number
|
|
||||||
sourceType: number
|
|
||||||
sourceCode: string
|
|
||||||
formula: string
|
|
||||||
sort: number
|
|
||||||
status: number
|
|
||||||
createDate: string
|
|
||||||
updateDate: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const list = ref<Variable[]>([])
|
|
||||||
const loading = ref(false)
|
|
||||||
const total = ref(0)
|
|
||||||
const page = ref(1)
|
|
||||||
const limit = ref(20)
|
|
||||||
const keyword = ref('')
|
|
||||||
|
|
||||||
// 新增/编辑弹窗
|
|
||||||
const dialogVisible = ref(false)
|
|
||||||
const saving = ref(false)
|
|
||||||
const form = reactive<any>({
|
|
||||||
id: null, varKey: '', varName: '', varUnit: '', currentValue: 0,
|
|
||||||
sourceType: 1, sourceCode: '', formula: '', sort: 0, status: 1
|
|
||||||
})
|
|
||||||
|
|
||||||
// 快速改值
|
|
||||||
const quickEditId = ref<string | null>(null)
|
|
||||||
const quickValue = ref('')
|
|
||||||
|
|
||||||
const SOURCE_MAP: Record<number, string> = { 1: '手工维护', 2: 'AKShare采集' }
|
|
||||||
|
|
||||||
async function load() {
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
const data: any = await httpGet('/supplier/supplierGoodPriceFormulaVariable/page', {
|
|
||||||
page: page.value, limit: limit.value, keyword: keyword.value || undefined
|
|
||||||
})
|
|
||||||
list.value = (data?.list || []).map((it: any) => ({
|
|
||||||
id: String(it.id), varKey: it.var_key || '--', varName: it.var_name || '--',
|
|
||||||
varUnit: it.var_unit || '', currentValue: Number(it.current_value || 0),
|
|
||||||
sourceType: Number(it.source_type || 1), sourceCode: it.source_code || '',
|
|
||||||
formula: it.formula || '', sort: Number(it.sort || 0), status: Number(it.status || 1),
|
|
||||||
createDate: it.create_date || '--', updateDate: it.update_date || '--'
|
|
||||||
}))
|
|
||||||
total.value = Number(data?.total || 0)
|
|
||||||
} catch (e: any) {
|
|
||||||
ElMessage.error(e?.msg || e?.message || '加载失败')
|
|
||||||
list.value = []
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSearch() { page.value = 1; load() }
|
|
||||||
function onReset() { keyword.value = ''; page.value = 1; load() }
|
|
||||||
function onPageChange(p: number) { page.value = p; load() }
|
|
||||||
function onLimitChange(l: number) { limit.value = l; page.value = 1; load() }
|
|
||||||
|
|
||||||
function openAdd() {
|
|
||||||
Object.assign(form, { id: null, varKey: '', varName: '', varUnit: '', currentValue: 0, sourceType: 1, sourceCode: '', formula: '', sort: 0, status: 1 })
|
|
||||||
dialogVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function openEdit(row: Variable) {
|
|
||||||
Object.assign(form, {
|
|
||||||
id: row.id, varKey: row.varKey, varName: row.varName, varUnit: row.varUnit,
|
|
||||||
currentValue: row.currentValue, sourceType: row.sourceType, sourceCode: row.sourceCode,
|
|
||||||
formula: row.formula, sort: row.sort, status: row.status
|
|
||||||
})
|
|
||||||
dialogVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
async function save() {
|
|
||||||
if (!form.varKey.trim() || !form.varName.trim()) {
|
|
||||||
ElMessage.warning('变量标识和变量名必填'); return
|
|
||||||
}
|
|
||||||
saving.value = true
|
|
||||||
try {
|
|
||||||
await httpPost('/supplier/supplierGoodPriceFormulaVariable/save', { ...form })
|
|
||||||
ElMessage.success('保存成功')
|
|
||||||
dialogVisible.value = false
|
|
||||||
load()
|
|
||||||
} catch (e: any) {
|
|
||||||
ElMessage.error(e?.msg || e?.message || '保存失败')
|
|
||||||
} finally { saving.value = false }
|
|
||||||
}
|
|
||||||
|
|
||||||
async function quickUpdateValue(row: Variable) {
|
|
||||||
if (quickValue.value === '') return
|
|
||||||
try {
|
|
||||||
await httpPost('/supplier/supplierGoodPriceFormulaVariable/updateValue', { id: row.id, currentValue: quickValue.value })
|
|
||||||
ElMessage.success(`「${row.varName}」值已更新为 ${quickValue.value}, 相关商品价格将自动变化`)
|
|
||||||
quickEditId.value = null
|
|
||||||
quickValue.value = ''
|
|
||||||
load()
|
|
||||||
} catch (e: any) {
|
|
||||||
ElMessage.error(e?.msg || e?.message || '更新失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function remove(row: Variable) {
|
|
||||||
try {
|
|
||||||
await ElMessageBox.confirm(`确认删除变量「${row.varName}」? 引用该变量的商品公式将无法计算`, '删除确认', { type: 'warning' })
|
|
||||||
await httpGet('/supplier/supplierGoodPriceFormulaVariable/delete', { id: row.id })
|
|
||||||
ElMessage.success('删除成功')
|
|
||||||
load()
|
|
||||||
} catch (e: any) {
|
|
||||||
if (e === 'cancel' || e === 'close') return
|
|
||||||
ElMessage.error(e?.msg || e?.message || '删除失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleStatus(row: Variable) {
|
|
||||||
const next = row.status === 1 ? 0 : 1
|
|
||||||
httpPost('/supplier/supplierGoodPriceFormulaVariable/save', {
|
|
||||||
id: row.id, varKey: row.varKey, varName: row.varName, varUnit: row.varUnit,
|
|
||||||
currentValue: row.currentValue, sourceType: row.sourceType, sourceCode: row.sourceCode,
|
|
||||||
formula: row.formula, sort: row.sort, status: next
|
|
||||||
}).then(() => {
|
|
||||||
ElMessage.success(next === 1 ? '已启用' : '已禁用')
|
|
||||||
load()
|
|
||||||
}).catch((e: any) => ElMessage.error(e?.msg || e?.message || '操作失败'))
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(load)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="rr-page">
|
|
||||||
<PageHeader title="价格变量管理" :sub="`系统级价格变量 (共 ${total} 个), 修改变量值后所有引用商品的售价自动更新`">
|
|
||||||
<el-button type="primary" @click="openAdd">新增变量</el-button>
|
|
||||||
</PageHeader>
|
|
||||||
|
|
||||||
<div class="rr-card">
|
|
||||||
<div class="flex items-center gap-3 mb-4">
|
|
||||||
<el-input v-model="keyword" placeholder="按变量名/标识搜索" clearable style="width: 260px" @keyup.enter="onSearch" />
|
|
||||||
<el-button type="primary" @click="onSearch">搜索</el-button>
|
|
||||||
<el-button @click="onReset">重置</el-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<DataTable
|
|
||||||
:columns="[
|
|
||||||
{ prop: 'varName', label: '变量名', width: 120 },
|
|
||||||
{ prop: 'varKey', label: '标识', width: 140 },
|
|
||||||
{ prop: 'varUnit', label: '单位', width: 80 },
|
|
||||||
{ prop: 'currentValue', label: '当前值', width: 130, slot: 'value' },
|
|
||||||
{ prop: 'sourceType', label: '取值方式', width: 110, slot: 'source' },
|
|
||||||
{ prop: 'sourceCode', label: '采集代码', width: 100 },
|
|
||||||
{ prop: 'status', label: '状态', width: 80, slot: 'status' },
|
|
||||||
{ prop: 'updateDate', label: '更新时间', width: 170 },
|
|
||||||
{ prop: 'action', label: '操作', width: 220, slot: 'action', fixed: 'right' }
|
|
||||||
]"
|
|
||||||
:data="list"
|
|
||||||
:loading="loading"
|
|
||||||
:total="total"
|
|
||||||
:page="page"
|
|
||||||
:limit="limit"
|
|
||||||
:show-pagination="true"
|
|
||||||
:page-sizes="[10, 20, 50, 100]"
|
|
||||||
@page-change="onPageChange"
|
|
||||||
@limit-change="onLimitChange"
|
|
||||||
empty-text="暂无变量"
|
|
||||||
borderless
|
|
||||||
row-key="id"
|
|
||||||
>
|
|
||||||
<template #value="{ row }">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<span class="font-bold text-primary">¥{{ Number(row.currentValue).toLocaleString() }}</span>
|
|
||||||
<template v-if="quickEditId === row.id">
|
|
||||||
<el-input v-model="quickValue" size="small" style="width: 90px" placeholder="新值" @keyup.enter="quickUpdateValue(row)" />
|
|
||||||
<el-button size="small" type="primary" @click="quickUpdateValue(row)">确认</el-button>
|
|
||||||
<el-button size="small" @click="quickEditId = null; quickValue = ''">取消</el-button>
|
|
||||||
</template>
|
|
||||||
<el-button v-else size="small" text type="primary" @click="quickEditId = row.id; quickValue = String(row.currentValue)">改值</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #source="{ row }">
|
|
||||||
<el-tag :type="row.sourceType === 2 ? 'warning' : 'info'" size="small" effect="light">
|
|
||||||
{{ SOURCE_MAP[row.sourceType] || '未知' }}
|
|
||||||
</el-tag>
|
|
||||||
</template>
|
|
||||||
<template #status="{ row }">
|
|
||||||
<el-tag :type="row.status === 1 ? 'success' : 'danger'" size="small" effect="light">
|
|
||||||
{{ row.status === 1 ? '启用' : '禁用' }}
|
|
||||||
</el-tag>
|
|
||||||
</template>
|
|
||||||
<template #action="{ row }">
|
|
||||||
<el-link type="primary" :underline="false" @click="openEdit(row)">编辑</el-link>
|
|
||||||
<el-divider direction="vertical" />
|
|
||||||
<el-link type="warning" :underline="false" @click="toggleStatus(row)">{{ row.status === 1 ? '禁用' : '启用' }}</el-link>
|
|
||||||
<el-divider direction="vertical" />
|
|
||||||
<el-link type="danger" :underline="false" @click="remove(row)">删除</el-link>
|
|
||||||
</template>
|
|
||||||
</DataTable>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 新增/编辑弹窗 -->
|
|
||||||
<el-dialog v-model="dialogVisible" :title="form.id ? '编辑变量' : '新增变量'" width="520px" destroy-on-close>
|
|
||||||
<el-form label-width="90px" label-position="left">
|
|
||||||
<el-form-item label="变量标识" required>
|
|
||||||
<el-input v-model="form.varKey" placeholder="如 copper_price (英文/下划线, 唯一)" :disabled="!!form.id" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="变量名" required>
|
|
||||||
<el-input v-model="form.varName" placeholder="如 铜价 (公式中显示的名称)" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="单位">
|
|
||||||
<el-input v-model="form.varUnit" placeholder="如 元/吨" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="当前值">
|
|
||||||
<el-input-number v-model="form.currentValue" :precision="4" :step="100" style="width: 100%" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="取值方式">
|
|
||||||
<el-radio-group v-model="form.sourceType">
|
|
||||||
<el-radio :value="1">手工维护</el-radio>
|
|
||||||
<el-radio :value="2">AKShare采集</el-radio>
|
|
||||||
</el-radio-group>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item v-if="form.sourceType === 2" label="采集代码">
|
|
||||||
<el-input v-model="form.sourceCode" placeholder="AKShare 商品代码, 如 copper/aluminum/steel" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item v-if="form.sourceType === 2" label="换算公式">
|
|
||||||
<el-input v-model="form.formula" placeholder="可选, 如 raw * 1.05 (raw=采集原始值)" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="排序">
|
|
||||||
<el-input-number v-model="form.sort" :min="0" style="width: 100%" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="状态">
|
|
||||||
<el-switch v-model="form.status" :active-value="1" :inactive-value="0" active-text="启用" inactive-text="禁用" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<el-button @click="dialogVisible = false">取消</el-button>
|
|
||||||
<el-button type="primary" :loading="saving" @click="save">保存</el-button>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -150,12 +150,6 @@ const routes: RouteRecordRaw[] = [
|
|||||||
component: () => import('@/modules/other/goodunit/GoodunitListView.vue'),
|
component: () => import('@/modules/other/goodunit/GoodunitListView.vue'),
|
||||||
meta: { title: '商品单位' }
|
meta: { title: '商品单位' }
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/other/priceFormulaVariable",
|
|
||||||
name: "OtherPriceFormulaVariable",
|
|
||||||
component: () => import("@/modules/other/priceFormulaVariable/PriceFormulaVariableListView.vue"),
|
|
||||||
meta: { title: "价格变量管理" }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/other/label',
|
path: '/other/label',
|
||||||
name: 'OtherLabel',
|
name: 'OtherLabel',
|
||||||
@ -255,8 +249,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
{ path: '/sys/log/operation', component: () => import('@/modules/sys/LogOperationView.vue'), meta: { title: '操作日志' } },
|
{ path: '/sys/log/operation', component: () => import('@/modules/sys/LogOperationView.vue'), meta: { title: '操作日志' } },
|
||||||
{ path: '/sys/log/error', component: () => import('@/modules/sys/LogErrorView.vue'), meta: { title: '异常日志' } },
|
{ path: '/sys/log/error', component: () => import('@/modules/sys/LogErrorView.vue'), meta: { title: '异常日志' } },
|
||||||
{ path: '/sys/notice', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'sys-notice' }), meta: { title: '通知公告' } },
|
{ path: '/sys/notice', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'sys-notice' }), meta: { title: '通知公告' } },
|
||||||
{ path: '/sys/params', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'sys-params' }), meta: { title: '参数配置' } },
|
{ path: '/sys/params', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'sys-params' }), meta: { title: '参数配置' } },
|
||||||
{ path: '/sys/theme-config', component: () => import('@/views/theme/ThemeConfigView.vue'), meta: { title: '主题配置' } },
|
|
||||||
{ path: '/sys/notice-user', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'sys-notice-user' }), meta: { title: '用户通知' } },
|
{ path: '/sys/notice-user', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'sys-notice-user' }), meta: { title: '用户通知' } },
|
||||||
{ path: '/sys/userManagement', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'sys-userManagement' }), meta: { title: '全国运营中心' } },
|
{ path: '/sys/userManagement', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'sys-userManagement' }), meta: { title: '全国运营中心' } },
|
||||||
// user 9 个
|
// user 9 个
|
||||||
|
|||||||
@ -1,178 +0,0 @@
|
|||||||
<!--
|
|
||||||
* F006 主题配置 (管理后台)
|
|
||||||
* - 选 4 套预设主题之一,应用到 pc-前端-v2 全站
|
|
||||||
* - 后端接口:
|
|
||||||
* GET /sys/openapi/theme/current → { theme: "industrial-blue", available: [...] }
|
|
||||||
* POST /sys/openapi/theme/update?theme=xxx 切换主题
|
|
||||||
* - 切换后 pc-前端-v2 下次刷新或主页轮询时会拿到最新主题
|
|
||||||
-->
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, computed, onMounted } from 'vue'
|
|
||||||
import { ElMessage } from 'element-plus'
|
|
||||||
import PageHeader from '@/components/PageHeader/PageHeader.vue'
|
|
||||||
import { httpGet, httpPost } from '@/utils/request'
|
|
||||||
|
|
||||||
interface Theme {
|
|
||||||
key: string
|
|
||||||
label: string
|
|
||||||
description: string
|
|
||||||
primary: string
|
|
||||||
'primary-container': string
|
|
||||||
secondary: string
|
|
||||||
surface: string
|
|
||||||
'text-primary': string
|
|
||||||
border: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const themes = ref<Theme[]>([
|
|
||||||
{
|
|
||||||
key: 'industrial-blue',
|
|
||||||
label: '工业蓝',
|
|
||||||
description: '默认主题,适用于工业品 B2B 大宗交易场景',
|
|
||||||
primary: '#0B5394', 'primary-container': '#E8F0FA',
|
|
||||||
secondary: '#E07B00', surface: '#FFFFFF',
|
|
||||||
'text-primary': '#1A1F2C', border: '#E2E6EE',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'steel-red',
|
|
||||||
label: '钢红',
|
|
||||||
description: '国企/政策导向客户首选,庄重正式',
|
|
||||||
primary: '#A8202E', 'primary-container': '#FAE8EB',
|
|
||||||
secondary: '#C99B30', surface: '#FFFFFF',
|
|
||||||
'text-primary': '#1A1F2C', border: '#EFE2E4',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'forest-green',
|
|
||||||
label: '森林绿',
|
|
||||||
description: '能源/碳中和/化工场景,传递可持续发展',
|
|
||||||
primary: '#1F6E4D', 'primary-container': '#E5F2EC',
|
|
||||||
secondary: '#D4A017', surface: '#FFFFFF',
|
|
||||||
'text-primary': '#1A1F2C', border: '#E2EAE5',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'midnight-slate',
|
|
||||||
label: '暗夜深灰',
|
|
||||||
description: '数据型暗模式,适合长时间盯盘的运营人员',
|
|
||||||
primary: '#3B82F6', 'primary-container': '#1E293B',
|
|
||||||
secondary: '#60A5FA', surface: '#0F172A',
|
|
||||||
'text-primary': '#F1F5F9', border: '#334155',
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
const current = ref<string>('')
|
|
||||||
const preview = ref<string>('') // 用户选中但未保存
|
|
||||||
const saving = ref(false)
|
|
||||||
const loading = ref(false)
|
|
||||||
|
|
||||||
const updateTime = ref('')
|
|
||||||
|
|
||||||
async function load() {
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
const res: any = await httpGet('/sys/openapi/theme/current')
|
|
||||||
if (res.code === 0) {
|
|
||||||
current.value = res.data.theme
|
|
||||||
preview.value = res.data.theme
|
|
||||||
updateTime.value = new Date().toLocaleString('zh-CN')
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
ElMessage.error('加载当前主题失败: ' + (e as Error).message)
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function save() {
|
|
||||||
if (!preview.value) return
|
|
||||||
saving.value = true
|
|
||||||
try {
|
|
||||||
const res: any = await httpPost(`/sys/openapi/theme/update?theme=${preview.value}`)
|
|
||||||
if (res.code === 0) {
|
|
||||||
current.value = preview.value
|
|
||||||
updateTime.value = new Date().toLocaleString('zh-CN')
|
|
||||||
ElMessage.success(`主题已切换为「${findByKey(preview.value)?.label}」,pc-前端-v2 下次访问将自动应用`)
|
|
||||||
} else {
|
|
||||||
ElMessage.error(res.msg || '保存失败')
|
|
||||||
}
|
|
||||||
} catch (e: any) {
|
|
||||||
ElMessage.error('保存失败: ' + e.message)
|
|
||||||
} finally {
|
|
||||||
saving.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function findByKey(key: string) {
|
|
||||||
return themes.value.find(t => t.key === key)
|
|
||||||
}
|
|
||||||
|
|
||||||
const dirty = computed(() => preview.value !== current.value)
|
|
||||||
|
|
||||||
onMounted(load)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<PageHeader title="主题配置" :sub="`当前生效主题 · ${findByKey(current)?.label || '加载中'} · 更新于 ${updateTime}`">
|
|
||||||
<div class="flex gap-2 items-center">
|
|
||||||
<el-tag v-if="dirty" type="warning" effect="plain">有未保存的更改</el-tag>
|
|
||||||
<el-button type="primary" :disabled="!dirty || saving" :loading="saving" @click="save">
|
|
||||||
保存并应用
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</PageHeader>
|
|
||||||
|
|
||||||
<div v-loading="loading" class="grid grid-cols-1 lg:grid-cols-2 gap-5 mt-5">
|
|
||||||
<div
|
|
||||||
v-for="theme in themes"
|
|
||||||
:key="theme.key"
|
|
||||||
class="rounded-2xl border-2 transition-all cursor-pointer overflow-hidden hover:-translate-y-1 hover:shadow-xl"
|
|
||||||
:class="preview === theme.key
|
|
||||||
? 'border-primary shadow-lg shadow-primary/20'
|
|
||||||
: 'border-border'"
|
|
||||||
@click="preview = theme.key"
|
|
||||||
>
|
|
||||||
<!-- 预览头 -->
|
|
||||||
<div
|
|
||||||
class="h-24 flex items-center justify-between px-5 text-white"
|
|
||||||
:style="{ background: `linear-gradient(135deg, ${theme.primary}, ${theme.secondary})` }"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<div class="text-lg font-bold">{{ theme.label }}</div>
|
|
||||||
<div class="text-xs opacity-80 mt-1">{{ theme.key }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<el-icon v-if="preview === theme.key" :size="22"><Check /></el-icon>
|
|
||||||
<el-tag v-if="current === theme.key" type="success" effect="dark" size="small">当前生效</el-tag>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Preview mock -->
|
|
||||||
<div class="p-5 space-y-3" :style="{ backgroundColor: theme.surface, color: theme['text-primary'] }">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<div class="w-8 h-8 rounded-lg flex items-center justify-center text-white font-bold"
|
|
||||||
:style="{ backgroundColor: theme.primary }">M</div>
|
|
||||||
<div class="font-bold text-sm">LOGO 主品牌色</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-xs opacity-70">{{ theme.description }}</div>
|
|
||||||
<div class="flex gap-2 mt-3">
|
|
||||||
<button class="px-4 py-1.5 rounded-lg text-xs font-bold text-white"
|
|
||||||
:style="{ backgroundColor: theme.primary }">主按钮</button>
|
|
||||||
<button class="px-4 py-1.5 rounded-lg text-xs font-bold border-2"
|
|
||||||
:style="{ borderColor: theme.primary, color: theme.primary }">描边按钮</button>
|
|
||||||
<span class="px-3 py-1 rounded text-xs font-bold"
|
|
||||||
:style="{ backgroundColor: theme['primary-container'], color: theme.primary }">标签</span>
|
|
||||||
</div>
|
|
||||||
<div class="text-[10px] opacity-50 pt-2 border-t" :style="{ borderColor: theme.border }">
|
|
||||||
HEX: {{ theme.primary }} · {{ theme.secondary }} · {{ theme['text-primary'] }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-alert
|
|
||||||
class="mt-6"
|
|
||||||
type="info"
|
|
||||||
:closable="false"
|
|
||||||
title="关于主题切换"
|
|
||||||
description="点击主题卡片预览,保存后 pc-前端-v2 的所有页面(包括其他用户正在浏览)将在下次请求 / 重载时生效。无需重启服务。"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
Loading…
Reference in New Issue
Block a user