更新代码
This commit is contained in:
@@ -117,6 +117,7 @@ import { User, SwitchButton, Sunny, Moon, Refresh, Bell, HomeFilled, Expand, Loa
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { getMySiteReminders, readAllSiteReminders } from "@/api/sitereminder";
|
||||
import MessageDetailDialog from "@/views/basicSettings/sitereminder/components/detail.vue";
|
||||
import { formatTime } from "@/utils/datetime";
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
@@ -178,12 +179,6 @@ const loadingMessages = ref(false);
|
||||
const detailVisible = ref(false);
|
||||
const currentReminder = ref<any>({});
|
||||
|
||||
const formatTime = (timeStr: any) => {
|
||||
if (!timeStr) return "-";
|
||||
const date = new Date(timeStr);
|
||||
return date.toLocaleString();
|
||||
};
|
||||
|
||||
const fetchUnreadCount = async () => {
|
||||
if (!authStore.token) return;
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 统一日期时间格式化工具(platform 端)
|
||||
*
|
||||
* 后端(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);
|
||||
}
|
||||
@@ -48,10 +48,10 @@
|
||||
{{ user.login_count || 0 }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ user.create_time || "未知" }}
|
||||
{{ user.create_time ? formatDateTime(user.create_time) : "未知" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间" :span="2">
|
||||
{{ user.update_time || "未知" }}
|
||||
{{ user.update_time ? formatDateTime(user.update_time) : "未知" }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
@@ -66,6 +66,7 @@
|
||||
import { ref, watch } from "vue";
|
||||
import { UserFilled } from "@element-plus/icons-vue";
|
||||
import { getAllRoles } from "@/api/role";
|
||||
import { formatDateTime } from "@/utils/datetime";
|
||||
|
||||
interface User {
|
||||
id: number;
|
||||
|
||||
@@ -76,7 +76,9 @@
|
||||
<el-tag v-else :type="sslTagType(scope.row.ssl_status)">{{ sslText(scope.row.ssl_status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" label="申请时间" width="170" />
|
||||
<el-table-column prop="create_time" label="申请时间" width="170">
|
||||
<template #default="{ row }">{{ formatDateTime(row.create_time) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="230" fixed="right" align="center">
|
||||
<template #default="scope">
|
||||
<!-- 自有域名无人工审核:解析与证书就绪后自动生效,这里只提供重新检测与禁用 -->
|
||||
@@ -134,6 +136,7 @@
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Refresh } from '@element-plus/icons-vue'
|
||||
import { formatDateTime } from '@/utils/datetime'
|
||||
import {
|
||||
getTenantDomainList,
|
||||
auditTenantDomain,
|
||||
|
||||
@@ -49,7 +49,9 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" label="创建时间" width="180" />
|
||||
<el-table-column prop="create_time" label="创建时间" width="180">
|
||||
<template #default="{ row }">{{ formatDateTime(row.create_time) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="240" fixed="right" align="center">
|
||||
<template #default="scope">
|
||||
<el-button size="small" text @click="handleEdit(scope.row)">
|
||||
@@ -107,6 +109,7 @@
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Edit, Delete, Refresh, Switch } from '@element-plus/icons-vue'
|
||||
import { formatDateTime } from '@/utils/datetime'
|
||||
import {
|
||||
getDomainPoolList,
|
||||
createDomainPool,
|
||||
|
||||
@@ -33,10 +33,10 @@
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ roleDetail.create_time }}
|
||||
{{ formatDateTime(roleDetail.create_time) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">
|
||||
{{ roleDetail.update_time }}
|
||||
{{ formatDateTime(roleDetail.update_time) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
@@ -51,6 +51,7 @@ import { ref, watch, computed } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { getRoleById } from "@/api/role";
|
||||
import { getAllMenus } from "@/api/menu";
|
||||
import { formatDateTime } from "@/utils/datetime";
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean;
|
||||
|
||||
@@ -23,7 +23,16 @@
|
||||
|
||||
<!-- 角色列表 -->
|
||||
<div v-else>
|
||||
<el-table :data="roles" stripe style="width: 100%" v-loading="loading">
|
||||
<div class="scope-filter">
|
||||
<el-radio-group v-model="scopeFilter" size="small">
|
||||
<el-radio-button label="all">全部</el-radio-button>
|
||||
<el-radio-button label="platform">平台角色</el-radio-button>
|
||||
<el-radio-button label="global">全局角色</el-radio-button>
|
||||
<el-radio-button label="tenant">租户角色</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
<el-table :data="displayRoles" stripe style="width: 100%" v-loading="loading">
|
||||
<el-table-column prop="id" label="ID" width="80" align="center" />
|
||||
<el-table-column
|
||||
prop="name"
|
||||
@@ -31,26 +40,41 @@
|
||||
min-width="150"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column prop="cid" label="角色分类" width="120" align="center">
|
||||
<el-table-column prop="cid" label="角色分类" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="Number(row.cid) === 2 ? 'warning' : 'primary'" size="small">
|
||||
{{ Number(row.cid) === 2 ? "租户角色" : "平台角色" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="100" align="center">
|
||||
<el-table-column label="所属范围" min-width="150" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ getScopeLabel(row) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="类型" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="Number(row.is_custom) === 1 ? 'warning' : 'info'"
|
||||
size="small"
|
||||
effect="plain"
|
||||
>
|
||||
{{ Number(row.is_custom) === 1 ? "自建" : "预置" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'success' : 'info'" size="small">
|
||||
{{ row.status === 1 ? "启用" : "禁用" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="rights"
|
||||
label="权限"
|
||||
min-width="150"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column label="权限" min-width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ formatRights(row.rights) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
@@ -101,10 +125,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from "vue";
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { Plus, View, Edit, Delete, Refresh } from "@element-plus/icons-vue";
|
||||
import { getAllRoles, getRoleById, deleteRole } from "@/api/role";
|
||||
import { getTenantList } from "@/api/tenant";
|
||||
import RoleEditDialog from "./components/edit.vue";
|
||||
import RoleDetailDialog from "./components/detail.vue";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
@@ -116,6 +141,82 @@ const roles = ref<any[]>([]);
|
||||
const loading = ref(false);
|
||||
const error = ref("");
|
||||
|
||||
// 范围筛选:all 全部 / platform 平台角色 / global 全局角色 / tenant 租户角色
|
||||
const scopeFilter = ref("all");
|
||||
// 租户ID -> 租户名称 映射(用于展示角色所属租户)
|
||||
const tenantNameMap = ref<Record<number, string>>({});
|
||||
|
||||
// 角色展示排序:平台角色 → 全局角色 → 租户角色,同级按 id 升序
|
||||
const sortRoles = (list: any[]) => {
|
||||
// cid 非 2(含历史数据 cid 为空)视为平台角色,排在全局/租户角色之前
|
||||
const rank = (r: any) =>
|
||||
Number(r.cid) === 2 ? (Number(r.tenant_id || 0) === 0 ? 1 : 2) : 0;
|
||||
return [...list].sort((a, b) => {
|
||||
const ra = rank(a);
|
||||
const rb = rank(b);
|
||||
if (ra !== rb) return ra - rb;
|
||||
return Number(a.id) - Number(b.id);
|
||||
});
|
||||
};
|
||||
|
||||
const displayRoles = computed(() => {
|
||||
if (scopeFilter.value === "platform")
|
||||
return roles.value.filter((r) => Number(r.cid) !== 2);
|
||||
if (scopeFilter.value === "global")
|
||||
return roles.value.filter(
|
||||
(r) => Number(r.cid) === 2 && Number(r.tenant_id || 0) === 0
|
||||
);
|
||||
if (scopeFilter.value === "tenant")
|
||||
return roles.value.filter(
|
||||
(r) => Number(r.cid) === 2 && Number(r.tenant_id || 0) > 0
|
||||
);
|
||||
return roles.value;
|
||||
});
|
||||
|
||||
// 所属范围:平台 / 全局(所有租户)/ 具体租户名
|
||||
const getScopeLabel = (row: any) => {
|
||||
if (Number(row.cid) !== 2) return "平台";
|
||||
const tid = Number(row.tenant_id || 0);
|
||||
if (tid === 0) return "全局(所有租户)";
|
||||
return tenantNameMap.value[tid] || `租户ID: ${tid}`;
|
||||
};
|
||||
|
||||
// 权限展示:空 = 全权限;有值展示权限项数量
|
||||
const formatRights = (rights: any) => {
|
||||
if (rights === null || rights === undefined) return "全权限";
|
||||
let ids: any[] = [];
|
||||
if (Array.isArray(rights)) {
|
||||
ids = rights;
|
||||
} else if (typeof rights === "string") {
|
||||
const s = rights.trim();
|
||||
if (!s) return "全权限";
|
||||
try {
|
||||
const arr = JSON.parse(s);
|
||||
ids = Array.isArray(arr) ? arr : [s];
|
||||
} catch {
|
||||
ids = s.split(",").filter((x) => x.trim() !== "");
|
||||
}
|
||||
}
|
||||
return ids.length === 0 ? "全权限" : `${ids.length} 项`;
|
||||
};
|
||||
|
||||
// 租户名映射(角色所属范围展示用;缺失时退化为显示租户ID)
|
||||
const loadTenantNames = async () => {
|
||||
try {
|
||||
const res = await getTenantList({ page: 1, pageSize: 1000 });
|
||||
if (res.code === 200) {
|
||||
const list = res.data?.list || res.data || [];
|
||||
const map: Record<number, string> = {};
|
||||
list.forEach((t: any) => {
|
||||
map[Number(t.id)] = t.tenant_name;
|
||||
});
|
||||
tenantNameMap.value = map;
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
|
||||
const editDialogVisible = ref(false);
|
||||
const detailDialogVisible = ref(false);
|
||||
const currentRole = ref<any>(null);
|
||||
@@ -138,7 +239,7 @@ const fetchRoles = async () => {
|
||||
try {
|
||||
const res = await getAllRoles();
|
||||
if (res.code === 200) {
|
||||
roles.value = res.data || [];
|
||||
roles.value = sortRoles(res.data || []);
|
||||
// console.log('角色列表:', roles.value);
|
||||
} else {
|
||||
error.value = res.msg || "获取角色列表失败";
|
||||
@@ -213,10 +314,15 @@ function handleEditSuccess() {
|
||||
|
||||
onMounted(() => {
|
||||
fetchRoles();
|
||||
loadTenantNames();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.scope-filter {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.error-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -26,14 +26,48 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="接收目标" prop="target_type">
|
||||
<el-radio-group v-model="form.target_type" @change="handleTargetTypeChange">
|
||||
<el-radio-group
|
||||
v-model="form.target_type"
|
||||
class="target-radio-group"
|
||||
@change="handleTargetTypeChange"
|
||||
>
|
||||
<el-radio-button label="all">全平台</el-radio-button>
|
||||
<el-radio-button label="platform">平台端</el-radio-button>
|
||||
<el-radio-button label="tenant_all">管理端 (所有租户)</el-radio-button>
|
||||
<el-radio-button label="role">平台角色</el-radio-button>
|
||||
<el-radio-button label="tenant">特定租户</el-radio-button>
|
||||
<el-radio-button label="tenant_all">租户端</el-radio-button>
|
||||
<el-radio-button label="tenant_role">租户角色</el-radio-button>
|
||||
<el-radio-button label="tenant">租户</el-radio-button>
|
||||
<el-radio-button label="tenant_user">租户用户</el-radio-button>
|
||||
<el-radio-button v-if="isLegacyRole" label="role">平台角色</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 租户角色(可多选) -->
|
||||
<el-form-item
|
||||
v-if="form.target_type === 'tenant_role'"
|
||||
label="选择角色"
|
||||
prop="target_ids"
|
||||
:rules="[{ required: true, type: 'array', min: 1, message: '请选择接收的租户角色', trigger: 'change' }]"
|
||||
>
|
||||
<el-select
|
||||
v-model="form.target_ids"
|
||||
multiple
|
||||
filterable
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
placeholder="请选择租户角色(可多选)"
|
||||
v-loading="loadingRoles"
|
||||
style="width: 100%;"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in tenantRolesList"
|
||||
:key="item.id"
|
||||
:label="getRoleLabel(item)"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 旧数据:平台角色 -->
|
||||
<el-form-item
|
||||
v-if="form.target_type === 'role'"
|
||||
label="选择角色"
|
||||
@@ -47,7 +81,7 @@
|
||||
style="width: 100%;"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in rolesList"
|
||||
v-for="item in platformRolesList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
@@ -55,28 +89,82 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 租户(可多选,默认发送给所选租户所有人员) -->
|
||||
<el-form-item
|
||||
v-if="form.target_type === 'tenant'"
|
||||
label="选择租户"
|
||||
prop="target_tenant_id"
|
||||
:rules="[{ required: true, message: '请选择接收租户', trigger: 'change' }]"
|
||||
prop="target_ids"
|
||||
:rules="[{ required: true, type: 'array', min: 1, message: '请选择接收租户', trigger: 'change' }]"
|
||||
>
|
||||
<el-select
|
||||
v-model="form.target_tenant_id"
|
||||
placeholder="请输入租户名称搜索"
|
||||
v-model="form.target_ids"
|
||||
multiple
|
||||
filterable
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
placeholder="请输入租户名称搜索(可多选,发送给租户全部人员)"
|
||||
v-loading="loadingTenants"
|
||||
style="width: 100%;"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in tenantsList"
|
||||
:key="item.id"
|
||||
:label="`${item.name} (${item.tenant_code})`"
|
||||
:label="`${item.tenant_name} (${item.tenant_code})`"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 租户用户(先选租户,再选该租户下用户,可多选) -->
|
||||
<template v-if="form.target_type === 'tenant_user'">
|
||||
<el-form-item
|
||||
label="选择租户"
|
||||
prop="user_tenant_id"
|
||||
:rules="[{ required: true, message: '请选择租户', trigger: 'change' }]"
|
||||
>
|
||||
<el-select
|
||||
v-model="form.user_tenant_id"
|
||||
placeholder="请输入租户名称搜索"
|
||||
filterable
|
||||
v-loading="loadingTenants"
|
||||
style="width: 100%;"
|
||||
@change="handleUserTenantChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in tenantsList"
|
||||
:key="item.id"
|
||||
:label="`${item.tenant_name} (${item.tenant_code})`"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
label="选择用户"
|
||||
prop="target_ids"
|
||||
:rules="[{ required: true, type: 'array', min: 1, message: '请选择接收用户', trigger: 'change' }]"
|
||||
>
|
||||
<el-select
|
||||
v-model="form.target_ids"
|
||||
multiple
|
||||
filterable
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
:placeholder="form.user_tenant_id ? '请选择租户用户(可多选)' : '请先选择租户'"
|
||||
:disabled="!form.user_tenant_id"
|
||||
v-loading="loadingTenantUsers"
|
||||
style="width: 100%;"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in tenantUsersList"
|
||||
:key="item.id"
|
||||
:label="getTenantUserLabel(item)"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<el-form-item label="消息内容" prop="content">
|
||||
<el-input
|
||||
v-model="form.content"
|
||||
@@ -109,6 +197,7 @@ import { Promotion } from "@element-plus/icons-vue";
|
||||
import { sendSiteReminder, updateSentSiteReminder } from "@/api/sitereminder";
|
||||
import { getAllRoles } from "@/api/role";
|
||||
import { getTenantList } from "@/api/tenant";
|
||||
import { getTenantUserList } from "@/api/tenantUser";
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: Boolean,
|
||||
@@ -134,38 +223,58 @@ const form = ref({
|
||||
title: "",
|
||||
content: "",
|
||||
target_type: "platform",
|
||||
target_ids: [] as number[],
|
||||
target_role_id: null as number | null,
|
||||
target_tenant_id: null as number | null
|
||||
user_tenant_id: null as number | null
|
||||
});
|
||||
|
||||
// 需要多选目标ID的接收目标类型
|
||||
const MULTI_TARGET_TYPES = ["tenant_role", "tenant", "tenant_user"];
|
||||
|
||||
// 旧数据兼容:平台角色
|
||||
const isLegacyRole = computed(() => form.value.target_type === "role");
|
||||
|
||||
const rules: FormRules = {
|
||||
title: [{ required: true, message: "请输入消息标题", trigger: "blur" }],
|
||||
content: [{ required: true, message: "请输入消息内容", trigger: "blur" }],
|
||||
target_type: [{ required: true, message: "请选择接收目标", trigger: "change" }]
|
||||
};
|
||||
|
||||
const rolesList = ref<any[]>([]);
|
||||
const loadingRoles = ref(false);
|
||||
const platformRolesList = ref<any[]>([]);
|
||||
const tenantRolesList = ref<any[]>([]);
|
||||
const tenantsList = ref<any[]>([]);
|
||||
const tenantUsersList = ref<any[]>([]);
|
||||
const loadingRoles = ref(false);
|
||||
const loadingTenants = ref(false);
|
||||
const loadingTenantUsers = ref(false);
|
||||
|
||||
const handleTargetTypeChange = (val: any) => {
|
||||
form.value.target_role_id = null;
|
||||
form.value.target_tenant_id = null;
|
||||
if (val === "role") {
|
||||
loadRoles();
|
||||
} else if (val === "tenant") {
|
||||
loadTenants();
|
||||
}
|
||||
const getRoleLabel = (item: any) => {
|
||||
const tenantName = tenantsList.value.find(
|
||||
(t) => Number(t.id) === Number(item.tenant_id)
|
||||
)?.tenant_name;
|
||||
if (item.tenant_id && tenantName) return `${item.name}(${tenantName})`;
|
||||
if (item.tenant_id) return `${item.name}(租户ID: ${item.tenant_id})`;
|
||||
return `${item.name}(通用)`;
|
||||
};
|
||||
|
||||
const getTenantUserLabel = (item: any) => {
|
||||
const name = item.name || item.account || `用户 ${item.id}`;
|
||||
const account = item.account && item.name ? `(${item.account})` : "";
|
||||
return `${name}${account}`;
|
||||
};
|
||||
|
||||
// 加载角色(平台角色 cid=1 / 租户角色 cid=2)
|
||||
const loadRoles = async () => {
|
||||
if (rolesList.value.length > 0) return;
|
||||
if (platformRolesList.value.length > 0 || tenantRolesList.value.length > 0) return;
|
||||
loadingRoles.value = true;
|
||||
try {
|
||||
const res = await getAllRoles();
|
||||
if (res.code === 200) {
|
||||
rolesList.value = (res.data || []).filter((r: any) => Number(r.cid) === 1);
|
||||
const rows: any[] = res.data || [];
|
||||
platformRolesList.value = rows.filter((r) => Number(r.cid) === 1);
|
||||
tenantRolesList.value = rows.filter(
|
||||
(r) => Number(r.cid) === 2 && Number(r.status) === 1
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -174,6 +283,7 @@ const loadRoles = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 加载租户列表
|
||||
const loadTenants = async () => {
|
||||
if (tenantsList.value.length > 0) return;
|
||||
loadingTenants.value = true;
|
||||
@@ -189,6 +299,81 @@ const loadTenants = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 加载指定租户下的用户
|
||||
const loadTenantUsers = async (tid: number) => {
|
||||
if (!tid) {
|
||||
tenantUsersList.value = [];
|
||||
return;
|
||||
}
|
||||
loadingTenantUsers.value = true;
|
||||
try {
|
||||
const res = await getTenantUserList({ tid });
|
||||
if (res.code === 200) {
|
||||
tenantUsersList.value = res.data?.list || res.data || [];
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
loadingTenantUsers.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 根据用户ID反查所属租户(编辑"租户用户"历史消息时回显租户)
|
||||
const resolveUserTenant = async (ids: number[]): Promise<number | null> => {
|
||||
if (!ids.length) return null;
|
||||
try {
|
||||
const res = await getTenantUserList({ uid: ids[0] });
|
||||
if (res.code === 200) {
|
||||
const rows = res.data?.list || res.data || [];
|
||||
return rows.length ? Number(rows[0].tid) : null;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// 解析历史数据的多选目标ID
|
||||
const parseTargetIds = (raw: any): number[] => {
|
||||
let ids: any[] = [];
|
||||
if (Array.isArray(raw)) {
|
||||
ids = raw;
|
||||
} else if (typeof raw === "string" && raw.trim()) {
|
||||
try {
|
||||
const arr = JSON.parse(raw);
|
||||
if (Array.isArray(arr)) ids = arr;
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return ids.map((n) => Number(n)).filter((n) => !Number.isNaN(n) && n > 0);
|
||||
};
|
||||
|
||||
// 接收目标切换:清空已选目标并预加载选项
|
||||
const handleTargetTypeChange = (val: any) => {
|
||||
form.value.target_ids = [];
|
||||
form.value.target_role_id = null;
|
||||
form.value.user_tenant_id = null;
|
||||
tenantUsersList.value = [];
|
||||
|
||||
if (val === "tenant_role") {
|
||||
loadRoles();
|
||||
loadTenants(); // 用于角色标签中展示所属租户名称
|
||||
} else if (val === "role") {
|
||||
loadRoles();
|
||||
} else if (val === "tenant") {
|
||||
loadTenants();
|
||||
} else if (val === "tenant_user") {
|
||||
loadTenants();
|
||||
}
|
||||
};
|
||||
|
||||
// "租户用户"模式下切换租户
|
||||
const handleUserTenantChange = (tid: number) => {
|
||||
form.value.target_ids = [];
|
||||
loadTenantUsers(tid);
|
||||
};
|
||||
|
||||
const handleSend = async () => {
|
||||
if (!formRef.value) return;
|
||||
try {
|
||||
@@ -197,16 +382,28 @@ const handleSend = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const needIds = MULTI_TARGET_TYPES.includes(form.value.target_type);
|
||||
if (needIds && form.value.target_ids.length === 0) {
|
||||
ElMessage.warning("请选择接收目标");
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
title: form.value.title,
|
||||
content: form.value.content,
|
||||
target_type: form.value.target_type,
|
||||
target_ids: needIds ? form.value.target_ids : [],
|
||||
target_role_id:
|
||||
form.value.target_type === "role" ? form.value.target_role_id || 0 : 0,
|
||||
target_tenant_id: 0
|
||||
};
|
||||
|
||||
submitting.value = true;
|
||||
try {
|
||||
if (isEdit.value) {
|
||||
const res = await updateSentSiteReminder({
|
||||
batch_id: props.reminder.batch_id,
|
||||
title: form.value.title,
|
||||
content: form.value.content,
|
||||
target_type: form.value.target_type,
|
||||
target_role_id: form.value.target_role_id || 0,
|
||||
target_tenant_id: form.value.target_tenant_id || 0
|
||||
...payload
|
||||
});
|
||||
if (res.code === 200) {
|
||||
ElMessage.success("修改成功");
|
||||
@@ -216,13 +413,6 @@ const handleSend = async () => {
|
||||
ElMessage.error(res.msg || "修改失败");
|
||||
}
|
||||
} else {
|
||||
const payload = {
|
||||
title: form.value.title,
|
||||
content: form.value.content,
|
||||
target_type: form.value.target_type,
|
||||
target_role_id: form.value.target_role_id || 0,
|
||||
target_tenant_id: form.value.target_tenant_id || 0
|
||||
};
|
||||
const res = await sendSiteReminder(payload);
|
||||
if (res.code === 200) {
|
||||
ElMessage.success("站内信发布成功");
|
||||
@@ -239,34 +429,56 @@ const handleSend = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.modelValue, (val) => {
|
||||
if (val) {
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async (val) => {
|
||||
if (!val) return;
|
||||
|
||||
if (props.reminder) {
|
||||
const targetType = props.reminder.target_type || "platform";
|
||||
const ids = parseTargetIds(props.reminder.target_ids);
|
||||
// 兼容旧版"特定租户"单值
|
||||
if (targetType === "tenant" && ids.length === 0 && props.reminder.target_tenant_id) {
|
||||
ids.push(Number(props.reminder.target_tenant_id));
|
||||
}
|
||||
|
||||
form.value = {
|
||||
title: props.reminder.title,
|
||||
content: props.reminder.content,
|
||||
target_type: props.reminder.target_type || "platform",
|
||||
target_type: targetType,
|
||||
target_ids: MULTI_TARGET_TYPES.includes(targetType) ? ids : [],
|
||||
target_role_id: props.reminder.target_role_id || null,
|
||||
target_tenant_id: props.reminder.target_tenant_id || null
|
||||
user_tenant_id: null
|
||||
};
|
||||
if (form.value.target_type === "role") {
|
||||
loadRoles();
|
||||
} else if (form.value.target_type === "tenant") {
|
||||
loadTenants();
|
||||
|
||||
if (targetType === "tenant_role") {
|
||||
await loadRoles();
|
||||
await loadTenants();
|
||||
} else if (targetType === "role") {
|
||||
await loadRoles();
|
||||
} else if (targetType === "tenant") {
|
||||
await loadTenants();
|
||||
} else if (targetType === "tenant_user") {
|
||||
await loadTenants();
|
||||
const tid = await resolveUserTenant(ids);
|
||||
if (tid) {
|
||||
form.value.user_tenant_id = tid;
|
||||
await loadTenantUsers(tid);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
form.value = {
|
||||
title: "",
|
||||
content: "",
|
||||
target_type: "platform",
|
||||
target_ids: [],
|
||||
target_role_id: null,
|
||||
target_tenant_id: null
|
||||
user_tenant_id: null
|
||||
};
|
||||
rolesList.value = [];
|
||||
tenantsList.value = [];
|
||||
tenantUsersList.value = [];
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -275,4 +487,10 @@ watch(() => props.modelValue, (val) => {
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.target-radio-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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<any[]>([]);
|
||||
@@ -124,41 +125,65 @@ const currentReminder = ref<any>({});
|
||||
// 接收目标标签类型
|
||||
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;
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ detailData.create_time || '-' }}
|
||||
{{ detailData.create_time ? formatDateTime(detailData.create_time) : '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -104,7 +104,9 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" label="申请时间" width="170" />
|
||||
<el-table-column prop="create_time" label="申请时间" width="170">
|
||||
<template #default="{ row }">{{ formatDateTime(row.create_time) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="230" fixed="right" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
@@ -262,6 +264,7 @@ import {
|
||||
getCustomDomainGuide,
|
||||
} from "@/api/domain";
|
||||
import { getTenantList } from "@/api/tenant";
|
||||
import { formatDateTime } from "@/utils/datetime";
|
||||
|
||||
// 域名类型:与后端 models.DomainTypeCustom 对应
|
||||
const DOMAIN_TYPE_CUSTOM = 2;
|
||||
|
||||
@@ -48,10 +48,10 @@
|
||||
{{ user.login_count || 0 }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ user.create_time || "未知" }}
|
||||
{{ user.create_time ? formatDateTime(user.create_time) : "未知" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间" :span="2">
|
||||
{{ user.update_time || "未知" }}
|
||||
{{ user.update_time ? formatDateTime(user.update_time) : "未知" }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
@@ -66,6 +66,7 @@
|
||||
import { ref, watch } from "vue";
|
||||
import { UserFilled } from "@element-plus/icons-vue";
|
||||
import { getAllRoles } from "@/api/role";
|
||||
import { formatDateTime } from "@/utils/datetime";
|
||||
|
||||
interface User {
|
||||
id: number;
|
||||
|
||||
@@ -100,6 +100,8 @@ import { Plus, Edit, Delete } from "@element-plus/icons-vue";
|
||||
import type { FormInstance, FormRules } from "element-plus";
|
||||
|
||||
import { getModuleCategory,editModuleCategory } from "@/api/moduleCenter";
|
||||
// 该页面展示精确到秒的时间:YYYY-MM-DD HH:mm:ss
|
||||
import { formatDateTime as formatDate } from "@/utils/datetime";
|
||||
|
||||
interface Category {
|
||||
id: number;
|
||||
@@ -160,16 +162,7 @@ const getStatusType = (status: number) => {
|
||||
return map[status] || "info";
|
||||
};
|
||||
|
||||
const formatDate = (date: string) => {
|
||||
if (!date) return "-";
|
||||
const d = new Date(date);
|
||||
const year = d.getFullYear();
|
||||
const month = String(d.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(d.getDate()).padStart(2, "0");
|
||||
const hours = String(d.getHours()).padStart(2, "0");
|
||||
const minutes = String(d.getMinutes()).padStart(2, "0");
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||
};
|
||||
|
||||
|
||||
const handleCategoryAdd = () => {
|
||||
isEdit.value = false;
|
||||
|
||||
@@ -146,6 +146,8 @@ import type { FormInstance, FormRules } from "element-plus";
|
||||
import { getModules, getModuleCategory,editModules } from "@/api/moduleCenter";
|
||||
import CreateModules from "../components/createModules.vue";
|
||||
import { uploadFile } from '@/api/file.js';
|
||||
// 该页面展示精确到秒的时间:YYYY-MM-DD HH:mm:ss
|
||||
import { formatDateTime as formatDate } from "@/utils/datetime";
|
||||
|
||||
interface Module {
|
||||
id: number;
|
||||
@@ -266,16 +268,7 @@ function handlePictureCardPreview(file: any) {
|
||||
|
||||
|
||||
|
||||
const formatDate = (date: string) => {
|
||||
if (!date) return "-";
|
||||
const d = new Date(date);
|
||||
const year = d.getFullYear();
|
||||
const month = String(d.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(d.getDate()).padStart(2, "0");
|
||||
const hours = String(d.getHours()).padStart(2, "0");
|
||||
const minutes = String(d.getMinutes()).padStart(2, "0");
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||
};
|
||||
|
||||
|
||||
const handleCreate = () => {
|
||||
createDialogVisible.value = true;
|
||||
|
||||
@@ -172,6 +172,8 @@
|
||||
import { ref, reactive, onMounted } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { Plus, View, Delete, UploadFilled, Document } from "@element-plus/icons-vue";
|
||||
// 该页面展示精确到秒的时间:YYYY-MM-DD HH:mm:ss
|
||||
import { formatDateTime as formatDate } from "@/utils/datetime";
|
||||
import type { FormInstance, FormRules, UploadFile } from "element-plus";
|
||||
|
||||
import { getModules, getModuleCategory } from "@/api/moduleCenter";
|
||||
@@ -245,16 +247,7 @@ const getStatusType = (status: number) => {
|
||||
return map[status] || "info";
|
||||
};
|
||||
|
||||
const formatDate = (date: string) => {
|
||||
if (!date) return "-";
|
||||
const d = new Date(date);
|
||||
const year = d.getFullYear();
|
||||
const month = String(d.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(d.getDate()).padStart(2, "0");
|
||||
const hours = String(d.getHours()).padStart(2, "0");
|
||||
const minutes = String(d.getMinutes()).padStart(2, "0");
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||
};
|
||||
|
||||
|
||||
const handlePublish = () => {
|
||||
publishDialogVisible.value = true;
|
||||
|
||||
@@ -280,6 +280,8 @@
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { getFileUrl as getFileUrlUtil } from "@/utils/url";
|
||||
// 日期显示统一为:YYYY-MM-DD
|
||||
import { formatDate } from "@/utils/datetime";
|
||||
import {
|
||||
Folder,
|
||||
Grid,
|
||||
@@ -683,14 +685,7 @@ const formatFileSize = (bytes: number): string => {
|
||||
};
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateStr: string): string => {
|
||||
const date = new Date(dateStr);
|
||||
return date.toLocaleDateString("zh-CN", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 文件操作
|
||||
const handleFileClick = (file: FileItem) => {
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="登录时间" label-width="150px">
|
||||
{{ log.create_time }}
|
||||
{{ formatDateTime(log.create_time) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="IP地址" label-width="150px">
|
||||
{{ log.ip || "-" }}
|
||||
@@ -50,6 +50,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from "vue";
|
||||
import { getLoginLogDetail } from "@/api/loginLog";
|
||||
import { formatDateTime } from "@/utils/datetime";
|
||||
|
||||
interface LoginLog {
|
||||
id: number;
|
||||
|
||||
@@ -128,12 +128,9 @@
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="ip" label="IP地址" width="130" align="center" />
|
||||
<el-table-column
|
||||
prop="create_time"
|
||||
label="登录时间"
|
||||
width="180"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column prop="create_time" label="登录时间" width="180" align="center">
|
||||
<template #default="{ row }">{{ formatDateTime(row.create_time) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
@@ -176,6 +173,7 @@ import { ElMessage } from "element-plus";
|
||||
import { Refresh, Search } from "@element-plus/icons-vue";
|
||||
import { getLoginLogs } from "@/api/loginLog";
|
||||
import DetailDialog from "./components/detail.vue";
|
||||
import { formatDateTime } from "@/utils/datetime";
|
||||
|
||||
interface LoginLog {
|
||||
id: number;
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
{{ log.ip }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="操作时间" label-width="150px" :span="2">
|
||||
{{ log.create_time }}
|
||||
{{ formatDateTime(log.create_time) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="用户代理" label-width="150px" :span="2">
|
||||
<div class="user-agent-text">{{ log.user_agent || "无" }}</div>
|
||||
@@ -74,6 +74,7 @@
|
||||
import { ref, watch } from "vue";
|
||||
import { Document } from "@element-plus/icons-vue";
|
||||
import { getOperationLogDetail } from "@/api/operationLog";
|
||||
import { formatDateTime } from "@/utils/datetime";
|
||||
|
||||
interface OperationLog {
|
||||
id: number;
|
||||
|
||||
@@ -117,12 +117,9 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="create_time"
|
||||
label="操作时间"
|
||||
width="180"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column prop="create_time" label="操作时间" width="180" align="center">
|
||||
<template #default="{ row }">{{ formatDateTime(row.create_time) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
@@ -170,6 +167,7 @@ import {
|
||||
getOperationStatistics,
|
||||
} from "@/api/operationLog";
|
||||
import DetailDialog from "./components/detail.vue";
|
||||
import { formatDateTime } from "@/utils/datetime";
|
||||
|
||||
interface OperationLog {
|
||||
id: number;
|
||||
|
||||
@@ -58,7 +58,9 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" label="创建时间" width="180" />
|
||||
<el-table-column prop="create_time" label="创建时间" width="180">
|
||||
<template #default="{ row }">{{ formatDateTime(row.create_time) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination" v-if="total > 0">
|
||||
@@ -81,6 +83,7 @@ import { reactive, ref, onMounted } from "vue";
|
||||
import { Refresh } from "@element-plus/icons-vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { getSmsTaskList } from "../../../api/sms";
|
||||
import { formatDateTime } from "@/utils/datetime";
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
|
||||
@@ -55,7 +55,9 @@
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" label="登记时间" width="170" />
|
||||
<el-table-column prop="create_time" label="登记时间" width="170">
|
||||
<template #default="{ row }">{{ formatDateTime(row.create_time) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button size="small" text type="primary" @click="openEditor(scope.row)">
|
||||
@@ -131,6 +133,7 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Upload, Refresh } from '@element-plus/icons-vue'
|
||||
import { formatDateTime } from '@/utils/datetime'
|
||||
import {
|
||||
getTemplateList,
|
||||
scanTemplates,
|
||||
|
||||
@@ -97,11 +97,11 @@
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="创建时间" label-width="130px">
|
||||
{{ detail.create_time || '—' }}
|
||||
{{ detail.create_time ? formatDateTime(detail.create_time) : '—' }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="更新时间" label-width="130px">
|
||||
{{ detail.update_time || '—' }}
|
||||
{{ detail.update_time ? formatDateTime(detail.update_time) : '—' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
@@ -124,6 +124,7 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getAgentApiDetail } from '@/api/agentapimanagement'
|
||||
import { formatDateTime } from '@/utils/datetime'
|
||||
import {
|
||||
providerLabel,
|
||||
providerColor,
|
||||
|
||||
@@ -203,7 +203,7 @@
|
||||
|
||||
<!-- 卡片底部 -->
|
||||
<div class="card-foot">
|
||||
<span class="foot-time">{{ row.create_time || '' }}</span>
|
||||
<span class="foot-time">{{ row.create_time ? formatDateTime(row.create_time) : '' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -272,6 +272,7 @@ import {
|
||||
import AgentApiEdit from './components/edit.vue'
|
||||
import AgentApiDetail from './components/detail.vue'
|
||||
import AgentApiTest from './components/test.vue'
|
||||
import { formatDateTime } from '@/utils/datetime'
|
||||
|
||||
const editRef = ref(null)
|
||||
const detailRef = ref(null)
|
||||
|
||||
@@ -77,6 +77,7 @@ import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Plus, Search, Delete, MoreFilled } from '@element-plus/icons-vue';
|
||||
import NoteEditor from './components/edit.vue';
|
||||
import { getNotebookList, deleteNotebook } from '@/api/notebook';
|
||||
import { formatDate } from '@/utils/datetime';
|
||||
|
||||
const searchKeyword = ref('');
|
||||
const currentNoteId = ref(null);
|
||||
@@ -117,11 +118,7 @@ const formatTime = (dateStr) => {
|
||||
} else if (diff < 7 * day) {
|
||||
return `${Math.floor(diff / day)} 天前`;
|
||||
} else {
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
});
|
||||
return formatDate(date);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -245,6 +245,8 @@ import {
|
||||
} from "@element-plus/icons-vue";
|
||||
import { getUserInfo, updateUserInfo } from "@/api/user";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
// 该页面展示精确到秒的时间:YYYY-MM-DD HH:mm:ss
|
||||
import { formatDateTime as formatDate } from "@/utils/datetime";
|
||||
|
||||
const router = useRouter();
|
||||
const authStore = useAuthStore();
|
||||
@@ -304,16 +306,7 @@ const passwordFormRules = {
|
||||
]
|
||||
};
|
||||
|
||||
const formatDate = (date) => {
|
||||
if (!date) return "-";
|
||||
const d = new Date(date);
|
||||
const year = d.getFullYear();
|
||||
const month = String(d.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(d.getDate()).padStart(2, "0");
|
||||
const hours = String(d.getHours()).padStart(2, "0");
|
||||
const minutes = String(d.getMinutes()).padStart(2, "0");
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||
};
|
||||
|
||||
|
||||
const getSexText = (sex) => {
|
||||
const map = {
|
||||
|
||||
Reference in New Issue
Block a user