Files
xk-admin/apps/web-antd/src/views/system/system-config/index.vue

199 lines
6.7 KiB
Vue
Raw Normal View History

2026-06-18 15:05:02 +08:00
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { Page } from '@vben/common-ui';
2026-07-02 16:21:09 +08:00
import { Button, Card, Input, InputNumber, message, Radio, Space, Switch, Tabs, Tag } from 'ant-design-vue';
2026-06-18 15:05:02 +08:00
import type { QuickDiscountOption } from '#/utils/pricePercentAdjust';
2026-07-02 16:21:09 +08:00
import FormAvatar from '#/components/form/components/avatar.vue';
2026-06-18 15:05:02 +08:00
import { getSystemConfigList, saveSystemConfig } from './api';
defineOptions({ name: 'SystemConfig' });
2026-07-02 16:21:09 +08:00
const TAB_STORAGE_KEY = 'system_config_active_tab';
2026-06-18 15:05:02 +08:00
const loading = ref(false);
const saving = ref(false);
2026-07-02 16:21:09 +08:00
const activeKey = ref('price_adjust');
2026-06-18 15:05:02 +08:00
const scope = ref<'both' | 'sale_only'>('sale_only');
const quickOptions = ref<QuickDiscountOption[]>([
{ name: '九五折', value: 95 },
{ name: '九折', value: 90 },
{ name: '八五折', value: 85 },
{ name: '八折', value: 80 },
{ name: '涨10%', value: 110 },
{ name: '涨20%', value: 120 },
]);
const newQuickName = ref('');
const newQuickValue = ref<number | null>(null);
const inputAutoAuditPass = ref(true);
2026-07-02 16:21:09 +08:00
const miniprogramEmptyImage = ref('');
function readStoredTab() {
const stored = localStorage.getItem(TAB_STORAGE_KEY);
if (stored === 'price_adjust' || stored === 'input_audit' || stored === 'miniprogram') {
activeKey.value = stored;
}
}
function handleTabChange(key: string | number) {
activeKey.value = String(key);
localStorage.setItem(TAB_STORAGE_KEY, String(key));
}
2026-06-18 15:05:02 +08:00
function parseQuickOptions(raw: unknown): QuickDiscountOption[] {
if (typeof raw === 'string') {
try {
const parsed = JSON.parse(raw);
return parseQuickOptions(parsed);
} catch {
return quickOptions.value;
}
}
if (!Array.isArray(raw)) return quickOptions.value;
const list: QuickDiscountOption[] = [];
for (const item of raw) {
if (item && typeof item === 'object' && 'value' in item) {
const val = Number((item as QuickDiscountOption).value);
if (val > 0) list.push({ name: String((item as QuickDiscountOption).name || `${val}%`), value: val });
} else if (typeof item === 'number' && item > 0) {
list.push({ name: `${item}%`, value: item });
}
}
return list.length ? list : quickOptions.value;
}
async function load() {
loading.value = true;
try {
const list = await getSystemConfigList();
const rows = Array.isArray(list) ? list : list?.data || [];
for (const row of rows) {
if (row.config_key === 'order_price_adjust_scope') {
scope.value = row.config_value === 'both' ? 'both' : 'sale_only';
}
if (row.config_key === 'order_discount_quick_options') {
quickOptions.value = parseQuickOptions(row.config_value);
}
if (row.config_key === 'input_auto_audit_pass') {
inputAutoAuditPass.value = row.config_value === '1' || row.config_value === true || row.config_value === 'true';
}
2026-07-02 16:21:09 +08:00
if (row.config_key === 'miniprogram_empty_image') {
miniprogramEmptyImage.value = String(row.config_value || '');
}
2026-06-18 15:05:02 +08:00
}
} finally {
loading.value = false;
}
}
function addQuick() {
const v = Number(newQuickValue.value);
const name = String(newQuickName.value || '').trim();
if (!name || !v || v <= 0) {
message.warning('请填写名称和有效比例值');
return;
}
if (!quickOptions.value.some((o) => o.value === v && o.name === name)) {
quickOptions.value = [...quickOptions.value, { name, value: v }].sort((a, b) => b.value - a.value);
}
newQuickName.value = '';
newQuickValue.value = null;
}
function removeQuick(opt: QuickDiscountOption) {
quickOptions.value = quickOptions.value.filter((o) => !(o.name === opt.name && o.value === opt.value));
}
async function handleSave() {
saving.value = true;
try {
await saveSystemConfig([
{ config_key: 'order_price_adjust_scope', config_value: scope.value },
{
config_key: 'order_discount_quick_options',
config_value: JSON.stringify(quickOptions.value),
},
{
config_key: 'input_auto_audit_pass',
config_value: inputAutoAuditPass.value ? '1' : '0',
},
2026-07-02 16:21:09 +08:00
{
config_key: 'miniprogram_empty_image',
config_value: miniprogramEmptyImage.value,
},
2026-06-18 15:05:02 +08:00
]);
message.success('保存成功');
} finally {
saving.value = false;
}
}
2026-07-02 16:21:09 +08:00
onMounted(() => {
readStoredTab();
load();
});
2026-06-18 15:05:02 +08:00
</script>
<template>
<Page auto-content-height title="系统配置">
2026-07-02 16:21:09 +08:00
<Card :loading="loading">
<Tabs :active-key="activeKey" @change="handleTabChange">
<Tabs.TabPane key="price_adjust" tab="订单调价">
<div class="py-4">
<div class="mb-6">
<div class="mb-2 font-medium">订单打折时价格浮动范围</div>
<Radio.Group v-model:value="scope">
<Radio value="sale_only">仅售价浮动</Radio>
<Radio value="both">供货价与售价一起浮动</Radio>
</Radio.Group>
</div>
<div class="mb-6">
<div class="mb-2 font-medium">调价快捷选项(name 展示名,value 比例:100=原价,90=九折,120=涨20%)</div>
<Space wrap class="mb-3">
<Tag
v-for="item in quickOptions"
:key="item.name + item.value"
closable
:color="item.value < 100 ? 'processing' : 'warning'"
@close="removeQuick(item)"
>
{{ item.name }}({{ item.value }}%)
</Tag>
</Space>
<Space wrap>
<Input v-model:value="newQuickName" placeholder="名称,如九折" style="width: 140px" />
<InputNumber v-model:value="newQuickValue" :min="1" :max="500" placeholder="比例值" />
<Button @click="addQuick">添加快捷项</Button>
</Space>
</div>
</div>
</Tabs.TabPane>
<Tabs.TabPane key="input_audit" tab="录入审核">
<div class="py-4">
<div class="mb-2 font-medium">诊所/医生信息预填录入后是否自动通过审核</div>
<Switch v-model:checked="inputAutoAuditPass" checked-children="是" un-checked-children="否" />
</div>
</Tabs.TabPane>
<Tabs.TabPane key="miniprogram" tab="小程序">
<div class="py-4">
<div class="mb-2 font-medium">空状态图片</div>
<div class="mb-2 text-sm text-gray-500">用于小程序列表等无数据时的占位图</div>
<FormAvatar v-model:value="miniprogramEmptyImage" />
</div>
</Tabs.TabPane>
</Tabs>
2026-06-18 15:05:02 +08:00
<div class="mt-4">
<Button type="primary" :loading="saving" @click="handleSave">保存配置</Button>
</div>
</Card>
</Page>
</template>