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>
|