feat: 管理端公账支付与确认、锁店遮罩及表单提交修复

新增公账账单/到账确认页与全局锁店组件;系统配置补公账参数面板;
统一弹窗 onConfirm 校验提交,并完善订单详情与门店相关交互。
This commit is contained in:
李琦
2026-08-21 12:51:42 +08:00
parent 66e666754a
commit 879efb8f29
145 changed files with 7326 additions and 884 deletions

View File

@@ -23,8 +23,8 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updateAdmin : createAdmin;
@@ -37,9 +37,7 @@ const [Modal, modalApi] = useVbenModal({
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;

View File

@@ -23,8 +23,8 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updateBaseConfig : createBaseConfig;
@@ -37,9 +37,7 @@ const [Modal, modalApi] = useVbenModal({
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;

View File

@@ -121,8 +121,8 @@ const [Modal, modalApi] = useVbenModal({
);
return;
}
formApi.validate().then(async (e: any) => {
if (e.valid) {
const e = await formApi.validate();
if (!e.valid) return;
const payload = { ...(await formApi.getValues()) };
// sale_price 只作封顶校验,不用费用合计覆盖
if (payload.sale_price === undefined || payload.sale_price === '') {
@@ -146,9 +146,7 @@ const [Modal, modalApi] = useVbenModal({
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
async onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;

View File

@@ -9,6 +9,15 @@ export async function getDeliveryWarehouseOrderList(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data });
}
/**
* 仓侧销售金额 / 我的收益(按本仓分账,不跟发货 Tab)
*/
export async function saleAmountApi(data?: Record<string, any>) {
return requestClient.get<{ total: number; income: number }>(`${prefix}sale-amount`, {
params: data,
});
}
/**
* 配送仓库订单详情(本仓药品 + 收货/物流基础字段)
*/

View File

@@ -1,4 +1,8 @@
<script lang="ts" setup>
/**
* 配送仓库发货弹窗
* 确认时自行校验并调发货接口;不再走已弃用的 validateAndSubmitForm(会表现为点确定无反应)
*/
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
@@ -21,37 +25,43 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
sendDeliveryWarehouseOrder(values)
.then(() => {
message.success('发货成功');
gridApi.value?.reload();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
try {
await sendDeliveryWarehouseOrder(values);
message.success('发货成功');
gridApi.value?.reload?.();
modalApi.close();
} catch (err) {
console.error(err);
} finally {
modalApi.setState({ loading: false, confirmLoading: false });
}
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values } = modalApi.getData<Record<string, any>>();
const { values } = modalApi.getData<Record<string, any>>() || {};
if (values) {
orderNo.value = values.order_no;
formApi.setValues(values);
orderNo.value = values.order_no || '';
formApi.setValues({
...values,
express_no: values.express_no || '',
express_company_code: values.express_company_code || '',
introduce: values.introduce || '',
});
}
}
},
});
</script>
<template>
<Modal :title="`订单:【 ${orderNo} 】发货`" class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]">
<Modal
:title="`订单:【 ${orderNo} 】发货`"
class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]"
>
<Form />
</Modal>
</template>

View File

@@ -1,4 +1,7 @@
<script lang="ts" setup>
/**
* 配送仓库「我的订单」:本仓发货列表 + 顶部销售/分账收益(与商品订单页分离)
*/
import { computed, onMounted, reactive, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
@@ -16,7 +19,11 @@ import {
Tag,
} from 'ant-design-vue';
import { getDeliveryWarehouseOrderList } from '#/views/system/delivery-warehouse-order/api';
import { StatIslands, type StatIslandItem } from '#/components/stat-islands';
import {
getDeliveryWarehouseOrderList,
saleAmountApi,
} from '#/views/system/delivery-warehouse-order/api';
import PrescriptionDetail from '#/views/doctor/doctor-reception/components/PrescriptionDetail.vue';
import DetailModalDemo from './components/detail-modal.vue';
@@ -178,7 +185,36 @@ function addressText(row: Record<string, any>) {
onMounted(() => {
void loadList();
void loadSaleAmount();
});
const overviewItems = ref<StatIslandItem[]>([]);
/**
* 顶部销售/收益不跟发货 Tab,按本仓已支付订单 + 分账表汇总
*/
async function loadSaleAmount() {
try {
const res = await saleAmountApi();
overviewItems.value = [
{
key: 'total',
label: '销售金额',
value: res?.total ?? 0,
icon: 'lucide:trending-up',
},
{
key: 'income',
label: '我的收益',
value: res?.income ?? 0,
icon: 'lucide:wallet',
emphasize: true,
},
];
} catch {
overviewItems.value = [];
}
}
</script>
<template>
@@ -188,6 +224,7 @@ onMounted(() => {
<PrescriptionDetailModal/>
<div class="flex flex-col gap-6 p-2 md:p-4">
<StatIslands v-if="overviewItems.length" class="mb-0" :items="overviewItems" :columns="4" />
<!-- 顶部操作栏 -->
<div class="flex flex-col gap-4 rounded-2xl bg-white dark:bg-[#141414] px-6 py-4 shadow-sm dark:shadow-none dark:border dark:border-[#303030] md:flex-row md:items-center md:justify-between transition-colors duration-300">
<Tabs :active-key="statusTab" :tab-bar-style="{ margin: 0, border: 'none' }" @change="handleTabChange" size="large">

View File

@@ -260,8 +260,8 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
const status = Number(values.status);
if (status !== 2 && status !== 3) {
@@ -315,9 +315,7 @@ const [Modal, modalApi] = useVbenModal({
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {

View File

@@ -69,8 +69,8 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
// 新建不提交 status;未审核编辑也不提交(后端也会忽略,前端再挡一层)
const payload = { ...values };
@@ -91,9 +91,7 @@ const [Modal, modalApi] = useVbenModal({
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
async onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;

View File

@@ -24,26 +24,23 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value
? updateDeliveryWarehouse
: createDeliveryWarehouse;
submitApi(values)
.then(() => {
message.success('保存成功');
// 优先 query,兼容部分页面挂的 reload
gridApi.value?.query?.() ?? gridApi.value?.reload?.();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value
? updateDeliveryWarehouse
: createDeliveryWarehouse;
try {
await submitApi(values);
message.success('保存成功');
gridApi.value?.query?.() ?? gridApi.value?.reload?.();
modalApi.close();
} catch (err) {
console.error(err);
} finally {
modalApi.setState({ loading: false, confirmLoading: false });
}
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;

View File

@@ -46,22 +46,21 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.lock();
const submitApi = isUpdate.value ? updateDepartment : createDepartment;
submitApi(values)
.then(() => {
message.success('保存成功');
gridApi.value?.query();
modalApi.close();
})
.finally(() => {
modalApi.unlock();
});
}
});
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
modalApi.lock();
const submitApi = isUpdate.value ? updateDepartment : createDepartment;
try {
await submitApi(values);
message.success('保存成功');
gridApi.value?.query();
modalApi.close();
} catch (err) {
console.error(err);
} finally {
modalApi.unlock();
}
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;

View File

@@ -26,8 +26,8 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updateDoctorInput : createDoctorInput;
@@ -40,9 +40,7 @@ const [Modal, modalApi] = useVbenModal({
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
async onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;

View File

@@ -24,8 +24,8 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
// 处理 store_id,如果为空字符串则设为 null
if (values.store_id === '' || values.store_id === undefined) {
@@ -42,9 +42,7 @@ const [Modal, modalApi] = useVbenModal({
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;

View File

@@ -23,8 +23,8 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updateMenu : createMenu;
@@ -37,9 +37,7 @@ const [Modal, modalApi] = useVbenModal({
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;

View File

@@ -21,8 +21,8 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updateNotice : createNotice;
@@ -35,9 +35,7 @@ const [Modal, modalApi] = useVbenModal({
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;

View File

@@ -34,8 +34,8 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updateOaChat : createOaChat;
@@ -48,9 +48,7 @@ const [Modal, modalApi] = useVbenModal({
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;

View File

@@ -95,8 +95,8 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
// 合并群聊绑定数组到提交载荷(后端 OaRobotController::notRequest 已包含 chat_ids)
const payload = { ...values, chat_ids: selectedChatIds.value };
@@ -111,9 +111,7 @@ const [Modal, modalApi] = useVbenModal({
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;

View File

@@ -209,8 +209,8 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const e = await formApi.validate();
if (!e.valid) return;
// 客户群必须配置确认发送员工(前端先拦,后端 syncSceneTargets 再校验)
const targetErr = targetsPanelRef.value?.validateBeforeSubmit?.() || '';
if (targetErr) {
@@ -229,9 +229,7 @@ const [Modal, modalApi] = useVbenModal({
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;

View File

@@ -192,8 +192,10 @@ function inlineFmt(s: string): string {
* 按消息类型计算的展示数据
* ------------------------------------------------------------------ */
/** 文本内容(用于 text / markdown) */
const textContent = computed(() => String(mergedPayload.value.content || ''));
/** 文本内容:企微/飞书用 content,钉钉 markdown 用 text */
const textContent = computed(() =>
String(mergedPayload.value.content || mergedPayload.value.text || ''),
);
/** @ 人列表(用于在文本后追加展示) */
const atList = computed<string[]>(() => {

View File

@@ -33,8 +33,8 @@ const [Modal, modalApi] = useVbenModal({
// 确认按钮回调(提交表单)
onConfirm: async () => {
// 验证表单
formApi.validate().then(async (e: any) => {
if (e.valid) {
const e = await formApi.validate();
if (!e.valid) return;
// 获取表单值
const values = await formApi.getValues();
// 设置加载状态
@@ -54,9 +54,7 @@ const [Modal, modalApi] = useVbenModal({
// 取消加载状态
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
// 弹窗打开/关闭状态变化回调
async onOpenChange(isOpen: boolean) {

View File

@@ -35,11 +35,10 @@ export const modalFormProps: VbenFormProps = {
defaultValue: 1,
},
{
// 基础信息分隔线
// 基础信息分隔线(仅展示,禁止 rules,否则空值必失败)
component: 'Divider',
fieldName: '',
fieldName: '_section_basic',
label: '',
rules: 'required',
hideLabel: true,
renderComponentContent: () => {
return {
@@ -230,11 +229,10 @@ export const modalFormProps: VbenFormProps = {
},
},
{
// 销售比例信息分隔线
// 销售比例信息分隔线(仅展示,禁止 rules / 勿用 label:'x')
component: 'Divider',
fieldName: '',
label: 'x',
rules: 'required',
fieldName: '_section_sale_percent',
label: '',
hideLabel: true,
dependencies: {
show: false,

View File

@@ -20,8 +20,8 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
updatePlatformInfo(values)
@@ -33,9 +33,7 @@ const [Modal, modalApi] = useVbenModal({
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;

View File

@@ -24,8 +24,8 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
// 处理 store_id,如果为空字符串则设为 null
if (values.store_id === '' || values.store_id === undefined) {
@@ -42,9 +42,7 @@ const [Modal, modalApi] = useVbenModal({
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;

View File

@@ -35,9 +35,10 @@ export const modalFormProps: VbenFormProps = {
},
},
{
// 分区标题:仅展示,fieldName 用唯一占位,禁止空串 / rules
component: 'Divider',
fieldName: '',
label: '联系方式',
fieldName: '_section_contact',
label: '',
formItemClass: 'col-span-12',
hideLabel: true,
renderComponentContent: () => {
@@ -80,9 +81,10 @@ export const modalFormProps: VbenFormProps = {
defaultValue: '',
},
{
// 分区标题:仅展示,fieldName 用唯一占位,禁止空串 / rules
component: 'Divider',
fieldName: '',
label: '投诉举报方式',
fieldName: '_section_complaint',
label: '',
formItemClass: 'col-span-12',
hideLabel: true,
renderComponentContent: () => {

View File

@@ -24,8 +24,8 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
// 处理 store_id,如果为空字符串则设为 null
if (values.store_id === '' || values.store_id === undefined) {
@@ -42,9 +42,7 @@ const [Modal, modalApi] = useVbenModal({
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;

View File

@@ -23,8 +23,8 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updatePlatform : createPlatform;
@@ -37,9 +37,7 @@ const [Modal, modalApi] = useVbenModal({
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;

View File

@@ -21,8 +21,8 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updateRoleQuickNav : createRoleQuickNav;
@@ -35,9 +35,7 @@ const [Modal, modalApi] = useVbenModal({
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;

View File

@@ -23,23 +23,21 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updateRole : createRole;
submitApi(values)
.then(() => {
message.success('保存成功');
gridApi.value?.reload();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updateRole : createRole;
try {
await submitApi(values);
message.success('保存成功');
gridApi.value?.reload();
modalApi.close();
} catch (err) {
console.error(err);
} finally {
modalApi.setState({ loading: false, confirmLoading: false });
}
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;

View File

@@ -1,15 +1,19 @@
<script lang="ts" setup>
/**
* 门店信息录入:新增/编辑弹窗
* 新增打开时 reset 写入 schema defaultValue(如中药比例 100),避免界面有数、模型为空导致「请输入{0}」误报
*/
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import dayjs from 'dayjs';
import { useVbenForm } from '#/adapter/form';
import { modalFormProps } from '#/views/system/store-input/config/form';
import { createStoreInput, getStoreInputDetail, updateStoreInput } from '../api';
import dayjs from 'dayjs';
import { createStoreInput, updateStoreInput } from '../api';
const isUpdate = ref(false);
const gridApi = ref();
@@ -23,52 +27,49 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updateStoreInput : createStoreInput;
submitApi(values)
.then(() => {
message.success(isUpdate.value ? '更新成功' : '录入成功');
gridApi.value?.reload();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updateStoreInput : createStoreInput;
try {
await submitApi(values);
message.success(isUpdate.value ? '更新成功' : '录入成功');
gridApi.value?.reload?.();
modalApi.close();
} catch (err) {
console.error(err);
} finally {
modalApi.setState({ loading: false, confirmLoading: false });
}
},
async onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values, update } = modalApi.getData<Record<string, any>>();
if (values) {
isUpdate.value = update;
values.address = [values.province_id, values.city_id];
// 处理合同文件
if (values.contract_files && Array.isArray(values.contract_files)) {
values.contract_files = values.contract_files.map((file: any) => {
if (typeof file === 'string') {
return { url: file, name: file.split('/').pop() || '合同文件' };
}
return file;
});
}
formApi.setValues({
...values,
start_time: dayjs(values.start_time || '08:00', 'HH:mm'),
end_time: dayjs(values.end_time || '20:00', 'HH:mm'),
subscribe_price_change: values.subscribe_price_change ?? 0,
if (!isOpen) return;
const { values, update } = modalApi.getData<Record<string, any>>() || {};
if (values) {
isUpdate.value = !!update;
values.address = [values.province_id, values.city_id];
// 处理合同文件
if (values.contract_files && Array.isArray(values.contract_files)) {
values.contract_files = values.contract_files.map((file: any) => {
if (typeof file === 'string') {
return { url: file, name: file.split('/').pop() || '合同文件' };
}
return file;
});
} else {
isUpdate.value = false;
}
formApi.setValues({
...values,
start_time: dayjs(values.start_time || '08:00', 'HH:mm'),
end_time: dayjs(values.end_time || '20:00', 'HH:mm'),
subscribe_price_change: values.subscribe_price_change ?? 0,
});
return;
}
// 新增:reset 应用 schema defaultValue(中药采购/销售比例等)
isUpdate.value = false;
await formApi.reset();
},
});
</script>

View File

@@ -24,9 +24,8 @@ export const modalFormProps: VbenFormProps = {
},
{
component: 'Divider',
fieldName: '',
fieldName: '_section_basic',
label: '',
rules: 'required',
hideLabel: true,
renderComponentContent: () => {
return {
@@ -194,6 +193,7 @@ export const modalFormProps: VbenFormProps = {
},
},
{
// 合同文件:选填(后端 StoreInputController::notRequest 已放行)
component: 'UploadImage',
fieldName: 'contract_files',
label: '合同文件',
@@ -204,9 +204,8 @@ export const modalFormProps: VbenFormProps = {
},
{
component: 'Divider',
fieldName: '',
label: 'x',
rules: 'required',
fieldName: '_section_sale_percent',
label: '',
hideLabel: true,
renderComponentContent: () => {
return {

View File

@@ -228,6 +228,11 @@ export async function updateStoreOrderPricePercentAdjust(data: { id: number }) {
return requestClient.post<any>(`${prefix}update-order-price-percent-adjust`, data);
}
/** 切换诊所是否开通公账支付 */
export async function updateStorePublicAccountPayEnabled(data: { id: number }) {
return requestClient.post<any>(`${prefix}update-public-account-pay-enabled`, data);
}
export async function toggleSalespersonSeePriceApi(data: { store_id: number }) {
return requestClient.post<any>('salesperson-store-config/toggle-see-price', data);
}

View File

@@ -32,8 +32,8 @@ const [Modal, modalApi] = useVbenModal({
},
onConfirm: async () => {
// 校验表单通过后再提交
formApi.validate().then(async (e: any) => {
if (!e.valid) return;
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
// 强制写入当前诊所 id(数组形式,匹配后端 normalizeStoreIds)
values.store_id = [storeId.value];
@@ -48,8 +48,7 @@ const [Modal, modalApi] = useVbenModal({
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
if (!isOpen) return;

View File

@@ -15,6 +15,7 @@ defineProps<{
onSubscribe: (id: number) => void;
onSeeRate: (id: number) => void;
onOrderPriceAdjust: (id: number) => void;
onPublicAccountPay?: (id: number) => void;
onBankCardReport: (row: Record<string, any>) => void;
onClinicType?: (row: Record<string, any>) => void;
onAllowInsurance?: (id: number) => void;
@@ -84,6 +85,18 @@ function handleBankCardTagClick(
</Tag>
</div>
</div>
<div v-if="onPublicAccountPay" class="config-item">
<span class="config-item__label">公账支付</span>
<div class="config-item__control">
<Tag
:color="Number(row.public_account_pay_enabled) === 1 ? 'success' : 'default'"
class="config-item__tag"
@click="onPublicAccountPay(row.id)"
>
{{ Number(row.public_account_pay_enabled) === 1 ? '开启' : '关闭' }}
</Tag>
</div>
</div>
</template>
<template v-else-if="section === 'business'">
@@ -135,6 +148,18 @@ function handleBankCardTagClick(
</Tag>
</div>
</div>
<div v-if="onPublicAccountPay" class="config-item">
<span class="config-item__label">公账支付</span>
<div class="config-item__control">
<Tag
:color="Number(row.public_account_pay_enabled) === 1 ? 'success' : 'default'"
class="config-item__tag"
@click="onPublicAccountPay(row.id)"
>
{{ Number(row.public_account_pay_enabled) === 1 ? '开启' : '关闭' }}
</Tag>
</div>
</div>
</template>
<template v-else-if="section === 'all'">
@@ -200,6 +225,18 @@ function handleBankCardTagClick(
</Tag>
</div>
</div>
<div v-if="onPublicAccountPay" class="config-item">
<span class="config-item__label">公账支付</span>
<div class="config-item__control">
<Tag
:color="Number(row.public_account_pay_enabled) === 1 ? 'success' : 'default'"
class="config-item__tag"
@click="onPublicAccountPay(row.id)"
>
{{ Number(row.public_account_pay_enabled) === 1 ? '开启' : '关闭' }}
</Tag>
</div>
</div>
</template>
</div>
</template>

View File

@@ -1,15 +1,19 @@
<script lang="ts" setup>
/**
* 诊所新增/编辑弹窗
* 固定 type=0;提交用单次 await validate,禁止 validateAndSubmitForm 双调用
*/
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import dayjs from 'dayjs';
import { useVbenForm } from '#/adapter/form';
import { modalFormProps } from '#/views/system/store/config/form';
import { createStore, updateStore } from '../api';
import dayjs from "dayjs";
const isUpdate = ref(false);
const gridApi = ref();
@@ -24,42 +28,41 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updateStore : createStore;
// 固定设置type=0(诊所类型),无论是新增还是编辑
submitApi(values)
.then(() => {
message.success('保存成功');
gridApi.value?.reload();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updateStore : createStore;
try {
// 固定设置 type=0(诊所类型)
await submitApi(values);
message.success('保存成功');
gridApi.value?.reload?.();
modalApi.close();
} catch (err) {
console.error(err);
} finally {
modalApi.setState({ loading: false, confirmLoading: false });
}
},
async onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values, update } = modalApi.getData<Record<string, any>>();
if (values) {
isUpdate.value = update;
values.address = [values.province_id, values.city_id];
formApi.setValues({
...values,
start_time: dayjs(values.start_time || '08:00', 'HH:mm'),
end_time: dayjs(values.end_time || '20:00', 'HH:mm'),
// 确保订阅状态有值,如果后端没有返回则默认为0(订阅)
subscribe_price_change: values.subscribe_price_change ?? 0,
sync_admin_phone: false,
});
}
if (!isOpen) return;
const { values, update } = modalApi.getData<Record<string, any>>() || {};
if (values) {
isUpdate.value = !!update;
values.address = [values.province_id, values.city_id];
formApi.setValues({
...values,
start_time: dayjs(values.start_time || '08:00', 'HH:mm'),
end_time: dayjs(values.end_time || '20:00', 'HH:mm'),
subscribe_price_change: values.subscribe_price_change ?? 0,
sync_admin_phone: false,
});
return;
}
isUpdate.value = false;
await formApi.reset();
},
});
</script>

View File

@@ -42,9 +42,8 @@ export const modalFormProps: VbenFormProps = {
},
{
component: 'Divider',
fieldName: '',
fieldName: '_section_basic',
label: '',
rules: 'required',
hideLabel: true,
renderComponentContent: () => {
return {
@@ -127,6 +126,19 @@ export const modalFormProps: VbenFormProps = {
label: '订单打折/涨幅',
defaultValue: 0,
},
{
component: 'RadioGroup',
formItemClass: 'col-span-6',
componentProps: {
options: [
{ label: '关闭', value: 0 },
{ label: '开启', value: 1 },
],
},
fieldName: 'public_account_pay_enabled',
label: '公账支付',
defaultValue: 0,
},
{
// 诊所类型(单选)
component: 'RadioGroup',
@@ -259,9 +271,8 @@ export const modalFormProps: VbenFormProps = {
},
{
component: 'Divider',
fieldName: '',
label: 'x',
rules: 'required',
fieldName: '_section_sale_percent',
label: '',
hideLabel: true,
renderComponentContent: () => {
return {

View File

@@ -30,6 +30,7 @@ import {
updateStoreSeeRateStatus,
updateStoreSubscribeStatus,
updateStoreOrderPricePercentAdjust,
updateStorePublicAccountPayEnabled,
} from './api';
import FormModalDemo from './components/modal.vue';
import StoreBasicInfoCell from './components/cells/StoreBasicInfoCell.vue';
@@ -451,6 +452,13 @@ const updateOrderPricePercentAdjust = (id: number) => {
});
};
const updatePublicAccountPayEnabled = (id: number) => {
updateStorePublicAccountPayEnabled({ id }).then(() => {
message.success('修改成功!');
gridApi.query();
});
};
/**
* 批量同步总仓库药品
*/
@@ -593,6 +601,7 @@ const handleSwitchClinicType = (row: any) => {
:on-subscribe="updateSubscribe"
:on-see-rate="updateSeeRate"
:on-order-price-adjust="updateOrderPricePercentAdjust"
:on-public-account-pay="updatePublicAccountPayEnabled"
:on-bank-card-report="handleBankCardReportColumnClick"
/>
</template>
@@ -610,6 +619,7 @@ const handleSwitchClinicType = (row: any) => {
:on-subscribe="updateSubscribe"
:on-see-rate="updateSeeRate"
:on-order-price-adjust="updateOrderPricePercentAdjust"
:on-public-account-pay="updatePublicAccountPayEnabled"
:on-bank-card-report="handleBankCardReportColumnClick"
:on-clinic-type="handleSwitchClinicType"
:on-allow-insurance="updateAllowInsurance"

View File

@@ -23,8 +23,8 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updateSupplier : createSupplier;
@@ -37,9 +37,7 @@ const [Modal, modalApi] = useVbenModal({
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;

View File

@@ -0,0 +1,442 @@
<script lang="ts" setup>
/**
* 系统配置 - 公账支付:出账模式、按天出账/锁定天数/锁店时刻、解锁条件、锁对象、提醒框颜色、催办联系人
* 模式=按订单时隐藏生成时刻、解锁条件、锁店项、提醒框颜色、催办联系人
*/
import { computed, onMounted, ref } from 'vue';
import { Button, Input, InputNumber, Radio, Select, Switch, TimePicker, message } from 'ant-design-vue';
import dayjs, { type Dayjs } from 'dayjs';
import { getAdminList } from '#/views/system/admin/api';
import { getSystemConfigList, saveSystemConfig } from '../api';
interface ConfirmContactRow {
uid: string;
name: string;
phone: string;
show: boolean;
admin_id: number;
}
defineOptions({ name: 'PublicAccountPayConfigPanel' });
const loading = ref(false);
const saving = ref(false);
const billMode = ref<'order' | 'daily'>('order');
const generateTime = ref<Dayjs>(dayjs('22:00', 'HH:mm'));
const lockTime = ref<Dayjs>(dayjs('09:00', 'HH:mm'));
/** 账单日起算满 N 天后锁店,默认 1=次日 */
const lockDays = ref(1);
const unlockMode = ref<'voucher' | 'confirm'>('confirm');
const lockClinicAdmin = ref(false);
const lockClinicRole = ref(false);
const alertColor = ref<'blue' | 'yellow' | 'red'>('yellow');
const selectedAdminIds = ref<number[]>([]);
const contacts = ref<ConfirmContactRow[]>([]);
const adminOptions = ref<
Array<{ label: string; value: number; name: string; phone: string }>
>([]);
let contactUid = 0;
const isDaily = computed(() => billMode.value === 'daily');
const showConfirmContacts = computed(
() => isDaily.value && unlockMode.value === 'confirm',
);
function nextContactUid() {
contactUid += 1;
return `c${contactUid}`;
}
function parseHm(raw: unknown, fallback: string): Dayjs {
const text = String(raw || fallback);
const parsed = dayjs(text, 'HH:mm', true);
return parsed.isValid() ? parsed : dayjs(fallback, 'HH:mm');
}
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 === 'public_account_bill_mode') {
billMode.value = row.config_value === 'daily' ? 'daily' : 'order';
}
if (row.config_key === 'public_account_daily_generate_time') {
generateTime.value = parseHm(row.config_value, '22:00');
}
if (row.config_key === 'public_account_lock_time') {
lockTime.value = parseHm(row.config_value, '09:00');
}
if (row.config_key === 'public_account_lock_days') {
const days = Number(row.config_value || 1);
lockDays.value = Number.isFinite(days) && days >= 1 ? Math.floor(days) : 1;
}
if (row.config_key === 'public_account_unlock_mode') {
unlockMode.value = row.config_value === 'voucher' ? 'voucher' : 'confirm';
}
if (row.config_key === 'public_account_lock_clinic_admin') {
lockClinicAdmin.value = ['1', 'true', true, 1].includes(row.config_value);
}
if (row.config_key === 'public_account_lock_clinic_role') {
lockClinicRole.value = ['1', 'true', true, 1].includes(row.config_value);
}
if (row.config_key === 'public_account_lock_alert_color') {
const color = String(row.config_value || 'yellow');
alertColor.value =
color === 'blue' || color === 'red' ? color : 'yellow';
}
if (row.config_key === 'public_account_confirm_contacts') {
contacts.value = parseContacts(row.config_value);
selectedAdminIds.value = contacts.value
.filter((item) => item.admin_id > 0)
.map((item) => item.admin_id);
}
}
} finally {
loading.value = false;
}
}
/** 后台 JSON 可能是字符串或已解析数组 */
function parseContacts(raw: unknown): ConfirmContactRow[] {
let list: unknown = raw;
if (typeof raw === 'string') {
try {
list = JSON.parse(raw);
} catch {
list = [];
}
}
if (!Array.isArray(list)) {
return [];
}
return list
.map((row: any) => ({
uid: nextContactUid(),
name: String(row?.name || ''),
phone: String(row?.phone || '').replace(/\s+/g, ''),
show: Number(row?.show || 0) === 1,
admin_id: Number(row?.admin_id || 0),
}))
.filter((row) => row.phone);
}
async function loadAdmins() {
const res = await getAdminList({ page: 1, pageSize: 500 });
const rows: any[] = res?.items || res?.data || [];
adminOptions.value = rows
.filter((row) => Number(row?.id || 0) > 0)
.map((row) => {
const name = String(row.nick_name || row.name || '');
const phone = String(row.phone || '').trim();
return {
value: Number(row.id),
name,
phone,
label: phone ? `${name} ${phone}` : name || `管理员#${row.id}`,
};
});
}
function filterAdmin(input: string, option: any) {
const keyword = String(input || '').trim().toLowerCase();
if (!keyword) {
return true;
}
return String(option?.label || '').toLowerCase().includes(keyword);
}
/** 多选管理员:带入姓名手机号,取消勾选则去掉对应行 */
function onAdminSelect(ids: number[]) {
const idSet = new Set(ids.map(Number));
contacts.value = contacts.value.filter(
(row) => !row.admin_id || idSet.has(row.admin_id),
);
for (const id of idSet) {
if (contacts.value.some((row) => row.admin_id === id)) {
continue;
}
const opt = adminOptions.value.find((item) => item.value === id);
contacts.value.push({
uid: nextContactUid(),
name: opt?.name || '',
phone: opt?.phone || '',
show: true,
admin_id: id,
});
}
}
function addContact() {
contacts.value.push({
uid: nextContactUid(),
name: '',
phone: '',
show: true,
admin_id: 0,
});
}
function removeContact(idx: number) {
const row = contacts.value[idx];
contacts.value.splice(idx, 1);
if (row?.admin_id) {
selectedAdminIds.value = selectedAdminIds.value.filter(
(id) => id !== row.admin_id,
);
}
}
async function handleSave() {
saving.value = true;
try {
await saveSystemConfig([
{
config_key: 'public_account_bill_mode',
config_value: billMode.value,
config_group: 'public_account_pay',
value_type: 'string',
},
{
config_key: 'public_account_daily_generate_time',
config_value: generateTime.value.format('HH:mm'),
config_group: 'public_account_pay',
value_type: 'string',
},
{
config_key: 'public_account_lock_time',
config_value: lockTime.value.format('HH:mm'),
config_group: 'public_account_pay',
value_type: 'string',
},
{
config_key: 'public_account_lock_days',
config_value: String(Math.max(1, Math.floor(Number(lockDays.value) || 1))),
config_group: 'public_account_pay',
value_type: 'int',
description:
'按天锁店宽限天数:账单日起算满 N 天后的 lock_time 未达解锁条件则锁店,默认1(次日)',
sort: 402,
},
{
config_key: 'public_account_unlock_mode',
config_value: unlockMode.value,
config_group: 'public_account_pay',
value_type: 'string',
},
{
config_key: 'public_account_lock_clinic_admin',
config_value: lockClinicAdmin.value ? '1' : '0',
config_group: 'public_account_pay',
value_type: 'bool',
},
{
config_key: 'public_account_lock_clinic_role',
config_value: lockClinicRole.value ? '1' : '0',
config_group: 'public_account_pay',
value_type: 'bool',
},
{
config_key: 'public_account_lock_alert_color',
config_value: alertColor.value,
config_group: 'public_account_pay',
value_type: 'string',
},
{
config_key: 'public_account_confirm_contacts',
config_value: JSON.stringify(
contacts.value
.map((row) => ({
name: row.name.trim(),
phone: row.phone.replace(/\s+/g, ''),
show: row.show ? 1 : 0,
admin_id: row.admin_id || 0,
}))
.filter((row) => row.phone),
),
config_group: 'public_account_pay',
value_type: 'json',
description: '确认到账催办联系人:[{name,phone,show,admin_id}],show=1展示可拨打',
sort: 407,
},
]);
message.success('保存成功');
} finally {
saving.value = false;
}
}
onMounted(() => {
load();
loadAdmins();
});
</script>
<template>
<div class="cfg-panel" :class="{ 'opacity-60': loading }">
<div class="cfg-panel-body">
<div class="mb-6">
<div class="mb-2 font-medium text-[hsl(var(--foreground))]">出账模式</div>
<Radio.Group v-model:value="billMode">
<Radio value="order">按订单(一单一确认,不锁店)</Radio>
<Radio value="daily">按天先用后付(到点出日汇总)</Radio>
</Radio.Group>
</div>
<template v-if="isDaily">
<div class="mb-6">
<div class="mb-2 font-medium text-[hsl(var(--foreground))]">日汇总生成时刻</div>
<TimePicker v-model:value="generateTime" format="HH:mm" />
<div class="mt-1 text-xs text-[hsl(var(--muted-foreground))]">
到达该时刻后汇总当天未入账单,历史漏单随时补
</div>
</div>
<div class="mb-6">
<div class="mb-2 font-medium text-[hsl(var(--foreground))]">锁定天数</div>
<InputNumber
v-model:value="lockDays"
:min="1"
:max="90"
:precision="0"
class="w-40"
/>
<span class="ml-2 text-sm text-[hsl(var(--muted-foreground))]">天</span>
<div class="mt-1 text-xs text-[hsl(var(--muted-foreground))]">
账单日起算满 N 天后,到达下方锁店时刻仍未处理则锁定。默认 1(即次日)
</div>
</div>
<div class="mb-6">
<div class="mb-2 font-medium text-[hsl(var(--foreground))]">锁店时刻</div>
<TimePicker v-model:value="lockTime" format="HH:mm" />
<div class="mt-1 text-xs text-[hsl(var(--muted-foreground))]">
宽限第 N 天该时刻后日账单仍未达解锁条件,则拦截对应角色;更早的账单全天锁定
</div>
</div>
<div class="mb-6">
<div class="mb-2 font-medium text-[hsl(var(--foreground))]">解锁条件</div>
<Radio.Group v-model:value="unlockMode">
<Radio value="voucher">诊所上传凭证即解锁并入账</Radio>
<Radio value="confirm">须超管/系统管理员确认到账才解锁</Radio>
</Radio.Group>
</div>
<div class="mb-6">
<div class="mb-2 font-medium text-[hsl(var(--foreground))]">锁店范围(默认只锁医生)</div>
<div class="flex flex-col gap-3">
<label class="flex items-center gap-2 text-sm text-[hsl(var(--foreground))]">
<Switch v-model:checked="lockClinicAdmin" />
同时锁定诊所管理员
</label>
<label class="flex items-center gap-2 text-sm text-[hsl(var(--foreground))]">
<Switch v-model:checked="lockClinicRole" />
同时锁定诊所角色(xk_role.user_type=1)
</label>
</div>
</div>
<div class="mb-6">
<div class="mb-2 font-medium text-[hsl(var(--foreground))]">逾期提醒框颜色</div>
<Radio.Group v-model:value="alertColor" class="alert-color-group">
<Radio value="blue">
<span class="color-opt">
<i class="swatch is-blue" />
蓝色
</span>
</Radio>
<Radio value="yellow">
<span class="color-opt">
<i class="swatch is-yellow" />
黄色
</span>
</Radio>
<Radio value="red">
<span class="color-opt">
<i class="swatch is-red" />
红色
</span>
</Radio>
</Radio.Group>
<div class="mt-1 text-xs text-[hsl(var(--muted-foreground))]">
仅影响逾期锁定时的全屏提醒框背景
</div>
</div>
<div v-if="showConfirmContacts" class="mb-6">
<div class="mb-2 font-medium text-[hsl(var(--foreground))]">催办联系手机号</div>
<div class="mb-2 text-xs text-[hsl(var(--muted-foreground))]">
可多选管理员带入手机号,也可手动添加。仅打开「展示」的号码会出现在锁店等待页,供诊所拨打。
</div>
<Select
v-model:value="selectedAdminIds"
mode="multiple"
class="mb-3 w-full"
:options="adminOptions"
:filter-option="filterAdmin"
allow-clear
placeholder="多选管理员(带手机号)"
@change="onAdminSelect"
/>
<div
v-for="(row, idx) in contacts"
:key="row.uid"
class="contact-row"
>
<Input v-model:value="row.name" placeholder="称呼" />
<Input v-model:value="row.phone" placeholder="手机号" />
<label class="contact-show">
<Switch v-model:checked="row.show" size="small" />
<span>展示</span>
</label>
<Button type="link" danger @click="removeContact(idx)">删除</Button>
</div>
<Button class="mt-2" @click="addContact">添加手机号</Button>
</div>
</template>
</div>
<div class="cfg-panel-footer">
<Button type="primary" :loading="saving" @click="handleSave">保存本模块配置</Button>
</div>
</div>
</template>
<style scoped>
.color-opt {
display: inline-flex;
gap: 6px;
align-items: center;
}
.swatch {
display: inline-block;
width: 14px;
height: 14px;
border-radius: 4px;
box-shadow: 0 0 6px hsl(var(--border));
}
.swatch.is-blue {
background: hsl(217 91% 53%);
border: 1px solid hsl(217 91% 53% / 40%);
}
.swatch.is-yellow {
background: hsl(var(--warning));
border: 1px solid hsl(var(--warning) / 40%);
}
.swatch.is-red {
background: hsl(0 72% 51%);
border: 1px solid hsl(0 72% 51% / 40%);
}
.contact-row {
display: grid;
grid-template-columns: 1fr 1fr auto auto;
gap: 8px;
align-items: center;
margin-bottom: 8px;
}
.contact-show {
display: inline-flex;
gap: 6px;
align-items: center;
font-size: 13px;
color: hsl(var(--foreground));
white-space: nowrap;
}
</style>

View File

@@ -26,6 +26,7 @@ import MiniprogramConfigPanel from './components/miniprogram-config-panel.vue';
import ModuleTabsPanel from './components/module-tabs-panel.vue';
import OaNotifyConfigPanel from './components/oa-notify-config-panel.vue';
import PriceAdjustConfigPanel from './components/price-adjust-config-panel.vue';
import PublicAccountPayConfigPanel from './components/public-account-pay-config-panel.vue';
import SalespersonConfigPanel from './components/salesperson-config-panel.vue';
import WorkbenchConfigPanel from './components/workbench-config-panel.vue';
import WxWorkbenchDefaultFavPanel from './components/wx-workbench-default-fav-panel.vue';
@@ -82,6 +83,11 @@ const menuItems: MenuItem[] = [
component: PriceAdjustConfigPanel,
},
{ key: 'logistics', title: '物流展示', component: LogisticsConfigPanel },
{
key: 'public_account_pay',
title: '公账支付',
component: PublicAccountPayConfigPanel,
},
],
},
{
@@ -138,6 +144,7 @@ const LEGACY_MENU_MAP: Record<string, { menu: string; tab?: string }> = {
input_audit: { menu: 'audit', tab: 'input_audit' },
miniprogram: { menu: 'mp_patient' },
logistics: { menu: 'order', tab: 'logistics' },
public_account_pay: { menu: 'order', tab: 'public_account_pay' },
salesperson: { menu: 'security', tab: 'salesperson' },
doctor_login: { menu: 'security', tab: 'login' },
dict_search: { menu: 'medical', tab: 'dict_search' },

View File

@@ -62,23 +62,19 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
updateTransferAssistantConfig(values)
.then(() => {
message.success('保存成功');
modalApi.close();
})
.catch((error) => {
message.error(error?.message || '保存失败');
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
try {
await updateTransferAssistantConfig(values);
message.success('保存成功');
modalApi.close();
} catch (error: any) {
message.error(error?.message || '保存失败');
} finally {
modalApi.setState({ loading: false, confirmLoading: false });
}
},
onOpenChange(isOpen: boolean) {
if (isOpen) {

View File

@@ -21,8 +21,8 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const e = await formApi.validate();
if (!e.valid) return;
const values = await formApi.getValues();
// 处理answers字段:将换行分隔的字符串转换为数组
@@ -54,9 +54,7 @@ const [Modal, modalApi] = useVbenModal({
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;