diff --git a/frontend/src/views/modules/TemplateDownloadLogPage.vue b/frontend/src/views/modules/TemplateDownloadLogPage.vue
index d8b00fc..3a1ce44 100644
--- a/frontend/src/views/modules/TemplateDownloadLogPage.vue
+++ b/frontend/src/views/modules/TemplateDownloadLogPage.vue
@@ -16,16 +16,7 @@
/>
-
-
-
-
-
-
-
-
-
-
+
查询
@@ -47,23 +38,19 @@
-
+
{{ formatEffectiveTime(row) }}
-
-
- {{ row.watermarkEnabled ? "开启" : "关闭" }}
-
-
-
+
+
-
+
- 查看详情
+ 查看详情
{{ canReadAllLogs ? "查看下载日志" : "查看我的下载日志" }}
@@ -76,15 +63,7 @@
>
下载
-
- 水印下载
-
+
@@ -123,12 +102,7 @@
-
-
-
-
-
-
+
@@ -157,11 +131,7 @@
-
-
- {{ getDownloadTypeLabel(row.downloadType) }}
-
-
+
@@ -170,7 +140,7 @@
{{ `用户#${row.userId}` }}
-
+
@@ -201,7 +171,7 @@
{{ getScopeTypeLabel(detailRow.scopeType) }}
V{{ detailRow.currentVersionNo || 1 }}
{{ formatEffectiveTime(detailRow) }}
- {{ detailRow.watermarkEnabled ? "开启" : "关闭" }}
+
{{ detailRow.downloadRateLimitPerHour ?? "-" }}
{{ detailRow.updatedAt || "-" }}
@@ -216,14 +186,6 @@
下载
-
- 水印下载
-
@@ -261,11 +223,7 @@
-
-
- {{ getDownloadTypeLabel(row.downloadType) }}
-
-
+
@@ -274,7 +232,7 @@
{{ `用户#${row.userId}` }}
-
+
@@ -291,11 +249,11 @@ import { ElMessage, ElMessageBox } from "element-plus";
import PageContainer from "../../components/PageContainer.vue";
import {
downloadTemplate,
- downloadTemplateWatermark,
- fetchTemplateDownloadLogs,
+
+ fetchTemplateViewList,
fetchTemplateTypeOptions,
+ fetchTemplateDownloadLogs,
fetchTemplateVersions,
- fetchTemplates,
} from "../../api/modules";
import { PERMS } from "../../constants/permissions";
import { DRAWER_SIZE } from "../../constants/ui";
@@ -323,6 +281,7 @@ const authStore = useAuthStore();
const route = useRoute();
const canRead = computed(() => authStore.hasPermission(PERMS.template.read));
+const canDetailRead = computed(() => authStore.hasPermission(PERMS.template.detailRead));
const canDownload = computed(() => authStore.hasPermission(PERMS.template.download));
const canReadAllLogs = computed(() => authStore.hasPermission(PERMS.template.downloadLogReadAll));
@@ -345,12 +304,7 @@ const detailDownloadRows = ref([]);
const templateTypeOptions = ref>([]);
-const templateStatusOptions = [
- { label: "草稿", value: "DRAFT" },
- { label: "已发布", value: "PUBLISHED" },
- { label: "已停用", value: "DISABLED" },
- { label: "已归档", value: "ARCHIVED" },
-];
+
const scopeTypeOptions = [
{ label: "全部", value: "ALL" },
@@ -358,16 +312,9 @@ const scopeTypeOptions = [
{ label: "会议级", value: "MEETING" },
];
-const watermarkFilterOptions = [
- { label: "开启", value: "true" },
- { label: "关闭", value: "false" },
-];
-
const templateQuery = ref({
templateName: "",
templateType: "" as TemplateTypeCode | "",
- status: "",
- watermarkEnabled: "" as "" | "true" | "false",
});
const logQuery = ref({
@@ -376,7 +323,6 @@ const logQuery = ref({
userId: undefined as number | undefined,
userKeyword: "",
versionNo: undefined as number | undefined,
- downloadType: "" as "" | "NORMAL" | "WATERMARK",
ip: "",
dateRange: [] as string[],
});
@@ -409,7 +355,7 @@ const getTemplateTypeLabel = (code: string) =>
const getScopeTypeLabel = (code: ScopeTypeCode | string) =>
scopeTypeOptions.find((item) => item.value === code)?.label || code || "-";
-const getDownloadTypeLabel = (value?: unknown) => (String(value ?? "").trim() === "WATERMARK" ? "水印下载" : "普通下载");
+
const statusFormatter = (_row: any, _column: any, value: unknown) => getStatusLabel(value);
const bizSceneFormatter = (_row: any, _column: any, value: unknown) => getBizSceneLabel(value);
@@ -425,7 +371,6 @@ const formatEffectiveTime = (row: any) => {
const buildTemplateName = (row: any) => row?.templateName || `模板#${row?.id ?? "-"}`;
const isDownloadBlocked = (row?: any) => ["DISABLED", "ARCHIVED"].includes(String(row?.status || "").trim().toUpperCase());
-const isWatermarkDownloadBlocked = (row?: any) => isDownloadBlocked(row) || !row?.watermarkEnabled;
const confirmAction = async (message: string, title: string) => {
try {
@@ -443,8 +388,7 @@ const confirmAction = async (message: string, title: string) => {
const buildTemplateParams = () => ({
templateName: templateQuery.value.templateName || undefined,
templateType: templateQuery.value.templateType || undefined,
- status: templateQuery.value.status || undefined,
- watermarkEnabled: templateQuery.value.watermarkEnabled ? templateQuery.value.watermarkEnabled === "true" : undefined,
+ status: "PUBLISHED",
pageNo: templatePageNo.value,
pageSize: templatePageSize.value,
});
@@ -455,7 +399,6 @@ const buildLogParams = () => ({
userId: canReadAllLogs.value ? logQuery.value.userId : undefined,
userKeyword: canReadAllLogs.value ? logQuery.value.userKeyword || undefined : undefined,
versionNo: logQuery.value.versionNo,
- downloadType: logQuery.value.downloadType || undefined,
ip: canReadAllLogs.value ? logQuery.value.ip || undefined : undefined,
downloadedFrom: logQuery.value.dateRange?.[0] || undefined,
downloadedTo: logQuery.value.dateRange?.[1] || undefined,
@@ -490,7 +433,7 @@ const loadTemplates = async () => {
templateTotalCount.value = 0;
return;
}
- const resp = await fetchTemplates(buildTemplateParams());
+ const resp = await fetchTemplateViewList(buildTemplateParams());
templateRows.value = resp?.data?.list || [];
templateTotalCount.value = Number(resp?.data?.total || 0);
};
@@ -544,8 +487,6 @@ const resetTemplateQuery = async () => {
templateQuery.value = {
templateName: "",
templateType: "",
- status: "",
- watermarkEnabled: "",
};
templatePageNo.value = 1;
templatePageSize.value = 20;
@@ -559,7 +500,6 @@ const resetLogQuery = async () => {
userId: undefined,
userKeyword: "",
versionNo: undefined,
- downloadType: "",
ip: "",
dateRange: [],
};
@@ -594,28 +534,7 @@ const handleDownload = async (row: any) => {
await Promise.all([loadLogs(), refreshDetailData()]);
};
-const handleDownloadWatermark = async (row: any) => {
- if (isWatermarkDownloadBlocked(row)) {
- ElMessage.warning(isDownloadBlocked(row) ? "该模板已停用或归档,当前不可下载" : "该模板未开启水印下载");
- return;
- }
- const confirmed = await confirmAction(
- `确认对「${buildTemplateName(row)}」执行水印下载?系统会记录本次下载日志。`,
- "水印下载",
- );
- if (!confirmed) {
- return;
- }
- const resp = await downloadTemplateWatermark(row.id, { watermarkText: "仅限内部使用" });
- const url = resp?.data?.signedUrl;
- if (!url) {
- ElMessage.error("获取水印下载链接失败");
- return;
- }
- window.open(url, "_blank");
- ElMessage.success("已生成水印下载链接并记录下载日志");
- await Promise.all([loadLogs(), refreshDetailData()]);
-};
+
onMounted(async () => {
initQueryFromRoute();
diff --git a/frontend/src/views/modules/TemplatePage.vue b/frontend/src/views/modules/TemplatePage.vue
index adc3bc4..f7e245b 100644
--- a/frontend/src/views/modules/TemplatePage.vue
+++ b/frontend/src/views/modules/TemplatePage.vue
@@ -39,11 +39,7 @@
-
-
-
-
-
+
查询
@@ -122,9 +118,6 @@
class="w-input-md"
/>
-
-
-
@@ -194,16 +187,20 @@
{{ formatEffectiveTime(row) }}
-
-
- {{ row.watermarkEnabled ? "开启" : "关闭" }}
-
-
+
-
+
- 版本管理
+ 版本管理
+
+ 编辑
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+ 保存修改
+
+
+
@@ -245,7 +304,7 @@
{{ getScopeTypeLabel(detailRow.scopeType) }}
V{{ detailRow.currentVersionNo || 1 }}
{{ formatEffectiveTime(detailRow) }}
- {{ detailRow.watermarkEnabled ? "开启" : "关闭" }}
+
{{ detailRow.downloadRateLimitPerHour ?? "-" }}
{{ detailRow.updatedAt || "-" }}
@@ -458,6 +517,8 @@ import {
createTemplate,
disableTemplate,
disableTemplateTypeOption,
+ downloadTemplate,
+
enableTemplateTypeOption,
fetchPublishedTemplateOptions,
fetchTemplateFlowLinks,
@@ -469,6 +530,7 @@ import {
fetchTemplates,
publishTemplate,
rollbackTemplate,
+ updateTemplate,
} from "../../api/modules";
import { PERMS } from "../../constants/permissions";
import { DRAWER_SIZE, DIALOG_WIDTH, LABEL_WIDTH } from "../../constants/ui";
@@ -495,7 +557,9 @@ const BIZ_SCENE_LABELS: Record = {
const authStore = useAuthStore();
const canRead = computed(() => authStore.hasPermission(PERMS.template.read));
+const canDetailRead = computed(() => authStore.hasPermission(PERMS.template.detailRead));
const canCreate = computed(() => authStore.hasPermission(PERMS.template.create));
+const canUpdate = computed(() => authStore.hasPermission(PERMS.template.update));
const canPublish = computed(() => authStore.hasPermission(PERMS.template.publish));
const canDisable = computed(() => authStore.hasPermission(PERMS.template.disable));
const canArchive = computed(() => authStore.hasPermission(PERMS.template.archive));
@@ -508,6 +572,7 @@ const pageSize = ref(20);
const totalCount = ref(0);
const createVisible = ref(false);
+const editVisible = ref(false);
const detailVisible = ref(false);
const flowBindingVisible = ref(false);
const rollbackDialogVisible = ref(false);
@@ -549,10 +614,7 @@ const effectiveStatusOptions = [
{ label: "已过期", value: "EXPIRED" },
];
-const watermarkFilterOptions = [
- { label: "开启", value: "true" },
- { label: "关闭", value: "false" },
-];
+
const listQuery = ref({
templateName: "",
@@ -561,7 +623,20 @@ const listQuery = ref({
scopeType: "" as ScopeTypeCode | "",
bizScene: "" as BizSceneCode | "",
effectiveStatus: "",
- watermarkEnabled: "" as "" | "true" | "false",
+});
+
+const editFormRef = ref();
+const editForm = ref({
+ id: 0,
+ templateName: "",
+ templateType: "AGENDA" as TemplateTypeCode,
+ scopeType: "ALL" as ScopeTypeCode,
+ projectId: undefined as number | undefined,
+ meetingId: undefined as number | undefined,
+ bizScene: "MEETING_RECOMMEND" as BizSceneCode,
+ effectiveFrom: "",
+ effectiveTo: "",
+ downloadRateLimitPerHour: 100,
});
const form = ref({
@@ -575,7 +650,6 @@ const form = ref({
changeLog: "",
effectiveFrom: "",
effectiveTo: "",
- watermarkEnabled: false,
downloadRateLimitPerHour: 100,
});
@@ -666,7 +740,6 @@ const buildListParams = () => ({
scopeType: listQuery.value.scopeType || undefined,
bizScene: listQuery.value.bizScene || undefined,
effectiveStatus: listQuery.value.effectiveStatus || undefined,
- watermarkEnabled: listQuery.value.watermarkEnabled ? listQuery.value.watermarkEnabled === "true" : undefined,
pageNo: pageNo.value,
pageSize: pageSize.value,
});
@@ -717,7 +790,6 @@ const resetCreateForm = () => {
changeLog: "",
effectiveFrom: "",
effectiveTo: "",
- watermarkEnabled: false,
downloadRateLimitPerHour: 100,
};
};
@@ -728,6 +800,23 @@ const openCreateDrawer = () => {
createVisible.value = true;
};
+const openEditDrawer = (row: any) => {
+ selectedTemplateId.value = row.id;
+ editForm.value = {
+ id: row.id,
+ templateName: row.templateName || "",
+ templateType: (row.templateType || "AGENDA") as TemplateTypeCode,
+ scopeType: (row.scopeType || "ALL") as ScopeTypeCode,
+ projectId: row.projectId || undefined,
+ meetingId: row.meetingId || undefined,
+ bizScene: (row.bizScene || "MEETING_RECOMMEND") as BizSceneCode,
+ effectiveFrom: row.effectiveFrom || "",
+ effectiveTo: row.effectiveTo || "",
+ downloadRateLimitPerHour: Number(row.downloadRateLimitPerHour || 100),
+ };
+ editVisible.value = true;
+};
+
const openFlowBindingDrawer = async () => {
if (!canRead.value) {
return;
@@ -884,7 +973,6 @@ const handleCreate = async () => {
changeLog: form.value.changeLog,
effectiveFrom: form.value.effectiveFrom || undefined,
effectiveTo: form.value.effectiveTo || undefined,
- watermarkEnabled: form.value.watermarkEnabled,
downloadRateLimitPerHour: form.value.downloadRateLimitPerHour,
});
ElMessage.success("模板创建成功");
@@ -892,6 +980,45 @@ const handleCreate = async () => {
await load();
};
+const handleEdit = async () => {
+ if (!editFormRef.value) {
+ return;
+ }
+ const valid = await editFormRef.value.validate().catch(() => false);
+ if (!valid) {
+ return;
+ }
+ if (editForm.value.scopeType === "PROJECT" && !editForm.value.projectId) {
+ ElMessage.warning("项目级模板需要填写项目ID");
+ return;
+ }
+ if (editForm.value.scopeType === "MEETING" && !editForm.value.meetingId) {
+ ElMessage.warning("会议级模板需要填写会议ID");
+ return;
+ }
+ if (editForm.value.effectiveFrom && editForm.value.effectiveTo && editForm.value.effectiveFrom > editForm.value.effectiveTo) {
+ ElMessage.warning("生效开始时间不能晚于生效结束时间");
+ return;
+ }
+ await updateTemplate(editForm.value.id, {
+ templateName: editForm.value.templateName,
+ templateType: editForm.value.templateType,
+ scopeType: editForm.value.scopeType,
+ projectId: editForm.value.projectId,
+ meetingId: editForm.value.meetingId,
+ bizScene: editForm.value.bizScene,
+ effectiveFrom: editForm.value.effectiveFrom || undefined,
+ effectiveTo: editForm.value.effectiveTo || undefined,
+ downloadRateLimitPerHour: editForm.value.downloadRateLimitPerHour,
+ });
+ ElMessage.success("模板修改成功");
+ editVisible.value = false;
+ await load();
+ if (detailVisible.value && Number(detailRow.value?.id) === Number(editForm.value.id)) {
+ await refreshDetailData();
+ }
+};
+
const enableType = async (typeCode: string) => {
await enableTemplateTypeOption(typeCode);
ElMessage.success(`模板类型 ${typeCode} 已启用`);
@@ -1116,7 +1243,6 @@ const resetListQuery = async () => {
scopeType: "",
bizScene: "",
effectiveStatus: "",
- watermarkEnabled: "",
};
pageNo.value = 1;
pageSize.value = 20;
diff --git a/frontend/src/views/modules/TenantDashboardPage.vue b/frontend/src/views/modules/TenantDashboardPage.vue
index 587202c..b5f52d8 100644
--- a/frontend/src/views/modules/TenantDashboardPage.vue
+++ b/frontend/src/views/modules/TenantDashboardPage.vue
@@ -74,24 +74,17 @@
stripe
:empty-text="loading ? '加载中...' : '暂无待审批任务'"
>
-
-
-
+
- {{ formatModuleCode(row.moduleCode) }}
+ {{ meetingNameMap[row.meetingId] || `会议#${row.meetingId}` }}
-
+
-
- {{ row.nodeStatus === 'PENDING' ? '待处理' : row.nodeStatus }}
-
+ {{ formatAuditNode(row.node) }}
-
+
import { computed, ref, onMounted } from "vue";
import { useRouter } from "vue-router";
-import { fetchAuditTasks, fetchDashboardStats } from "../../api/modules";
+import { fetchAuditTasks, fetchDashboardStats, fetchMeetings } from "../../api/modules";
import { PERMS } from "../../constants/permissions";
import { useAuthStore } from "../../stores/auth";
-import { toZhModuleCode } from "../../utils/status";
+import { toZhAuditNode } from "../../utils/status";
const router = useRouter();
const authStore = useAuthStore();
@@ -127,8 +120,9 @@ const myPendingTasks = ref([]);
const pendingAuditCount = ref(0);
const activeMeetingCount = ref(0);
const pendingFinanceCount = ref(0);
+const meetingNameMap = ref>({});
-const formatModuleCode = (code: string) => toZhModuleCode(code);
+const formatAuditNode = (code: string) => toZhAuditNode(code);
const goToAudit = (meetingId?: number) => {
if (meetingId) {
@@ -139,17 +133,19 @@ const goToAudit = (meetingId?: number) => {
};
const loadMyTasks = async () => {
- if (!authStore.hasPermission(PERMS.audit.read)) {
- return;
- }
loading.value = true;
try {
- const resp = await fetchAuditTasks({ mine: true, pageNo: 1, pageSize: 50 });
+ const resp = await fetchAuditTasks({ mine: true, scope: "PENDING_MINE", pageNo: 1, pageSize: 50 });
const list = resp?.data?.list || [];
// 过滤出PENDING的任务以供展示
- const pendings = list.filter((x: any) => x.nodeStatus === "PENDING" || x.nodeStatus === "CLAIMED");
+ const pendings = list.filter((x: any) => x.status === "PENDING" || x.status === "CLAIMED");
myPendingTasks.value = pendings.slice(0, 10); // 工作台只展示Top 10
pendingAuditCount.value = pendings.length;
+
+ // 如果有待办任务,获取会议名称
+ if (pendings.length > 0) {
+ await loadNameMaps();
+ }
} catch (e) {
// ignore
} finally {
@@ -172,6 +168,25 @@ const loadDashboardStats = async () => {
// 接口未就绪时静默失败
}
};
+
+const loadNameMaps = async () => {
+ try {
+ const meetingResp = await fetchMeetings({ pageNo: 1, pageSize: 200 });
+ const meetings = meetingResp?.data?.list || [];
+ const meetingMap: Record = {};
+ meetings.forEach((item: any) => {
+ const id = Number(item?.id || 0);
+ if (id > 0) {
+ const projectName = String(item?.projectName || "").trim();
+ const topic = String(item?.topic || "").trim();
+ meetingMap[id] = projectName && topic ? `${projectName} / ${topic}` : topic || `会议#${id}`;
+ }
+ });
+ meetingNameMap.value = meetingMap;
+ } catch (_e) {
+ meetingNameMap.value = {};
+ }
+};