liu
2 天以前 fe2d6627b8f1002af7a31b08f6f43bec034d04ee
src/views/wxcp/activity/modules/form.vue
@@ -11,12 +11,14 @@
import { useVbenForm } from '#/adapter/form';
import { toAccessibleFileUrl } from '#/utils';
import {
  ACTIVITY_TYPE,
  createActivity,
  getActivity,
  updateActivity,
} from '#/api/wxcp/activity';
import { useActivityFormSchema } from '../data';
import { FIELD_TYPE_OPTIONS, useActivityFormSchema } from '../data';
import FieldEditor from './field-editor.vue';
defineOptions({ name: 'WxCpActivityForm' });
@@ -25,6 +27,23 @@
const formType = ref<ActivityFormType>('create');
const id = ref<number>();
const submitting = ref(false);
/** 当前活动模板类型,用于控制采集字段编辑器的显隐 */
const activityType = ref<number>(ACTIVITY_TYPE.FORM);
/** 数据采集模板的自定义字段(表单项之外单独维护) */
const fields = ref<WxCpActivityApi.Field[]>([]);
const showFieldEditor = computed(
  () => activityType.value === ACTIVITY_TYPE.FORM && formType.value !== 'detail',
);
function fieldTypeLabel(type?: string) {
  return (
    FIELD_TYPE_OPTIONS.find((item) => item.value === type)?.label ??
    type ??
    '-'
  );
}
const isEdit = computed(() => formType.value !== 'detail');
const title = computed(() => {
@@ -43,19 +62,22 @@
  schema: [],
  showDefaultActions: false,
  wrapperClass: 'grid-cols-2',
  handleValuesChange: (values) => {
    activityType.value =
      (values.activityType as number) ?? ACTIVITY_TYPE.FORM;
  },
});
/**
 * 把活动详情映射成表单值
 * 把附件列表转成上传组件能识别的文件对象
 *
 * 封面在后端不落业务表字段,而是存在附件中间表里,
 * 回显时要把 attachmentList 转成上传组件能识别的文件对象。
 * 回显对象必须带 response:组件设了 resultField 后只认 item.response,
 * 不带的话「进编辑页不动图直接保存」会走 URL 分支 → NaN → [] → 图被清空
 */
function activityToForm(
  activity: WxCpActivityApi.Activity,
): Record<string, unknown> {
  const form: Record<string, unknown> = { ...activity };
  form.blobIds = (activity.attachmentList ?? []).map((item) => {
function attachmentsToUploadFiles(
  list: WxCpActivityApi.SignupAttachment[] | undefined,
) {
  return (list ?? []).map((item) => {
    const name = item.name || item.originalFilename || '';
    const url = toAccessibleFileUrl(item.url || item.previewURL);
    return {
@@ -64,11 +86,26 @@
      url,
      status: 'done',
      id: item.id,
      // 回显对象也必须带 response:组件设了 resultField 后只认 item.response,
      // 不带的话「进编辑页不动图直接保存」会走 URL 分支 → NaN → [] → 封面被清空
      response: { id: item.id, name, url },
    };
  });
}
/**
 * 把活动详情映射成表单值
 *
 * 封面与详情图片在后端都不落业务表字段,而是存在附件中间表里(两套 recordType),
 * 回显时分别把 attachmentList / detailAttachmentList 转成上传组件的文件对象。
 */
function activityToForm(
  activity: WxCpActivityApi.Activity,
): Record<string, unknown> {
  const form: Record<string, unknown> = { ...activity };
  form.blobIds = attachmentsToUploadFiles(activity.attachmentList);
  form.detailBlobIds = attachmentsToUploadFiles(activity.detailAttachmentList);
  // 图片通知 / 附件通知的素材走各自的附件关系,回显时同样转成上传组件对象
  form.imageBlobIds = attachmentsToUploadFiles(activity.imageAttachmentList);
  form.fileBlobIds = attachmentsToUploadFiles(activity.fileAttachmentList);
  return form;
}
@@ -108,12 +145,26 @@
      modalApi.lock();
      try {
        const detail = await getActivity(data.id);
        activityType.value = detail.activityType ?? ACTIVITY_TYPE.FORM;
        fields.value = (detail.fieldList ?? []).map((item) => ({ ...item }));
        await formApi.setValues(activityToForm(detail));
      } finally {
        modalApi.unlock();
      }
    } else {
      await formApi.resetForm();
      activityType.value = ACTIVITY_TYPE.FORM;
      fields.value = [];
      // 默认给「数据采集/评价」,与改造前的报名活动一致
      await formApi.setValues(
        {
          activityType: ACTIVITY_TYPE.FORM,
          ratingEnabled: false,
          ratingMax: 5,
          signupLimit: 0,
        },
        false,
      );
    }
  },
});
@@ -126,9 +177,15 @@
  submitting.value = true;
  try {
    const data = (await formApi.getValues()) as Record<string, unknown>;
    const type = data.activityType ?? ACTIVITY_TYPE.FORM;
    const payload = {
      ...data,
      blobIds: normalizeBlobIds(data.blobIds),
      detailBlobIds: normalizeBlobIds(data.detailBlobIds),
      imageBlobIds: normalizeBlobIds(data.imageBlobIds),
      fileBlobIds: normalizeBlobIds(data.fileBlobIds),
      // 只有数据采集模板提交字段定义;其它模板不传,避免误清空
      fieldList: type === ACTIVITY_TYPE.FORM ? fields.value : undefined,
    } as WxCpActivityApi.Activity;
    if (id.value) {
      await updateActivity({ ...payload, id: id.value });
@@ -151,6 +208,28 @@
  <Modal :title="title" class="w-3/5">
    <Form class="mx-4" />
    <div v-if="showFieldEditor" class="mx-4 mt-2">
      <FieldEditor v-model="fields" />
    </div>
    <div
      v-else-if="
        formType === 'detail' &&
        activityType === ACTIVITY_TYPE.FORM &&
        fields.length
      "
      class="mx-4 mt-2"
    >
      <div class="mb-2 text-sm font-medium">采集字段</div>
      <div
        v-for="(item, index) in fields"
        :key="index"
        class="text-sm text-gray-600 dark:text-gray-400"
      >
        · {{ item.fieldLabel }}({{ fieldTypeLabel(item.fieldType) }})
        <span v-if="item.required" class="text-red-500">*</span>
      </div>
    </div>
    <template #footer>
      <div class="flex justify-end gap-2">
        <Button @click="modalApi.close()">取消</Button>