feat(gxmu): 审核流的审核意见改为必填项

通过/拒绝审核时审核意见为空或仅空白字符将被拦截,提交前去除首尾空格,输入框限制 500 字并显示字数。
This commit is contained in:
2026-11-01 17:06:13 +08:00
parent c81e24c977
commit 3888438694
@@ -15,7 +15,9 @@
<a-tag>{{ moduleName || module || '-' }}</a-tag>
<span>业务ID:</span>
<a-tag>{{ pk ?? '-' }}</a-tag>
<a-button size="small" :loading="loading" @click="loadData">刷新</a-button>
<a-button size="small" :loading="loading" @click="loadData"
>刷新</a-button
>
</a-space>
<div v-if="flowList.length" class="flow-wrap">
@@ -23,7 +25,9 @@
<a-step v-for="item in flowList" :key="item.id">
<template #title>
<div class="flow-title">
<span>{{ item.user?.realName || item.user?.username || '未分配审核人' }}</span>
<span>{{
item.user?.realName || item.user?.username || '未分配审核人'
}}</span>
<a-tag :color="getStatusColor(item.status)">
{{ getStatusText(item.status) }}
</a-tag>
@@ -31,13 +35,27 @@
</template>
<template #description>
<div class="flow-desc">
<div>创建时间:{{ toDateString(item.createTime, 'yyyy-MM-dd HH:mm:ss') }}</div>
<div
>创建时间:{{
toDateString(item.createTime, 'yyyy-MM-dd HH:mm:ss')
}}</div
>
<div v-if="item.content">审核意见:{{ item.content }}</div>
<a-space v-if="canReview(item)" style="margin-top: 12px">
<a-button type="primary" size="small" :loading="submitting" @click="openActionModal('approve', item)">
<a-button
type="primary"
size="small"
:loading="submitting"
@click="openActionModal('approve', item)"
>
通过
</a-button>
<a-button danger size="small" :loading="submitting" @click="openActionModal('reject', item)">
<a-button
danger
size="small"
:loading="submitting"
@click="openActionModal('reject', item)"
>
拒绝
</a-button>
</a-space>
@@ -56,13 +74,20 @@
:confirm-loading="submitting"
@ok="submitReview"
>
<a-form layout="vertical">
<a-form-item label="审核意见(非必填)">
<a-form
ref="actionFormRef"
layout="vertical"
:model="actionForm"
:rules="actionRules"
>
<a-form-item label="审核意见" name="content">
<a-textarea
v-model:value="actionContent"
v-model:value="actionForm.content"
:rows="4"
:maxlength="500"
show-count
allow-clear
placeholder="请输入审核意见"
placeholder="请输入审核意见(必填)"
/>
</a-form-item>
</a-form>
@@ -71,163 +96,187 @@
</template>
<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import { Empty, message } from 'ant-design-vue';
import { toDateString } from 'ele-admin-pro';
import { listReviewList, updateReviewList } from '@/api/gxmu/reviewList';
import type { ReviewList } from '@/api/gxmu/reviewList/model';
import { useUserStore } from '@/store/modules/user';
import { computed, reactive, ref, watch } from 'vue';
import { Empty, message } from 'ant-design-vue';
import type { FormInstance } from 'ant-design-vue/es/form';
import { toDateString } from 'ele-admin-pro';
import { listReviewList, updateReviewList } from '@/api/gxmu/reviewList';
import type { ReviewList } from '@/api/gxmu/reviewList/model';
import { useUserStore } from '@/store/modules/user';
const props = defineProps<{
visible: boolean;
module?: string;
moduleName?: string;
pk?: number;
}>();
const props = defineProps<{
visible: boolean;
module?: string;
moduleName?: string;
pk?: number;
}>();
const emit = defineEmits<{
(e: 'update:visible', visible: boolean): void;
(e: 'done'): void;
}>();
const emit = defineEmits<{
(e: 'update:visible', visible: boolean): void;
(e: 'done'): void;
}>();
const userStore = useUserStore();
const loginUserId = computed(() => Number(userStore.info?.userId || 0));
const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE;
const loading = ref(false);
const submitting = ref(false);
const flowList = ref<ReviewList[]>([]);
const actionVisible = ref(false);
const actionType = ref<'approve' | 'reject'>('approve');
const actionContent = ref('');
const actionTarget = ref<ReviewList | null>(null);
const userStore = useUserStore();
const loginUserId = computed(() => Number(userStore.info?.userId || 0));
const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE;
const loading = ref(false);
const submitting = ref(false);
const flowList = ref<ReviewList[]>([]);
const actionVisible = ref(false);
const actionType = ref<'approve' | 'reject'>('approve');
const actionFormRef = ref<FormInstance | null>(null);
const actionForm = reactive<{ content: string }>({ content: '' });
const actionTarget = ref<ReviewList | null>(null);
const currentStepIndex = computed(() => {
const pendingIndex = flowList.value.findIndex((item) => item.status === 0);
if (pendingIndex >= 0) {
return pendingIndex;
}
return Math.max(flowList.value.length - 1, 0);
});
// 审核意见必填(仅空白字符视为未填写)
const actionRules = {
content: [
{
required: true,
validator: (_rule: unknown, value: string) => {
const text = (value || '').trim();
return text
? Promise.resolve()
: Promise.reject(new Error('请输入审核意见'));
},
trigger: 'blur'
}
]
};
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
const getStatusText = (status?: number) => {
if (status === 1) return '通过';
if (status === 2) return '拒绝';
return '待审核';
};
const getStatusColor = (status?: number) => {
if (status === 1) return 'success';
if (status === 2) return 'error';
return 'default';
};
const canReview = (item: ReviewList) => {
return item.status === 0 && Number(item.userId || 0) === loginUserId.value;
};
const loadData = () => {
if (!props.module || !props.pk) {
flowList.value = [];
return;
}
loading.value = true;
listReviewList({
module: props.module,
pk: props.pk
})
.then((data) => {
flowList.value = [...(data || [])].sort(
(a, b) => Number(b.id || 0) - Number(a.id || 0)
);
})
.catch((e) => {
flowList.value = [];
message.error(e.message);
})
.finally(() => {
loading.value = false;
});
};
const openActionModal = (type: 'approve' | 'reject', item: ReviewList) => {
if (!canReview(item)) {
message.error('当前记录不是由你审核');
return;
}
actionType.value = type;
actionTarget.value = item;
actionContent.value = '';
actionVisible.value = true;
};
const submitReview = () => {
if (!actionTarget.value?.id) {
actionVisible.value = false;
return;
}
if (!canReview(actionTarget.value)) {
actionVisible.value = false;
message.error('当前记录不是由你审核');
return;
}
submitting.value = true;
updateReviewList({
id: actionTarget.value.id,
status: actionType.value === 'approve' ? 1 : 2,
module: actionTarget.value.module,
pk: actionTarget.value.pk,
sortNumber: actionTarget.value.sortNumber,
content: actionContent.value || undefined
})
.then((msg) => {
message.success(msg);
actionVisible.value = false;
emit('done');
loadData();
})
.catch((e) => {
message.error(e.message);
})
.finally(() => {
submitting.value = false;
});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
loadData();
} else {
flowList.value = [];
actionVisible.value = false;
actionTarget.value = null;
actionContent.value = '';
const currentStepIndex = computed(() => {
const pendingIndex = flowList.value.findIndex((item) => item.status === 0);
if (pendingIndex >= 0) {
return pendingIndex;
}
},
{ immediate: true }
);
return Math.max(flowList.value.length - 1, 0);
});
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
const getStatusText = (status?: number) => {
if (status === 1) return '通过';
if (status === 2) return '拒绝';
return '待审核';
};
const getStatusColor = (status?: number) => {
if (status === 1) return 'success';
if (status === 2) return 'error';
return 'default';
};
const canReview = (item: ReviewList) => {
return item.status === 0 && Number(item.userId || 0) === loginUserId.value;
};
const loadData = () => {
if (!props.module || !props.pk) {
flowList.value = [];
return;
}
loading.value = true;
listReviewList({
module: props.module,
pk: props.pk
})
.then((data) => {
flowList.value = [...(data || [])].sort(
(a, b) => Number(b.id || 0) - Number(a.id || 0)
);
})
.catch((e) => {
flowList.value = [];
message.error(e.message);
})
.finally(() => {
loading.value = false;
});
};
const openActionModal = (type: 'approve' | 'reject', item: ReviewList) => {
if (!canReview(item)) {
message.error('当前记录不是由你审核');
return;
}
actionType.value = type;
actionTarget.value = item;
actionForm.content = '';
actionVisible.value = true;
};
const submitReview = () => {
const target = actionTarget.value;
if (!target?.id) {
actionVisible.value = false;
return;
}
if (!canReview(target)) {
actionVisible.value = false;
message.error('当前记录不是由你审核');
return;
}
actionFormRef.value
?.validate()
.then(() => {
submitting.value = true;
return updateReviewList({
id: target.id,
status: actionType.value === 'approve' ? 1 : 2,
module: target.module,
pk: target.pk,
sortNumber: target.sortNumber,
content: actionForm.content.trim()
})
.then((msg) => {
message.success(msg);
actionVisible.value = false;
emit('done');
loadData();
})
.catch((e) => {
message.error(e.message);
})
.finally(() => {
submitting.value = false;
});
})
.catch(() => {});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
loadData();
} else {
flowList.value = [];
actionVisible.value = false;
actionTarget.value = null;
actionForm.content = '';
}
},
{ immediate: true }
);
</script>
<style lang="less" scoped>
.flow-wrap {
max-height: 560px;
overflow-y: auto;
padding-right: 8px;
}
.flow-wrap {
max-height: 560px;
overflow-y: auto;
padding-right: 8px;
}
.flow-title {
display: flex;
align-items: center;
gap: 8px;
}
.flow-title {
display: flex;
align-items: center;
gap: 8px;
}
.flow-desc {
color: rgba(0, 0, 0, 0.65);
line-height: 1.8;
}
.flow-desc {
color: rgba(0, 0, 0, 0.65);
line-height: 1.8;
}
</style>