| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 |
- <script setup lang="ts">
- import RemoteSelect from '@/libs/v-select-page/RemoteSelect.vue';
- import type { ReportSchemeItemModel } from '@/model';
- import {
- acupointsMethod,
- herbalMedicineUnitMethod,
- medicinesMethod,
- schemeCategoryOptionsMethod,
- } from '@/request/api/dictionary.api';
- import { editSchemeMethod, searchSchemeMethod } from '@/request/api/report.api';
- import { withResolvers } from '@/tools/promise';
- import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons-vue';
- import { useRequest, useWatcher } from 'alova/client';
- import { Form, message as Message, notification as Notification } from 'ant-design-vue';
- import type { Rule } from 'ant-design-vue/es/form';
- import { h } from 'vue';
- const getModel = (item: Partial<ReportSchemeItemModel> = {}): ReportSchemeItemModel => {
- model.id = item.id;
- model.name = item.name;
- model.category = item.category;
- model.type = item.type;
- model.content = item.content?.map(_ => Object.assign({}, _)) ?? [];
- model.descriptions = item.descriptions?.map(_ => Object.assign({}, _)) ?? [];
- };
- const dataValidValidator = (_rule, value: number, callback): Rule['validator'] => {
- return model.content?.filter(t => t.name)?.length || model.descriptions?.filter(t => t?.name)?.length
- ? Promise.resolve()
- : Promise.reject();
- };
- const groupValidatorDescription = (_rule, value: Record<string, string>, callback): Rule['validator'] => {
- if ( value ) return Promise.resolve();
- const index = _rule.field.split('.')[ 1 ];
- const values = [ model.descriptions[ index ]?.name, model.descriptions[ index ]?.description ];
- return values.some(v => !!v) ? Promise.reject() : Promise.resolve();
- };
- const groupValidatorContent = (_rule, value: Record<string, string>, callback): Rule['validator'] => {
- if ( value ) return Promise.resolve();
- const index = _rule.field.split('.')[ 1 ];
- const values = [ model.content[ index ]?.name, model.content[ index ]?.doase ];
- return values.some(v => !!v) ? Promise.reject() : Promise.resolve();
- };
- const props = defineProps<{
- value?: ReportSchemeItemModel,
- reportId?: string;
- }>();
- const emits = defineEmits<{
- 'update:value': [ model: ReportSchemeItemModel ],
- 'destroy': [],
- }>();
- const model = reactive<ReportSchemeItemModel>({
- count: 0,
- });
- const rules = reactive<Record<string, Rule[]>>({
- category: [ { required: true, message: '请选择方案类型' } ],
- count: [],
- });
- const nextDestroy = ref(false);
- const { validateInfos } = Form.useForm(model, rules);
- const { data: medicineUnitOptions, loading: medicineUnitLoading } = useRequest(
- herbalMedicineUnitMethod,
- { initialData: [] },
- );
- const { data: categoryOptions, loading: categoryLoading } = useRequest(
- schemeCategoryOptionsMethod,
- { initialData: [] },
- ).onSuccess(({ data }) => {
- if ( !model.category ) model.category = data[ 0 ]?.value;
- });
- const { data: scheme, loading: schemeLoading, send: search } = useWatcher(
- (keyword) => searchSchemeMethod(keyword, model),
- [ () => model.category ],
- {
- middleware: (_, next) => { if ( model.category ) next(); },
- },
- );
- const { send: submit } = useRequest(
- () => editSchemeMethod(props.reportId, model),
- { immediate: false },
- ).onSuccess(({ data }) => {
- Notification.success({
- message: '操作成功',
- });
- emits('update:value', data);
- if ( nextDestroy.value ) emits('destroy');
- });
- const editableContent = computed(() => [ 'acupoint', 'medicine' ].includes(model.type));
- const appendContent = (data?: Record<string, any>) => {
- if ( !editableContent.value ) return;
- model.content?.push({ id: `custom-${ Date.now() }`, ...data });
- };
- const removeContent = (data: Record<string, any>, index?: number) => {
- if ( !editableContent.value ) return;
- index ??= model.content?.findIndex(t => t.id === data.id);
- if ( index != null && index > -1 ) model.content?.splice(index, 1);
- if ( model.content?.length === 0 ) appendContent();
- };
- const updateContent = (data: Record<string, any> | null, index: number) => {
- const old = model.content[ index ];
- const name = data?.name;
- const doase = data?.doase ?? old?.doase;
- const imgUrl = data?.photo;
- let unit = data?.unit ?? old?.unit;
- if ( model.type === 'medicine' ) unit ??= medicineUnitOptions.value[ 0 ]?.value;
- model.content[ index ] = { ...model.content[ index ], name, imgUrl, doase, unit, type: model.type };
- };
- const appendDescription = (data?: Record<string, any>) => {
- model.descriptions?.push({ id: `custom-${ Date.now() }`, name: '', description: '', ...data });
- };
- const removeDescription = (data: Record<string, any>, index?: number) => {
- index ??= model.descriptions?.findIndex(t => t.id === data.id);
- if ( index != null && index > -1 ) model.descriptions?.splice(index, 1);
- if ( model.descriptions?.length === 0 ) appendDescription();
- };
- const toggleTypeConfirmProps = shallowReactive({
- show: false,
- title: '确定切换?',
- resolve: () => void 0,
- });
- const toggleTypeConfirm = (value: string | null) => {
- const { promise, resolve } = withResolvers<boolean>();
- setTimeout(() => document.documentElement.addEventListener('click', () => resolve(false), { once: true }), 20);
- toggleTypeConfirmProps.resolve = resolve;
- toggleTypeConfirmProps.title = value ? `确定切换数据` : `确定取消数据`;
- toggleTypeConfirmProps.show = true;
- promise.then(() => toggleTypeConfirmProps.show = false);
- return promise;
- };
- const toggleType = async (value) => {
- let toggle = !model.content?.some(t => t.name) || await toggleTypeConfirm(value);
- if ( toggle ) {
- model.type = value;
- model.content = [];
- appendContent();
- }
- };
- onBeforeMount(() => {
- getModel(props.value);
- if ( !model.content?.length ) appendContent();
- if ( !model.descriptions?.length ) appendDescription();
- });
- function onFinishFailed(e) {
- Message.warning(`请补充完整!`);
- }
- function selectSchemeHandle(name: string) {
- const data = scheme.value.find((s) => s.label === name);
- Object.keys(data ?? {}).forEach((key: string) => {
- const value = data[ key ];
- if ( Array.isArray(value) ) {
- model[ key ] = [ ...value ];
- } else if ( value && typeof value === 'object' ) {
- model[ key ] = { ...value };
- } else {
- model[ key ] = value;
- }
- });
- }
- </script>
- <template>
- <a-form
- class="form-wrapper" :model="model" scroll-to-first-error
- @finish="submit()" @finishFailed="onFinishFailed"
- >
- <a-form-item
- label="方案类型" name="category"
- :rules="{ required: true, message: '请选择方案类型' }"
- >
- <a-select v-model:value="model.category" placeholder="方案类型" showSearch
- :options="categoryOptions" :loading="categoryLoading"
- />
- </a-form-item>
- <a-form-item label="方案名称" v-bind="validateInfos.name">
- <a-select
- show-search placeholder="方案名称"
- :options="scheme" :loading="schemeLoading"
- :field-names="{label: 'label', value: 'label'}"
- v-model:value="model.name" @update:value="selectSchemeHandle"
- allow-clear @deselect="model.name = void 0;"
- @search="search" @dropdownVisibleChange="$event && search('');"
- not-found-content="请输入方案名称"
- ></a-select>
- </a-form-item>
- <a-form-item name="name">
- <template #label>
- <div>数据类型</div>
- <a-popconfirm :title="toggleTypeConfirmProps.title" description="确定后将重置【数据】"
- placement="topLeft" :open="toggleTypeConfirmProps.show"
- okText="确定" @confirm="()=>toggleTypeConfirmProps.resolve(true)"
- cancelText="取消" @cancel="()=>toggleTypeConfirmProps.resolve(false)"
- />
- </template>
- <vxe-radio-group :modelValue="model.type" :strict="false" @update:modelValue="toggleType">
- <vxe-radio label="medicine" content="中药"></vxe-radio>
- <vxe-radio label="acupoint" content="穴位"></vxe-radio>
- </vxe-radio-group>
- </a-form-item>
- <!-- 可编辑数据 -->
- <template v-if="editableContent">
- <a-space-compact v-for="(row, index) in model.content" :key="row.id" block>
- <!-- 穴位 -->
- <template v-if="model.type === 'acupoint'">
- <a-form-item
- :label="index ? ' ' : '穴位数据'" :colon="!index"
- :name="['descriptions', index, 'name']" class="w-full"
- >
- <RemoteSelect
- :load="acupointsMethod" key-prop="name" v-model:value="row.name"
- @update="updateContent($event, index)"
- />
- </a-form-item>
- </template>
- <template v-else-if="model.type === 'medicine'">
- <a-form-item
- class="w-80%" :label="index ? ' ' : '中药数据'" :colon="!index"
- :name="['content', index, 'name']"
- :rules="{ validator: groupValidatorContent, message: '请选择中药' }"
- >
- <RemoteSelect
- :load="medicinesMethod" key-prop="name" v-model:value="row.name"
- @update="updateContent($event, index)"
- />
- </a-form-item>
- <a-form-item
- :name="['content', index, 'doase']"
- :rules="{ validator: groupValidatorContent, message: '请输入剂量' }"
- >
- <a-input-number v-model:value="row.doase" :min="0" :max="100" :precision="2" placeholder="剂量" />
- </a-form-item>
- <a-form-item
- class="w-120px"
- :name="['content', index, 'unit']"
- :rules="{ validator: groupValidatorContent, message: '请选择单位' }"
- >
- <a-select v-model:value="row.unit" placeholder="单位" :options="medicineUnitOptions"
- :loading="medicineUnitLoading" allow-clear
- ></a-select>
- </a-form-item>
- </template>
- <span class="m-l-8px">
- <span class="p-b-24px flex items-center h-full">
- <MinusCircleOutlined @click="removeContent(row, index)" />
- </span>
- </span>
- </a-space-compact>
- <a-form-item label=" " :colon="false" class="p-r-22px">
- <a-button type="dashed" block :icon="h(PlusOutlined)" @click="appendContent">
- 添加一条数据
- </a-button>
- </a-form-item>
- </template>
- <!-- 说明 -->
- <a-space-compact v-for="(row, index) in model.descriptions" :key="row.id" block>
- <a-form-item
- :label="index ? ' ' : '说明'" :colon="!index"
- :name="['descriptions', index, 'name']"
- :rules="{ validator: groupValidatorDescription, message: '请输入标题' }"
- >
- <a-input v-model:value.trim="model.descriptions[index].name" placeholder="标题" />
- </a-form-item>
- <a-form-item
- class="w-80%"
- :name="['descriptions', index, 'description']"
- :rules="{ validator: groupValidatorDescription, message: '请输入内容' }"
- >
- <a-textarea v-model:value="row.description" placeholder="内容" :auto-size="{ minRows: 1, maxRows: 10 }" />
- </a-form-item>
- <span class="m-l-8px">
- <span class="p-b-24px flex items-center h-full">
- <MinusCircleOutlined @click="removeDescription(row, index)" />
- </span>
- </span>
- </a-space-compact>
- <a-form-item label=" " :colon="false" class="p-r-22px">
- <a-button type="dashed" block :icon="h(PlusOutlined)" @click="appendDescription">
- 添加一条说明
- </a-button>
- <div></div>
- </a-form-item>
- <a-form-item
- name="count"
- :rules="{ validator: dataValidValidator, message: '数据或者说明至少需要添加一项' }"
- >
- <a-space>
- <a-button type="primary" html-type="submit" @click="nextDestroy=false">保存</a-button>
- <a-button type="default" html-type="submit" @click="nextDestroy=true">保存并关闭</a-button>
- <a-button type="dashed" @click="emits('destroy')">关闭</a-button>
- </a-space>
- </a-form-item>
- </a-form>
- </template>
- <style scoped lang="scss">
- .form-wrapper {
- padding: 12px 24px;
- :deep(.ant-form-item) {
- &.max {
- flex: 1 1 80%;
- }
- .ant-form-item-label {
- width: 80px;
- }
- }
- }
- </style>
|