From 943c3708b084102cdf19a8d54552a6e8ceadd828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BF=97=E5=BC=BA?= <357099073@qq.com> Date: Tue, 15 Sep 2026 10:44:31 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/components/CommonHeader.vue | 7 +- .../src/components/MessageDetailDialog.vue | 7 +- backend/src/utils/datetime.ts | 55 +++ .../cms/article/index/components/preview.vue | 3 +- .../views/apps/cms/article/index/index.vue | 9 +- backend/src/views/apps/cms/demand/index.vue | 5 +- backend/src/views/apps/cms/domain/audit.vue | 5 +- backend/src/views/apps/cms/domain/pool.vue | 5 +- .../src/views/apps/cms/friendlink/index.vue | 5 +- .../views/apps/cms/product/index/index.vue | 5 +- .../src/views/apps/cms/product/type/index.vue | 5 +- .../views/apps/cms/solution/index/index.vue | 5 +- .../views/apps/cms/solution/type/index.vue | 5 +- .../components/contactDetailDialog.vue | 3 +- backend/src/views/apps/oa/dashboard/index.vue | 11 +- .../apps/oa/reimburse/components/detail.vue | 3 +- backend/src/views/apps/oa/reimburse/index.vue | 12 +- .../basicSettings/roles/components/detail.vue | 5 +- .../src/views/basicSettings/roles/index.vue | 10 +- .../tenants/components/detail.vue | 3 +- .../views/basicSettings/tenants/domain.vue | 5 +- .../users/components/preview.vue | 5 +- .../users/components/userEdit.vue | 34 +- .../src/views/moduleshop/category/index.vue | 13 +- backend/src/views/moduleshop/center/index.vue | 13 +- .../src/views/moduleshop/publish/index.vue | 13 +- .../src/views/settings/systeminfo/index.vue | 9 +- .../system/dict/components/DictItemList.vue | 18 +- .../system/dict/components/DictTypeList.vue | 18 +- backend/src/views/system/dict/index.vue | 17 +- .../src/views/system/fileManager/index.vue | 11 +- backend/src/views/system/loginLog/index.vue | 10 +- .../src/views/system/operationLog/index.vue | 10 +- backend/src/views/tools/notebook/index.vue | 7 +- backend/src/views/user/userProfile.vue | 13 +- go/cmd/migrate_storage/main.go | 1 - go/controllers/backend_admin_user.go | 7 +- go/controllers/backend_article.go | 58 ---- .../backend_crm_product_category.go | 1 + go/controllers/backend_crm_unit.go | 1 + go/controllers/backend_file.go | 5 - go/controllers/backend_menu.go | 3 - go/controllers/backend_menu_front.go | 3 - go/controllers/backend_oa_document.go | 6 - go/controllers/backend_oa_notice.go | 5 - go/controllers/backend_oa_schedule.go | 5 - go/controllers/backend_product.go | 34 -- go/controllers/backend_role.go | 71 +++- go/controllers/backend_site_settings.go | 26 -- go/controllers/backend_solution.go | 34 -- go/controllers/index_portal.go | 7 - go/controllers/platform_file.go | 5 - go/controllers/platform_sitereminder.go | 54 ++- go/controllers/platform_tenant_user.go | 6 +- go/docs/sql/alter_backend_crm_columns.sql | 144 ++++++++ go/docs/sql/alter_backend_crm_overdue.sql | 13 +- .../sql/alter_backend_crm_payback_receipt.sql | 15 +- .../alter_backend_crm_product_dev_mode.sql | 14 +- go/docs/sql/alter_contact_mobiles.sql | 27 +- .../alter_crm_product_category_default.sql | 7 +- go/docs/sql/alter_crm_unit_default.sql | 7 +- .../sql/alter_oa_schedule_carry_columns.sql | 48 +++ go/docs/sql/alter_payback_record_item.sql | 15 +- go/docs/sql/alter_project_contacts.sql | 15 +- .../alter_site_reminderlist_target_ids.sql | 21 ++ .../sql/alter_system_admin_role_columns.sql | 20 ++ .../alter_system_files_storage_columns.sql | 51 +++ .../sql/alter_system_tenant_user_columns.sql | 20 ++ go/docs/sql/create_backend_crm_contract.sql | 47 +++ go/docs/sql/create_backend_crm_payback.sql | 62 ++++ go/docs/sql/create_cms_article.sql | 49 +++ go/docs/sql/create_cms_product.sql | 39 +++ go/docs/sql/create_cms_solution.sql | 46 +++ go/docs/sql/create_oa_document.sql | 133 ++++++++ go/docs/sql/create_oa_notice.sql | 31 ++ go/docs/sql/create_platform_site_setting.sql | 3 +- go/docs/sql/create_tenant_setting_items.sql | 20 ++ go/docs/sql/init_front_menu_defaults.sql | 29 ++ go/docs/sql/update_role_system.sql | 67 ++++ go/models/admin_role_default.go | 88 +---- go/models/cms_article.go | 56 ---- go/models/cms_product.go | 44 --- go/models/cms_solution.go | 46 --- go/models/front_menu_default.go | 77 ----- go/models/init.go | 282 +--------------- go/models/oa_document.go | 153 +-------- go/models/oa_notice.go | 35 -- go/models/oa_schedule.go | 27 -- go/models/platform_site_setting.go | 131 -------- go/models/system_file.go | 31 -- go/models/system_reminderlist.go | 41 ++- go/models/system_tenant_user.go | 31 -- go/pkg/tagengine/provider.go | 6 - go/services/crm_project_doc.go | 2 - go/services/file_service.go | 4 - go/services/oa_document.go | 20 -- go/services/system_sitereminder.go | 315 ++++++++++++------ go/services/tenant_user.go | 4 - platform/src/components/CommonHeader.vue | 7 +- platform/src/utils/datetime.ts | 55 +++ .../babyhealth/users/components/preview.vue | 5 +- platform/src/views/apps/cms/domain/audit.vue | 5 +- platform/src/views/apps/cms/domain/pool.vue | 5 +- .../basicSettings/roles/components/detail.vue | 5 +- .../src/views/basicSettings/roles/index.vue | 128 ++++++- .../sitereminder/components/detail.vue | 7 +- .../sitereminder/components/edit.vue | 312 ++++++++++++++--- .../basicSettings/sitereminder/index.vue | 49 ++- .../tenants/components/detail.vue | 3 +- .../views/basicSettings/tenants/domain.vue | 5 +- .../users/components/preview.vue | 5 +- .../src/views/moduleshop/category/index.vue | 13 +- .../src/views/moduleshop/center/index.vue | 13 +- .../src/views/moduleshop/publish/index.vue | 13 +- .../src/views/system/fileManager/index.vue | 11 +- .../system/loginLog/components/detail.vue | 3 +- platform/src/views/system/loginLog/index.vue | 10 +- .../system/operationLog/components/detail.vue | 3 +- .../src/views/system/operationLog/index.vue | 10 +- .../src/views/system/smssettings/tasklist.vue | 5 +- platform/src/views/template/index.vue | 5 +- .../agentapimanagement/components/detail.vue | 5 +- .../views/tools/agentapimanagement/index.vue | 3 +- platform/src/views/tools/notebook/index.vue | 7 +- platform/src/views/user/userProfile.vue | 13 +- 125 files changed, 2009 insertions(+), 1728 deletions(-) create mode 100644 backend/src/utils/datetime.ts create mode 100644 go/docs/sql/alter_backend_crm_columns.sql create mode 100644 go/docs/sql/alter_oa_schedule_carry_columns.sql create mode 100644 go/docs/sql/alter_site_reminderlist_target_ids.sql create mode 100644 go/docs/sql/alter_system_admin_role_columns.sql create mode 100644 go/docs/sql/alter_system_files_storage_columns.sql create mode 100644 go/docs/sql/alter_system_tenant_user_columns.sql create mode 100644 go/docs/sql/create_backend_crm_contract.sql create mode 100644 go/docs/sql/create_backend_crm_payback.sql create mode 100644 go/docs/sql/create_cms_article.sql create mode 100644 go/docs/sql/create_cms_product.sql create mode 100644 go/docs/sql/create_cms_solution.sql create mode 100644 go/docs/sql/create_oa_document.sql create mode 100644 go/docs/sql/create_oa_notice.sql create mode 100644 go/docs/sql/create_tenant_setting_items.sql create mode 100644 go/docs/sql/init_front_menu_defaults.sql create mode 100644 go/docs/sql/update_role_system.sql delete mode 100644 go/models/front_menu_default.go create mode 100644 platform/src/utils/datetime.ts diff --git a/backend/src/components/CommonHeader.vue b/backend/src/components/CommonHeader.vue index 6e89dc4..cf24c46 100644 --- a/backend/src/components/CommonHeader.vue +++ b/backend/src/components/CommonHeader.vue @@ -120,6 +120,7 @@ import { User, SwitchButton, Sunny, Moon, Refresh, Bell, HomeFilled, Loading, Me import { ElMessage } from 'element-plus'; import { getMySiteReminders, readAllSiteReminders } from "@/api/sitereminder"; import MessageDetailDialog from "./MessageDetailDialog.vue"; +import { formatTime } from "@/utils/datetime"; const router = useRouter(); const route = useRoute(); @@ -181,12 +182,6 @@ const loadingMessages = ref(false); const detailVisible = ref(false); const currentReminder = ref({}); -const formatTime = (timeStr: any) => { - if (!timeStr) return "-"; - const date = new Date(timeStr); - return date.toLocaleString(); -}; - const fetchUnreadCount = async () => { if (!authStore.token) return; try { diff --git a/backend/src/components/MessageDetailDialog.vue b/backend/src/components/MessageDetailDialog.vue index 32e367a..9818445 100644 --- a/backend/src/components/MessageDetailDialog.vue +++ b/backend/src/components/MessageDetailDialog.vue @@ -32,6 +32,7 @@ import { computed, ref, watch } from "vue"; import { User, Calendar } from "@element-plus/icons-vue"; import { readSiteReminder } from "@/api/sitereminder"; +import { formatTime } from "@/utils/datetime"; const props = defineProps({ modelValue: Boolean, @@ -62,12 +63,6 @@ const getSenderName = (item: any) => { return item.sender_type; }; -const formatTime = (timeStr: any) => { - if (!timeStr) return "-"; - const date = new Date(timeStr); - return date.toLocaleString(); -}; - watch(() => props.modelValue, async (val) => { if (val && props.reminder && !props.viewOnly && props.reminder.is_read === 0) { loading.value = true; diff --git a/backend/src/utils/datetime.ts b/backend/src/utils/datetime.ts new file mode 100644 index 0000000..6cf5ba8 --- /dev/null +++ b/backend/src/utils/datetime.ts @@ -0,0 +1,55 @@ +/** + * 统一日期时间格式化工具(backend 端) + * + * 后端(Go)返回的时间字段为 RFC3339 / ISO 8601 字符串, + * 例如 "2026-03-31T23:34:07+08:00",直接渲染不友好。 + * 页面上显示时间统一使用本工具,输出: + * formatDateTime / formatTime -> 2026-03-31 23:34:07(YYYY-MM-DD HH:mm:ss) + * formatDate / formatDateOnly -> 2026-03-31(YYYY-MM-DD) + */ + +const pad = (n: number) => String(n).padStart(2, "0"); + +function toDate(value: any): Date | null { + if (value === null || value === undefined || value === "") return null; + if (value instanceof Date) return isNaN(value.getTime()) ? null : value; + // 兼容秒级 / 毫秒级时间戳 + if (typeof value === "number") { + const d = new Date(value < 1e12 ? value * 1000 : value); + return isNaN(d.getTime()) ? null : d; + } + // "2026-03-31 23:34:07" 形式在部分浏览器解析异常,统一先替换为 ISO 形式 + const str = String(value).trim().replace(" ", "T"); + const d = new Date(str); + return isNaN(d.getTime()) ? null : d; +} + +/** 2026-03-31 23:34:07(YYYY-MM-DD HH:mm:ss);非法值返回 "-" */ +export function formatDateTime(value: any): string { + const d = toDate(value); + if (!d) return "-"; + return ( + `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ` + + `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}` + ); +} + +/** 与 formatDateTime 相同(兼容旧调用名) */ +export function formatTime(value: any): string { + return formatDateTime(value); +} + +/** 2026-03-31(YYYY-MM-DD);非法值返回 "-";纯日期串不做时区换算 */ +export function formatDate(value: any): string { + if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value.trim())) { + return value.trim(); + } + const d = toDate(value); + if (!d) return "-"; + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; +} + +/** 与 formatDate 相同(兼容旧调用名) */ +export function formatDateOnly(value: any): string { + return formatDate(value); +} diff --git a/backend/src/views/apps/cms/article/index/components/preview.vue b/backend/src/views/apps/cms/article/index/components/preview.vue index ef5977a..66f5c6b 100644 --- a/backend/src/views/apps/cms/article/index/components/preview.vue +++ b/backend/src/views/apps/cms/article/index/components/preview.vue @@ -30,7 +30,7 @@ - 创建日期:{{ model?.create_time }} + 创建日期:{{ formatDateTime(model?.create_time) }} @@ -52,6 +52,7 @@ diff --git a/platform/src/views/basicSettings/sitereminder/index.vue b/platform/src/views/basicSettings/sitereminder/index.vue index 1750fc4..666cbb6 100644 --- a/platform/src/views/basicSettings/sitereminder/index.vue +++ b/platform/src/views/basicSettings/sitereminder/index.vue @@ -105,6 +105,7 @@ import { } from "@/api/sitereminder"; import MessageComposeDialog from "./components/edit.vue"; import MessageDetailDialog from "./components/detail.vue"; +import { formatTime } from "@/utils/datetime"; const loading = ref(false); const reminderList = ref([]); @@ -124,41 +125,65 @@ const currentReminder = ref({}); // 接收目标标签类型 const getTargetTagType = (type: string) => { switch (type) { + case "all": + return "danger"; case "platform": return "primary"; case "tenant_all": return "success"; - case "role": + case "tenant_role": return "warning"; case "tenant": - return "danger"; + return "warning"; + case "tenant_user": + return "info"; + case "role": + return "warning"; default: return "info"; } }; +// 解析接收目标多选ID数量(兼容数组 / JSON 字符串) +const getTargetIdsCount = (row: any) => { + const raw = row?.target_ids; + if (!raw) return 0; + if (Array.isArray(raw)) return raw.length; + if (typeof raw === "string") { + try { + const arr = JSON.parse(raw); + return Array.isArray(arr) ? arr.length : 0; + } catch { + return 0; + } + } + return 0; +}; + // 接收目标显示文字 const getTargetLabel = (row: any) => { + const count = getTargetIdsCount(row); switch (row.target_type) { + case "all": + return "全平台(所有人)"; case "platform": - return "平台端"; + return "平台端(所有人)"; case "tenant_all": - return "管理端 (所有租户)"; + return "租户端(所有租户)"; + case "tenant_role": + return count > 0 ? `租户角色(${count}个)` : "租户角色"; + case "tenant": + if (count > 0) return `租户(${count}个)`; + return row.target_tenant_id ? `租户(ID: ${row.target_tenant_id})` : "租户"; + case "tenant_user": + return count > 0 ? `租户用户(${count}人)` : "租户用户"; case "role": return `平台角色 (ID: ${row.target_role_id})`; - case "tenant": - return `特定租户 (ID: ${row.target_tenant_id})`; default: return row.target_type || "未知"; } }; -const formatTime = (timeStr: any) => { - if (!timeStr) return "-"; - const date = new Date(timeStr); - return date.toLocaleString(); -}; - // 获取已发送列表 const fetchList = async () => { loading.value = true; diff --git a/platform/src/views/basicSettings/tenants/components/detail.vue b/platform/src/views/basicSettings/tenants/components/detail.vue index 3fc0336..bc92dea 100644 --- a/platform/src/views/basicSettings/tenants/components/detail.vue +++ b/platform/src/views/basicSettings/tenants/components/detail.vue @@ -36,7 +36,7 @@ - {{ detailData.create_time || '-' }} + {{ detailData.create_time ? formatDateTime(detailData.create_time) : '-' }} @@ -68,6 +68,7 @@ import { ref } from "vue"; import { getTenantDetail } from "@/api/tenant"; import TenantUsersTab from "./TenantUsersTab.vue"; +import { formatDateTime } from "@/utils/datetime"; const visible = ref(false); const loading = ref(false); diff --git a/platform/src/views/basicSettings/tenants/domain.vue b/platform/src/views/basicSettings/tenants/domain.vue index 975cd1b..3cd2243 100644 --- a/platform/src/views/basicSettings/tenants/domain.vue +++ b/platform/src/views/basicSettings/tenants/domain.vue @@ -104,7 +104,9 @@ - + + +