1. 推广员功能增强
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled

This commit is contained in:
李琦
2026-06-18 15:05:02 +08:00
parent d9db629c5d
commit 073f7f882b
10 changed files with 750 additions and 309 deletions

View File

@@ -0,0 +1,29 @@
import { requestClient } from '#/api/request';
const prefix = 'file-gallery/';
export interface FileGalleryItem {
id: number;
url: string;
file_size: number;
file_size_text: string;
created_at?: string;
original_name?: string;
}
export async function getFileGalleryList(params?: { page?: number; page_size?: number; pid?: number; type?: number }) {
return requestClient.get<{
items: FileGalleryItem[];
total: number;
page: number;
page_size: number;
}>(`${prefix}list`, { params });
}
export async function deleteFileGalleryItem(id: number) {
return requestClient.post<any>(`${prefix}delete`, { id });
}
export async function syncFileGalleryFromOss() {
return requestClient.post<{ added: number; skipped: number }>(`${prefix}sync-oss`);
}

View File

@@ -61,7 +61,7 @@ export async function getOssSignature() {
/**
* OSS 直传成功后登记 xk_file
*/
export async function registerOssFile(data: { url: string; type?: number; source?: number }) {
export async function registerOssFile(data: { url: string; type?: number; source?: number; file_size?: number }) {
return requestClient.post('/upload/register-oss-file', data);
}

View File

@@ -2,7 +2,7 @@
import { useVModel } from '@vueuse/core';
import { Upload } from 'ant-design-vue';
// 导入上传相关API和工具函数
import GalleryPickLink from '#/components/form/components/gallery-pick-link.vue';
import { uploadFile } from '#/api/core/upload';
import { uploadToOss } from '#/utils/oss-upload';
import {
@@ -26,67 +26,43 @@ const mValue = useVModel(props, 'value', emits, {
defaultValue: props.value,
passive: true,
});
/**
* 自定义上传请求处理函数
*
* 该函数根据preferences配置的上传方式选择使用OSS直传或后端上传
* - 'direct': OSS直传模式文件直接从浏览器上传到阿里云OSS
* - 'backend': 后端上传模式文件先上传到后端服务器再由后端上传到OSS
*
* 上传方式说明:
* 1. OSS直传direct
* - 调用uploadToOss函数直接上传到OSS
* - 减少服务器负载,上传速度更快
*
* 2. 后端上传backend
* - 调用uploadFile API通过后端服务器上传
* - 兼容现有功能,所有上传逻辑由后端统一处理
*
* 兼容性处理:
* - 两种上传方式返回的数据结构保持一致:{ url: string }
* - 上传成功后将URL赋值给mValue触发组件更新
*
* @param e 上传事件对象包含file字段文件对象
*/
const customRequest = async (e: any) => {
try {
const actualFile = resolveUploadFile(e.file as File);
// 从preferences中读取上传方式配置
// uploadMethod可能的值'direct'OSS直传或 'backend'(后端上传)
const uploadMethod = preferences.app.uploadMethod || 'direct';
let data: { url: string };
// 根据配置选择上传方式
if (uploadMethod === 'direct') {
// OSS直传模式文件直接从浏览器上传到OSS
// uploadToOss函数会处理签名获取、文件上传等所有逻辑
data = await uploadToOss({
file: actualFile,
});
} else {
// 后端上传模式文件先上传到后端服务器再由后端上传到OSS
// uploadFile函数会将文件发送到后端API/upload/image
data = await uploadFile({
file: actualFile,
});
}
// 上传成功将URL赋值给mValue
// mValue是双向绑定的值更新后会触发父组件的更新
mValue.value = data.url;
} catch (error) {
// 上传失败,记录错误信息
// 注意这里没有显示错误提示如果需要可以添加message.error
console.error('头像上传失败', error);
}
};
function onGallerySelect(urls: string[]) {
if (urls[0]) {
mValue.value = urls[0];
}
}
const handleRemove = (e: Event) => {
e.stopPropagation();
mValue.value = '';
};
</script>
<template>
<div class="avatar-upload-wrap">
<Upload
:before-upload="beforeImageUpload"
:custom-request="customRequest"
@@ -99,20 +75,28 @@ const handleRemove = (e: Event) => {
icon="ant-design:delete-outlined"
@click="handleRemove"
/>
<img :src="value" width="100%" />
<img :src="mValue" width="100%" />
</div>
<Icon v-else icon="ant-design:plus-outlined" />
</Upload>
<GalleryPickLink @select="onGallerySelect" />
</div>
</template>
<style lang="less" scoped>
.avatar-upload-wrap {
display: inline-block;
}
.m-avatar-wrap {
position: relative;
height: 102px;
overflow: hidden;
.m-avatar-icon-delete {
position: absolute;
top: 0;
right: 0;
z-index: 1;
cursor: pointer;
}
}

View File

@@ -0,0 +1,82 @@
<script setup lang="ts">
import { ref } from 'vue';
import ImageGalleryPicker from '#/components/form/components/image-gallery-picker.vue';
const props = withDefaults(
defineProps<{
multiple?: boolean;
maxCount?: number;
disabled?: boolean;
}>(),
{
multiple: false,
maxCount: 1,
disabled: false,
},
);
const emit = defineEmits<{
select: [urls: string[]];
}>();
const galleryOpen = ref(false);
function openGallery() {
if (props.disabled) {
return;
}
galleryOpen.value = true;
}
function onSelect(urls: string[]) {
emit('select', urls);
}
</script>
<template>
<div class="gallery-pick-link-wrap">
<a class="gallery-pick-link" :class="{ disabled }" @click.prevent="openGallery">从图库选择</a>
<ImageGalleryPicker
v-model:open="galleryOpen"
:multiple="multiple"
:max-count="maxCount"
@select="onSelect"
/>
</div>
</template>
<style scoped lang="scss">
.gallery-pick-link-wrap {
margin-top: 8px;
}
.gallery-pick-link {
font-size: 13px;
color: #165dff;
cursor: pointer;
user-select: none;
&:hover {
color: #4080ff;
}
&.disabled {
color: #c9cdd4;
cursor: not-allowed;
pointer-events: none;
}
}
.dark .gallery-pick-link {
color: #60a5fa;
&:hover {
color: #93c5fd;
}
&.disabled {
color: #6b7280;
}
}
</style>

View File

@@ -0,0 +1,200 @@
<script setup lang="ts">
import { ref, watch } from 'vue';
import { Empty, Modal, Pagination, Spin } from 'ant-design-vue';
import type { FileGalleryItem } from '#/api/core/file-gallery';
import { getFileGalleryList } from '#/api/core/file-gallery';
const props = withDefaults(
defineProps<{
open: boolean;
multiple?: boolean;
maxCount?: number;
}>(),
{
multiple: false,
maxCount: 1,
},
);
const emit = defineEmits<{
'update:open': [value: boolean];
select: [urls: string[]];
}>();
const loading = ref(false);
const items = ref<FileGalleryItem[]>([]);
const total = ref(0);
const page = ref(1);
const pageSize = ref(24);
const selected = ref<string[]>([]);
async function load() {
loading.value = true;
try {
const res = await getFileGalleryList({
page: page.value,
page_size: pageSize.value,
pid: 0,
type: 0,
});
const data = (res as any)?.data ?? res;
items.value = data?.items ?? [];
total.value = data?.total ?? 0;
} finally {
loading.value = false;
}
}
watch(
() => props.open,
(val) => {
if (val) {
selected.value = [];
page.value = 1;
load();
}
},
);
function toggleSelect(url: string) {
if (props.multiple) {
const idx = selected.value.indexOf(url);
if (idx >= 0) {
selected.value = selected.value.filter((u) => u !== url);
return;
}
if (selected.value.length >= props.maxCount) {
return;
}
selected.value = [...selected.value, url];
return;
}
selected.value = [url];
}
function isSelected(url: string) {
return selected.value.includes(url);
}
function handleOk() {
if (!selected.value.length) {
return;
}
emit('select', [...selected.value]);
emit('update:open', false);
}
function handleCancel() {
emit('update:open', false);
}
function onPageChange(p: number) {
page.value = p;
load();
}
</script>
<template>
<Modal
:open="open"
title="从图库选择"
width="820px"
:ok-button-props="{ disabled: !selected.length }"
@ok="handleOk"
@cancel="handleCancel"
>
<Spin :spinning="loading">
<div v-if="items.length" class="gallery-grid">
<div
v-for="item in items"
:key="item.id"
class="gallery-card"
:class="{ active: isSelected(item.url) }"
@click="toggleSelect(item.url)"
>
<img :src="item.url" alt="" class="gallery-thumb" />
<div class="gallery-meta">
<div>{{ item.file_size_text || '-' }}</div>
<div>{{ item.created_at || '-' }}</div>
</div>
</div>
</div>
<Empty v-else description="图库暂无图片" />
<div v-if="total > pageSize" class="gallery-pagination">
<Pagination
:current="page"
:page-size="pageSize"
:total="total"
size="small"
show-less-items
@change="onPageChange"
/>
</div>
</Spin>
</Modal>
</template>
<style scoped lang="scss">
@use './picker-card-theme.scss' as picker;
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(118px, 1fr));
gap: 12px;
max-height: 420px;
overflow-y: auto;
padding: 4px;
}
.gallery-card {
@include picker.picker-card-base;
padding: 6px;
&.active {
border-color: #165dff;
background: #f2f7ff;
box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.15);
}
}
.gallery-thumb {
width: 100%;
height: 88px;
object-fit: cover;
border-radius: 6px;
background: #f5f5f5;
}
.gallery-meta {
font-size: 11px;
color: #86909c;
}
.gallery-pagination {
margin-top: 16px;
display: flex;
justify-content: center;
}
.dark {
.gallery-card {
@include picker.picker-card-dark-props;
&.active {
border-color: #60a5fa;
background: #374151;
box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.2);
}
}
.gallery-thumb {
background: #374151;
}
.gallery-meta {
@include picker.picker-text-secondary-dark;
}
}
</style>

View File

@@ -3,7 +3,7 @@ import { computed, ref, watch } from 'vue';
import { Modal, Upload } from 'ant-design-vue';
import type { UploadFile } from 'ant-design-vue';
// 导入上传相关API和工具函数
import GalleryPickLink from '#/components/form/components/gallery-pick-link.vue';
import { uploadFile } from '#/api/core/upload';
import { uploadToOss } from '#/utils/oss-upload';
import {
@@ -13,7 +13,6 @@ import {
import { preferences } from '@vben/preferences';
import { Icon } from '#/components/icon';
// 定义接口
interface FileItem {
uid: string;
name: string;
@@ -21,7 +20,6 @@ interface FileItem {
url: string;
}
// 定义props
interface Props {
modelValue: string[];
multiple?: boolean;
@@ -40,7 +38,6 @@ const emit = defineEmits<{
const fileList = ref<FileItem[]>([]);
// 初始化fileList
const initFileList = () => {
fileList.value = props.modelValue.map((url) => ({
uid: url,
@@ -50,12 +47,14 @@ const initFileList = () => {
}));
};
// 初始化
initFileList();
// 监听外部modelValue变化同步到fileList
watch(() => props.modelValue, (newVal) => {
const currentUrls = fileList.value.filter(file => file.status === 'done').map(file => file.url);
watch(
() => props.modelValue,
(newVal) => {
const currentUrls = fileList.value
.filter((file) => file.status === 'done')
.map((file) => file.url);
if (JSON.stringify(newVal) !== JSON.stringify(currentUrls)) {
fileList.value = newVal.map((url) => ({
uid: url,
@@ -64,9 +63,10 @@ watch(() => props.modelValue, (newVal) => {
url,
}));
}
}, { deep: true });
},
{ deep: true },
);
// 统一通过updateModelValue函数更新modelValue
const updateModelValue = () => {
const urls = fileList.value
.filter((file) => file.status === 'done' && file.url)
@@ -74,36 +74,10 @@ const updateModelValue = () => {
emit('update:modelValue', urls);
};
/**
* 自定义上传请求处理函数
*
* 该函数根据preferences配置的上传方式选择使用OSS直传或后端上传
* - 'direct': OSS直传模式文件直接从浏览器上传到阿里云OSS
* - 'backend': 后端上传模式文件先上传到后端服务器再由后端上传到OSS
*
* 两种上传方式的区别:
* 1. OSS直传direct
* - 优点:减少服务器负载,上传速度更快,用户体验更好
* - 缺点:需要后端提供签名接口,配置相对复杂
* - 实现调用uploadToOss函数直接上传到OSS
*
* 2. 后端上传backend
* - 优点:兼容现有功能,所有上传逻辑由后端统一处理
* - 缺点:增加服务器负载,上传速度相对较慢
* - 实现调用uploadFile API通过后端服务器上传
*
* 兼容性处理:
* - 两种上传方式返回的数据结构保持一致:{ url: string }
* - 确保无论使用哪种方式,组件的行为都是一致的
* - 如果上传失败会更新文件状态为error并调用onError回调
*
* @param options 上传选项包含file、onProgress、onSuccess、onError等
*/
const customRequest = async (options: any) => {
const { file, onProgress, onSuccess, onError } = options;
const actualFile = resolveUploadFile(file as File);
// 创建上传中的文件项用于在UI中显示上传状态
const uploadingFile: FileItem = {
uid: file.uid,
name: file.name,
@@ -111,107 +85,80 @@ const customRequest = async (options: any) => {
url: '',
};
// 将上传中的文件添加到文件列表
fileList.value = [...fileList.value, uploadingFile];
try {
// 从preferences中读取上传方式配置
// uploadMethod可能的值'direct'OSS直传或 'backend'(后端上传)
const uploadMethod = preferences.app.uploadMethod || 'direct';
let res: { url: string };
// 根据配置选择上传方式
if (uploadMethod === 'direct') {
// OSS直传模式文件直接从浏览器上传到OSS
// uploadToOss函数会
// 1. 从后端获取OSS签名信息
// 2. 生成全局唯一的文件名UUID + 时间戳)
// 3. 使用FormData构造POST请求直接上传到OSS
// 4. 处理上传进度和错误
// 5. 返回上传后的文件URL
res = await uploadToOss({
const res =
uploadMethod === 'direct'
? await uploadToOss({
file: actualFile,
onProgress: (percent) => {
// 将OSS上传进度传递给组件
// percent范围0-100表示上传百分比
if (onProgress) {
onProgress(percent);
}
},
});
} else {
// 后端上传模式文件先上传到后端服务器再由后端上传到OSS
// uploadFile函数会
// 1. 将文件发送到后端API/upload/image
// 2. 后端接收文件后上传到OSS
// 3. 返回上传后的文件URL
res = await uploadFile({
file: actualFile,
});
}
})
: await uploadFile({ file: actualFile });
// 上传成功,更新文件状态为完成
// 将上传结果中的URL赋值给文件项
const updatedFileList = fileList.value.map(item =>
item.uid === file.uid
? { ...item, status: 'done' as const, url: res.url }
: item
fileList.value = fileList.value.map((item) =>
item.uid === file.uid ? { ...item, status: 'done' as const, url: res.url } : item,
);
fileList.value = updatedFileList;
// 更新组件的modelValue触发父组件的更新
updateModelValue();
// 调用成功回调,通知上传组件上传已完成
onSuccess(res);
} catch (error) {
// 上传失败,记录错误信息
console.error('上传失败', error);
// 更新文件状态为错误
// 错误状态的文件会在UI中显示错误图标
const updatedFileList = fileList.value.map(item =>
item.uid === file.uid
? { ...item, status: 'error' as const }
: item
fileList.value = fileList.value.map((item) =>
item.uid === file.uid ? { ...item, status: 'error' as const } : item,
);
fileList.value = updatedFileList;
// 调用错误回调,通知上传组件上传失败
onError(error);
}
};
// 处理文件删除
const handleRemove = (file: UploadFile) => {
fileList.value = fileList.value.filter((item) => item.uid !== file.uid);
updateModelValue();
};
// 预览功能
const previewVisible = ref(false);
const previewImage = ref('');
const previewTitle = ref('');
function onGallerySelect(urls: string[]) {
const remain = props.maxCount - fileList.value.length;
const picked = props.multiple ? urls.slice(0, remain) : urls.slice(0, 1);
for (const url of picked) {
if (fileList.value.some((f) => f.url === url)) {
continue;
}
fileList.value = [
...fileList.value,
{
uid: url,
name: url.split('/').pop() || 'file',
status: 'done' as const,
url,
},
];
}
updateModelValue();
}
const handlePreview = async (file: UploadFile) => {
previewImage.value = file.url || '';
previewVisible.value = true;
previewTitle.value = file.name || file.url?.split('/').pop() || '';
};
// 计算是否显示上传按钮
const showUploadButton = computed(() =>
props.multiple
? fileList.value.length < props.maxCount
: fileList.value.length === 0,
props.multiple ? fileList.value.length < props.maxCount : fileList.value.length === 0,
);
const galleryRemainCount = computed(() => Math.max(props.maxCount - fileList.value.length, 0));
</script>
<template>
<div>
<div class="upload-image-wrap">
<Upload
:file-list="fileList"
:before-upload="beforeImageUpload"
@@ -223,23 +170,24 @@ const showUploadButton = computed(() =>
showRemoveIcon: true,
}"
list-type="picture-card"
accept="image/*"
@remove="handleRemove"
@preview="handlePreview"
accept="image/*"
>
<div v-if="showUploadButton" class="upload-button">
<!-- 恢复使用Icon组件 -->
<Icon icon="ant-design:plus-outlined" />
<div class="ant-upload-text">上传图片</div>
</div>
</Upload>
<Modal
v-model:visible="previewVisible"
:title="previewTitle"
footer=""
width="60%"
>
<GalleryPickLink
v-if="showUploadButton"
:multiple="multiple"
:max-count="galleryRemainCount"
@select="onGallerySelect"
/>
<Modal v-model:visible="previewVisible" :title="previewTitle" footer="" width="60%">
<img alt="预览图片" style="width: 100%" :src="previewImage" />
</Modal>
</div>

View File

@@ -286,7 +286,7 @@ export async function uploadToOss(options: UploadOptions): Promise<UploadResult>
// 注意使用后端返回的host确保配置的灵活性
const fileUrl = `${signature.host}/${objectName}`;
registerOssFile({ url: fileUrl, source: 0 }).catch((err) => {
registerOssFile({ url: fileUrl, source: 0, file_size: file.size }).catch((err) => {
console.warn('OSS 文件登记失败:', err);
});

View File

@@ -164,6 +164,19 @@ export const modalFormProps: VbenFormProps = {
label: '简介',
rules: 'required',
},
{
component: 'InputNumber',
componentProps: {
min: 0,
precision: 2,
placeholder: '请输入挂号费',
class: 'w-full',
},
fieldName: 'register_price',
formItemClass: 'col-span-6',
label: '挂号费',
defaultValue: 0,
},
{
component: 'Avatar',
componentProps: {

View File

@@ -0,0 +1,169 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { Page } from '@vben/common-ui';
import { Button, Card, Empty, Modal, Pagination, Spin, message } from 'ant-design-vue';
import type { FileGalleryItem } from '#/api/core/file-gallery';
import {
deleteFileGalleryItem,
getFileGalleryList,
syncFileGalleryFromOss,
} from '#/api/core/file-gallery';
defineOptions({ name: 'FileGallery' });
const loading = ref(false);
const syncing = ref(false);
const items = ref<FileGalleryItem[]>([]);
const total = ref(0);
const page = ref(1);
const pageSize = ref(24);
async function load() {
loading.value = true;
try {
const res = await getFileGalleryList({
page: page.value,
page_size: pageSize.value,
pid: 0,
type: 0,
});
const data = (res as any)?.data ?? res;
items.value = data?.items ?? [];
total.value = data?.total ?? 0;
} finally {
loading.value = false;
}
}
async function handleSync() {
syncing.value = true;
try {
const res = await syncFileGalleryFromOss();
const data = (res as any)?.data ?? res;
message.success(`同步完成:扫描 ${data?.scanned ?? 0} 个,新增 ${data?.added ?? 0} 张,跳过 ${data?.skipped ?? 0}`);
await load();
} finally {
syncing.value = false;
}
}
function handleDelete(item: FileGalleryItem) {
Modal.confirm({
title: '确认删除',
content: '删除后将同时移除 OSS 中的文件,此操作不可恢复,是否继续?',
okText: '删除',
okType: 'danger',
cancelText: '取消',
async onOk() {
await deleteFileGalleryItem(item.id);
message.success('删除成功');
await load();
},
});
}
function onPageChange(p: number) {
page.value = p;
load();
}
load();
</script>
<template>
<Page auto-content-height title="图库管理">
<Card>
<div class="toolbar">
<Button type="primary" :loading="syncing" @click="handleSync">同步 OSS 图片</Button>
<Button :loading="loading" @click="load">刷新</Button>
</div>
<Spin :spinning="loading">
<div v-if="items.length" class="gallery-grid">
<div v-for="item in items" :key="item.id" class="gallery-card">
<img :src="item.url" alt="" class="gallery-thumb" />
<div class="gallery-meta">
<div>{{ item.file_size_text || '-' }}</div>
<div>{{ item.created_at || '-' }}</div>
</div>
<Button danger size="small" block @click="handleDelete(item)">删除</Button>
</div>
</div>
<Empty v-else description="暂无图片,可点击「同步 OSS 图片」导入" />
</Spin>
<div v-if="total > pageSize" class="gallery-pagination">
<Pagination
:current="page"
:page-size="pageSize"
:total="total"
show-size-changer
@change="onPageChange"
/>
</div>
</Card>
</Page>
</template>
<style scoped lang="scss">
@use '#/components/form/components/picker-card-theme.scss' as theme;
.toolbar {
display: flex;
gap: 12px;
margin-bottom: 16px;
}
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 16px;
}
.gallery-card {
display: flex;
flex-direction: column;
gap: 8px;
padding: 10px;
border: 1px solid #e5e6eb;
border-radius: 8px;
background: #fff;
}
.gallery-thumb {
width: 100%;
height: 120px;
object-fit: cover;
border-radius: 6px;
background: #f5f5f5;
}
.gallery-meta {
font-size: 12px;
color: #86909c;
text-align: center;
}
.gallery-pagination {
margin-top: 20px;
display: flex;
justify-content: center;
}
.dark {
.gallery-card {
@include theme.picker-card-dark-props;
}
.gallery-thumb {
background: #374151;
}
.gallery-meta {
@include theme.picker-text-secondary-dark;
}
}
</style>

View File

@@ -3,7 +3,7 @@ import { onMounted, ref } from 'vue';
import { Page } from '@vben/common-ui';
import { Button, Card, Input, InputNumber, message, Radio, Space, Switch, Tag } from 'ant-design-vue';
import type { QuickDiscountOption } from '#/utils/pricePercentAdjust';
@@ -24,6 +24,7 @@ const quickOptions = ref<QuickDiscountOption[]>([
]);
const newQuickName = ref('');
const newQuickValue = ref<number | null>(null);
const inputAutoAuditPass = ref(true);
function parseQuickOptions(raw: unknown): QuickDiscountOption[] {
if (typeof raw === 'string') {
@@ -59,6 +60,9 @@ async function load() {
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';
}
}
} finally {
loading.value = false;
@@ -92,6 +96,10 @@ async function handleSave() {
config_key: 'order_discount_quick_options',
config_value: JSON.stringify(quickOptions.value),
},
{
config_key: 'input_auto_audit_pass',
config_value: inputAutoAuditPass.value ? '1' : '0',
},
]);
message.success('保存成功');
} finally {
@@ -135,5 +143,13 @@ onMounted(load);
<Button type="primary" :loading="saving" @click="handleSave">保存配置</Button>
</Card>
<Card :loading="loading" class="mt-4" title="录入审核配置">
<div class="mb-2 font-medium">诊所/医生信息预填录入后是否自动通过审核</div>
<Switch v-model:checked="inputAutoAuditPass" checked-children="" un-checked-children="" />
<div class="mt-4">
<Button type="primary" :loading="saving" @click="handleSave">保存配置</Button>
</div>
</Card>
</Page>
</template>