| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558 |
- <template>
- <div class="about main-background main-background-type0">
- <div v-if="!isInMiniProgram" class="nav-placeholder" />
- <section class="main-section large">
- <div class="content">
- <div class="title left-right">
- <a-button :icon="h(ArrowLeftOutlined)" @click="router.back()">返回</a-button>
- <h2>自查评估表审核</h2>
- <div class="w-20" />
- </div>
- <a-spin :spinning="loader.loading.value">
- <div
- v-if="loader.loading.value" class="h-50"
- />
- <a-result
- v-else-if="!currentForm"
- status="warning"
- title="无法加载评估表"
- sub-title="请从管理员列表进入,并确认自查表 ID 与传承人用户 ID 正确。"
- />
- <div v-else>
- <a-alert
- type="info"
- show-icon
- class="mb-4"
- :message="progressHint"
- />
- <SelfAssessmentFormDisplay
- :current-form="(currentForm as SelfAssessmentDetail)"
- :check-item-list="(checkItemList as CheckItemInfo[])"
- :current-form-check-items="(currentFormCheckItems as SelfAssessmentCheckItemAnswer[])"
- :readonly="true"
- />
- <a-divider />
- <a-card title="审核提交" size="small" class="review-submit-card" :class="{ collapsed: reviewCardCollapsed }">
- <template #extra>
- <a-button
- size="small"
- type="text"
- :icon="h(FileTextOutlined)"
- @click="goToAssessmentProtocol"
- >
- 传承协议审核
- </a-button>
- <a-button
- size="small"
- type="text"
- :icon="h(reviewCardCollapsed ? UpOutlined : DownOutlined)"
- @click="reviewCardCollapsed = !reviewCardCollapsed"
- >
- {{ reviewCardCollapsed ? '展开' : '收起' }}
- </a-button>
- </template>
- <a-alert
- v-if="!canSubmitReview"
- type="warning"
- show-icon
- class="mb-4"
- message="当前账号用户组无权在此环节审核"
- />
- <a-alert
- v-else-if="currentForm.rejectType > 0"
- type="warning"
- show-icon
- class="mb-4"
- :message="`${getRejectTypeText(currentForm.rejectType)}已退回: ${currentForm.rejectReason}`"
- />
- <a-form v-else layout="vertical" size="middle">
-
- <div class="flex flex-col md:flex-row lg:flex-row w-full gap-3 py-3">
- <div
- v-for="{ gid, def } in displayFieldDefs"
- :key="gid"
- class="flex flex-col flex-1"
- >
- <div class="flex flex-row gap-3">
- <a-form-item :required="gid === currentUserGroupId" :label="`${def.label} — 审核意见`">
- <a-select
- :value="reviewFields[gid]?.opinion"
- allow-clear
- placeholder="请选择审核意见"
- :options="opinionSelectOptions"
- @update:value="(v: number | null) => reviewFields[gid].opinion = v"
- />
- </a-form-item>
- <a-form-item :label="`${def.label} — 评分`">
- <a-input-number
- :value="reviewFields[gid]?.points"
- :min="0"
- :max="100"
- class="w-full"
- placeholder="请填写分数"
- @update:value="(v: number | null) => reviewFields[gid].points = v"
- />
- </a-form-item>
- </div>
- <a-form-item :label="`${def.label} — 修改意见`">
- <a-textarea
- :value="reviewFields[gid]?.reason"
- @update:value="(v: string | null) => reviewFields[gid].reason = v"
- allow-clear
- placeholder="请输入修改意见/回退原因"
- :rows="5"
- :maxlength="500"
- show-count
- />
- </a-form-item>
- <a-alert
- v-if="alreadySubmitted && gid === currentUserGroupId"
- type="success"
- show-icon
- class="mb-4"
- message="当前角色已提交意见"
- />
- </div>
- <div v-if="displayFieldDefs.length === 0" class="flex flex-col flex-1">
- <a-form-item required :label="isNoProgressChange ? `提交意见:${reviewLevelLabel}` : `审核通过:${reviewLevelLabel}`">
- <a-select
- :value="reviewFields[currentUserGroupId]?.opinion"
- allow-clear
- placeholder="请选择审核意见"
- :options="opinionSelectOptions"
- :disabled="alreadySubmitted"
- @update:value="(v: number | null) => reviewFields[currentUserGroupId].opinion = v"
- />
- </a-form-item>
- <a-form-item label="评分">
- <a-input-number
- :value="reviewFields[currentUserGroupId]?.points"
- :min="0"
- :max="100"
- class="w-full"
- placeholder="请填写分数"
- :disabled="alreadySubmitted"
- @update:value="(v: number | null) => reviewFields[currentUserGroupId].points = v"
- />
- </a-form-item>
- </div>
- <div v-if="displayFieldDefs.length <= 1 && (!isNoProgressChange || reviewProgressInfo?.canReject)" class="flex flex-col flex-1">
- <a-form-item required label="不通过回退:回退原因">
- <a-textarea
- :value="reviewFields[currentUserGroupId].reason"
- @update:value="(v: string | null) => reviewFields[currentUserGroupId].reason = v"
- allow-clear
- placeholder="请输入回退原因"
- :rows="5"
- :maxlength="500"
- show-count
- />
- </a-form-item>
- </div>
- </div>
- <div class="flex row w-full gap-3">
- <template v-if="!alreadySubmitted || isMulitRole">
- <div class="flex flex-col flex-1">
- <a-button
- block
- type="primary"
- :loading="submitLoading"
- :disabled="!canSubmitReview"
- @click="submitReview"
- >
- {{ isNoProgressChange ? '提交意见' : '通过审核' }}
- </a-button>
- </div>
- <div v-if="isMulitRole" class="flex flex-col flex-1">
- <a-button
- block
- type="primary"
- :loading="submitLoading"
- :disabled="!canSubmitReview"
- @click="saveReasions"
- >
- 保存意见
- </a-button>
- </div>
- <div v-if="reviewProgressInfo?.canReject" class="flex flex-col flex-1">
- <a-button
- block
- danger
- :loading="rejectLoading"
- :disabled="!canSubmitReview"
- @click="submitReject"
- >
- 回退
- </a-button>
- </div>
- </template>
- </div>
- </a-form>
- </a-card>
- </div>
- </a-spin>
- </div>
- </section>
- </div>
- </template>
- <script setup lang="ts">
- import { computed, h, onMounted, ref, watch } from 'vue';
- import { useRoute, useRouter } from 'vue-router';
- import { message, Modal } from 'ant-design-vue';
- import { RequestApiError } from '@imengyu/imengyu-utils';
- import { ArrowLeftOutlined, UpOutlined, DownOutlined, FileTextOutlined } from '@ant-design/icons-vue';
- import AssessmentContentApi, {
- type CheckItemInfo,
- getRejectTypeText,
- type SelfAssessmentCheckItemAnswer,
- SelfAssessmentDetail,
- } from '@/api/collect/AssessmentContent';
- import SelfAssessmentFormDisplay from './components/SelfAssessmentFormDisplay.vue';
- import { useSimpleDataLoader } from '@/composeables/useSimpleDataLoader';
- import { useAuthStore } from '@/stores/auth';
- import { useMemorizeVar } from '@/composeables/useMemorizeVar';
- import { GROUP_TO_REVIEW_PROGRESS, GROUP_DISTRICT_ST, GROUP_DISTRICT_ND, GROUP_DISTRICT_RD, GROUP_DISTRICT_TH, GROUP_DISTRICT_FI, PROGRESS_SUBMIT_LABELS, GROUP_FIELD_MAP, type GroupFieldDef } from './composeables/GroupData.ts';
- import { isInMiniProgram } from '@/composeables/MiniProgramIng.ts';
- import { useReview } from './composeables/Review.ts';
- function formatErr(e: unknown): string {
- if (e instanceof RequestApiError)
- return e.errorMessage;
- if (e instanceof Error)
- return e.message;
- return String(e);
- }
- const router = useRouter();
- const route = useRoute();
- const authStore = useAuthStore();
- const currentUserGroups = computed(() => authStore.userInfo?.adminGroup || []);
- const queryFormId = computed(() => Number(route.query.id) || 0);
- const queryUserId = computed(() => Number(route.query.userId) || 0);
- /** 列表当前进度(与管理员列表一致) */
- const listProgress = computed(() => {
- const p = Number(route.query.progress);
- return Number.isFinite(p) ? p : NaN;
- });
- const currentForm = ref<SelfAssessmentDetail | null>(null);
- const currentFormCheckItems = ref([] as SelfAssessmentCheckItemAnswer[]);
- const checkItemList = ref([] as CheckItemInfo[]);
- const { variable: reviewCardCollapsed } = useMemorizeVar<boolean>('evaluation-form-review-card-collapsed', false);
- const submitLoading = ref(false);
- const rejectLoading = ref(false);
- const rejectReason = ref<string | null>(null);
- interface ReviewFieldState {
- opinion: number | null;
- points: number | null;
- reason: string|null;
- }
- const reviewFields = ref<Record<number, ReviewFieldState>>({});
- const displayGroupIds = computed(() => {
- const info = reviewProgressInfo.value;
- if (!info) return [currentUserGroupId.value];
- return info.displayGroup ?? [currentUserGroupId.value];
- });
- const displayFieldDefs = computed(() => {
- return displayGroupIds.value
- .map((gid) => ({ gid, def: GROUP_FIELD_MAP[gid] }))
- .filter((item): item is { gid: number; def: GroupFieldDef } => !!item.def);
- });
- const isMulitRole = computed(() => displayFieldDefs.value.length > 1);
- const opinionSelectOptions = [
- { label: '请选择', value: 0 },
- { label: '优秀', value: 1 },
- { label: '合格', value: 2 },
- { label: '不合格', value: 3 },
- { label: '丧失传承能力', value: 4 },
- { label: '取消资格', value: 5 },
- ];
- const currentProgress = computed(() => currentForm.value?.progress ?? 0);
- const { reviewProgressInfo, canSubmitReview, reviewLevelLabel, progressHint, isNoProgressChange } = useReview(currentProgress);
- const currentUserGroupId = computed(() => {
- const groups = [...currentUserGroups.value].sort((a, b) => b.id - a.id);
- return groups.find((g) => GROUP_TO_REVIEW_PROGRESS[g.id])?.id ?? 0;
- });
- /** 当前子角色是否已提交过意见(不可重复提交) */
- const alreadySubmitted = computed(() => {
- const f = currentForm.value;
- if (!f) return false;
- const gid = currentUserGroupId.value;
- if (gid === GROUP_DISTRICT_ST) return (f.districtSt ?? 0) > 0;
- if (gid === GROUP_DISTRICT_ND) return (f.districtNd ?? 0) > 0;
- if (gid === GROUP_DISTRICT_FI) return (f.districtFi ?? 0) > 0;
- return false;
- });
- function levelTitleFromForm(f: SelfAssessmentDetail) {
- if (f.level === 23) return '国家级';
- if (f.level === 24) return '省级';
- if (f.level === 25) return '市级';
- return '国家级';
- }
- function loadEditorContent(f: SelfAssessmentDetail) {
- if (typeof f.content !== 'object' || f.content === null)
- f.content = {};
- f.content.title = `传承人填写${f.year}年1月1日至${f.year}年12月31日${levelTitleFromForm(f)}非遗传承人义务履行和传承补助经费使用情况等,不超过1000字,如未履行职责请进行说明。参考提纲如下:`;
- for (let i = 0; i < 8; i++) {
- if (typeof f.content[`item${i}`] !== 'string')
- f.content[`item${i}`] = '';
- }
- }
- async function loadCheckItems(f: SelfAssessmentDetail) {
- const { top } = await AssessmentContentApi.getCheckItems(Number(f.level));
- checkItemList.value = top as CheckItemInfo[];
- currentFormCheckItems.value = [...f.checkItems] as SelfAssessmentCheckItemAnswer[];
- }
- function applyPrefillFromDetail(f: SelfAssessmentDetail) {
- rejectReason.value = f.rejectReason;
- const fields: Record<number, ReviewFieldState> = {};
- for (const { gid, def } of displayFieldDefs.value) {
- fields[gid] = {
- opinion: (f as any)[def.opinionField] ?? null,
- points: (f as any)[def.pointsField] || null,
- reason: f.reasons?.[gid] || null,
- };
- }
- // fallback for non-displayGroup roles (progress-based)
- if (displayFieldDefs.value.length === 0) {
- const gid = currentUserGroupId.value;
- const t = reviewProgressInfo.value?.target;
- if (t === 2) fields[gid] = { opinion: f.ichUnit ?? null, points: f.unitPoints || null, reason: f.rejectReason || null };
- else if (t === 3) fields[gid] = { opinion: f.county ?? null, points: f.countyPoints || null, reason: f.rejectReason || null };
- else if (t === 4) fields[gid] = { opinion: f.district ?? null, points: f.districtPoints || null, reason: f.rejectReason || null };
- else if (t === 5) fields[gid] = { opinion: f.province ?? null, points: f.provincePoints || null, reason: f.rejectReason || null };
- }
- reviewFields.value = fields;
- }
- const loader = useSimpleDataLoader(async () => {
- const id = queryFormId.value;
- const uid = queryUserId.value;
- if (!id || !uid) {
- currentForm.value = null;
- return null;
- }
- const detail = await AssessmentContentApi.getSelfAssessmentDetail(id, uid);
- currentForm.value = detail;
- loadEditorContent(detail);
- await loadCheckItems(detail);
- applyPrefillFromDetail(detail);
- return detail;
- }, { immediate: false });
- watch(reviewProgressInfo, () => {
- const f = currentForm.value;
- if (f)
- applyPrefillFromDetail(f as SelfAssessmentDetail);
- });
- watch(
- () => [queryFormId.value, queryUserId.value, listProgress.value] as const,
- () => {
- loader.load();
- },
- );
- onMounted(() => {
- loader.load();
- setTimeout(() => {
- console.log('currentUserGroups', currentUserGroups.value, 'currentUserGroupId', currentUserGroupId.value);
- console.log('reviewProgressInfo', reviewProgressInfo.value, 'canSubmitReview', canSubmitReview.value);
- console.log('isNoProgressChange', isNoProgressChange.value);
- console.log(reviewFields.value);
- }, 800);
- });
- async function goToAssessmentProtocol() {
- const list = await AssessmentContentApi.getUserAgreementList({
- year: new Date().getFullYear(),
- page: 1,
- pageSize: 10,
- keywords: currentForm.value?.inheritor || undefined
- });
- if (list.data.length === 0) {
- message.warning('无可审核的传承协议');
- return;
- }
- const item = list.data[0];
- const path = router.resolve({
- name: 'CollectAgreementSignReview',
- query: {
- id: String(item.agreementId),
- userId: String(item.userId),
- ...(item.progress != null ? { progress: String(item.progress) } : {}),
- },
- });
- window.open(path.href, '_blank');
- }
- async function saveReasions() {
- if (!isMulitRole.value) {
- message.warning('非多角色账号');
- return;
- }
- const f = currentForm.value;
- if (!f?.id) {
- message.warning('缺少自查表 ID');
- return;
- }
-
- const reasons = {} as Record<number, string>;
- for (const { gid, def } of displayFieldDefs.value) {
- reasons[gid] = reviewFields.value[gid].reason || '';
- }
- try {
- submitLoading.value = true;
- await AssessmentContentApi.reviewSelfAssessment({
- id: f.id,
- progress: f.progress,
- reasons
- });
- message.success('保存成功');
- } catch (e) {
- Modal.error({ title: '保存失败', content: formatErr(e) });
- } finally {
- submitLoading.value = false;
- }
- }
- async function submitReject() {
- const f = currentForm.value;
- if (!f?.id) {
- message.warning('缺少自查表 ID');
- return;
- }
- if (!canSubmitReview.value) {
- message.warning('当前账号用户组无权回退');
- return;
- }
- if (isMulitRole.value) {
- let result = '';
- for (const { gid, def } of displayFieldDefs.value) {
- if (gid === currentUserGroupId.value) {
- result = reviewFields.value[gid].reason || '';
- break;
- }
- }
- if (!result) {
- Modal.error({
- title: '提示',
- content: '未找到对应id的回退原因',
- });
- return;
- }
- rejectReason.value = result
- }
- const reason = (rejectReason.value ?? '').trim();
- if (!reason) {
- message.warning('请输入回退原因');
- return;
- }
- try {
- rejectLoading.value = true;
- await AssessmentContentApi.reviewSelfAssessment({
- id: f.id,
- progress: f.progress,
- rejectType: reviewProgressInfo.value.rejectTarget,
- rejectReason: reason,
- });
- message.success('已回退');
- loader.load();
- } catch (e) {
- Modal.error({ title: '回退失败', content: formatErr(e) });
- } finally {
- rejectLoading.value = false;
- }
- }
- async function submitReview() {
- const f = currentForm.value;
- if (!f?.id) {
- message.warning('缺少自查表 ID');
- return;
- }
- if (!canSubmitReview.value) {
- message.warning('当前账号用户组无权提交审核');
- return;
- }
- const gid = currentUserGroupId.value;
- const myField = reviewFields.value[gid];
- if (myField?.opinion == null || myField.opinion === 0) {
- message.warning('请选择审核意见');
- return;
- }
- if (myField?.points == null) {
- message.warning('请输入审核评分');
- return;
- }
- const noProgressChange = isNoProgressChange.value;
- const base: Record<string, any> = { id: f.id, progress: noProgressChange ? f.progress : reviewProgressInfo.value.target };
- // Collect all fields from the display group
- for (const { gid: fieldGid, def } of displayFieldDefs.value) {
- const state = reviewFields.value[fieldGid];
- if (state?.opinion != null && state.opinion > 0) {
- base[def.opinionField] = state.opinion;
- }
- if (state?.points != null) {
- base[def.pointsField] = state.points;
- }
- }
- // Fallback for non-displayGroup (progress-based) roles
- if (displayFieldDefs.value.length === 0) {
- const op = myField.opinion;
- const points = myField.points;
- if (base.progress === 2) { base.ichUnit = op; base.unitPoints = points; }
- else if (base.progress === 3) { base.county = op; base.countyPoints = points; }
- else if (base.progress === 4) { base.district = op; base.districtPoints = points; }
- else { base.province = op; base.provincePoints = points; }
- }
- try {
- submitLoading.value = true;
- await AssessmentContentApi.reviewSelfAssessment(base as any);
- message.success(noProgressChange ? '意见提交成功' : '审核提交成功');
- loader.load();
- } catch (e) {
- Modal.error({ title: '审核提交失败', content: formatErr(e) });
- } finally {
- submitLoading.value = false;
- }
- }
- </script>
- <style lang="scss" scoped>
- .review-submit-card {
- position: sticky;
- bottom: 0;
- z-index: 10;
- box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
- :deep(.ant-form-item) {
- margin-bottom: 0!important;
- }
- &.collapsed :deep(.ant-card-body) {
- display: none;
- }
- }
- </style>
|