diff --git a/backend/src/views/apps/cms/templates/index.vue b/backend/src/views/apps/cms/templates/index.vue index e834ea6..7138d5f 100644 --- a/backend/src/views/apps/cms/templates/index.vue +++ b/backend/src/views/apps/cms/templates/index.vue @@ -278,4 +278,50 @@ onMounted(() => { } } } + +// ===== 暗色主题适配 ===== +html.dark & { + .template-layout { + background-color: var(--el-bg-color-page); + } + + .category-sidebar { + background: var(--el-bg-color); + border-right-color: var(--el-border-color); + + .sidebar-title { + color: var(--el-text-color-primary); + } + } + + .content-area { + .result-count { + color: var(--el-text-color-regular); + } + + .loading-state { + color: var(--el-text-color-secondary); + } + + .template-card { + background: var(--el-bg-color); + border-color: var(--el-border-color-lighter); + + .card-preview { + background: var(--el-fill-color-light); + } + + .card-info { + .title { + color: var(--el-text-color-primary); + } + + .description, + .meta { + color: var(--el-text-color-secondary); + } + } + } + } +} diff --git a/backend/src/views/apps/erp/dashboard/index.vue b/backend/src/views/apps/erp/dashboard/index.vue index d4dc3aa..42eeb48 100644 --- a/backend/src/views/apps/erp/dashboard/index.vue +++ b/backend/src/views/apps/erp/dashboard/index.vue @@ -111,6 +111,10 @@ const getMonthLabels = (): string[] => { const initChart = (refEl: HTMLElement, title: string, color: string) => { const chart = echarts.init(refEl); const months = getMonthLabels(); + const isDark = document.documentElement.classList.contains('dark'); + const palette = isDark + ? { axis: '#4c4d4f', label: '#a8abb2', split: '#2b2b2d' } + : { axis: '#E0E6ED', label: '#606266', split: '#F2F6FC' }; chart.setOption({ tooltip: { @@ -128,15 +132,15 @@ const initChart = (refEl: HTMLElement, title: string, color: string) => { type: 'category', boundaryGap: false, data: months, - axisLine: { lineStyle: { color: '#E0E6ED' } }, - axisLabel: { color: '#606266', fontSize: 11 } + axisLine: { lineStyle: { color: palette.axis } }, + axisLabel: { color: palette.label, fontSize: 11 } }, yAxis: { type: 'value', axisLine: { show: false }, axisTick: { show: false }, - splitLine: { lineStyle: { color: '#F2F6FC' } }, - axisLabel: { color: '#606266', fontSize: 11 } + splitLine: { lineStyle: { color: palette.split } }, + axisLabel: { color: palette.label, fontSize: 11 } }, series: [ { @@ -386,4 +390,41 @@ onUnmounted(() => { height: 280px; } } + +// ===== 暗色主题适配 ===== +html.dark & { + .erp-dashboard { + background-color: var(--el-bg-color-page); + } + + .stat-card { + background: var(--el-bg-color); + + .stat-header { + .stat-label { + color: var(--el-text-color-regular); + } + + .stat-icon { + color: var(--el-text-color-secondary); + } + } + + .stat-value { + .currency, + .number { + color: var(--el-text-color-primary); + } + } + } + + .chart-card { + background: var(--el-bg-color); + + .chart-title { + color: var(--el-text-color-primary); + border-bottom-color: var(--el-border-color-lighter); + } + } +} diff --git a/backend/src/views/apps/oa/compensation/index.vue b/backend/src/views/apps/oa/compensation/index.vue index 1082560..5b98918 100644 --- a/backend/src/views/apps/oa/compensation/index.vue +++ b/backend/src/views/apps/oa/compensation/index.vue @@ -434,7 +434,7 @@ async function removeScheme(row) { onMounted(async () => { await Promise.all([loadEmployees(), loadSchemes(), loadDashboard(), loadPayrolls()]) }) - diff --git a/backend/src/views/apps/oa/contact/index.vue b/backend/src/views/apps/oa/contact/index.vue index 686b1fd..0234e4c 100644 --- a/backend/src/views/apps/oa/contact/index.vue +++ b/backend/src/views/apps/oa/contact/index.vue @@ -631,4 +631,63 @@ onMounted(() => { flex-wrap: wrap; } } +// ===== 暗色主题适配 ===== +html.dark & { + .contact-page { + background: var(--el-bg-color-page); + } + + .page-header { + h2 { + color: var(--el-text-color-primary); + } + p { + color: var(--el-text-color-secondary); + } + } + + .org-tree-panel { + background: var(--el-bg-color); + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + + .tree-header { + border-bottom-color: var(--el-border-color-light); + color: var(--el-text-color-primary); + } + + .node-count { + color: var(--el-text-color-secondary); + background: var(--el-fill-color); + } + } + + .contact-main { + background: var(--el-bg-color); + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + + .filter-bar { + border-bottom-color: var(--el-border-color-light); + } + } + + .pagination-wrap { + border-top-color: var(--el-border-color-light); + } + + .contact-name { + color: var(--el-color-primary); + } + + .star-icon { + color: var(--el-text-color-placeholder); + + &:hover { + color: #e6a23c; + } + + &.starred { + color: #e6a23c; + } + } +} diff --git a/backend/src/views/apps/oa/dashboard/index.vue b/backend/src/views/apps/oa/dashboard/index.vue index 47f0b57..45a566b 100644 --- a/backend/src/views/apps/oa/dashboard/index.vue +++ b/backend/src/views/apps/oa/dashboard/index.vue @@ -170,6 +170,7 @@ class="schedule-item-title" :class="{ done: item.status === 1 }" > + {{ item.title }} [ // 趋势图 const trendChartRef = ref(null); let trendChart = null; +let themeObserver = null; const renderTrendChart = () => { if (!trendChartRef.value) return; if (!trendChart) trendChart = echarts.init(trendChartRef.value); + const dark = document.documentElement.classList.contains("dark"); + const axisLineColor = dark ? "#3a3a3a" : "#E0E6ED"; + const labelColor = dark ? "#A3A6AD" : "#606266"; + const splitColor = dark ? "#2a2a2a" : "#F2F6FC"; const months = dashboard.trend.map((item) => item.month.slice(5) + "月"); trendChart.setOption({ tooltip: { trigger: "axis", axisPointer: { type: "shadow" } }, - legend: { data: ["已报销", "未报销"], top: 0 }, + legend: { data: ["已报销", "未报销"], top: 0, textStyle: { color: labelColor } }, grid: { left: "3%", right: "4%", @@ -313,15 +319,15 @@ const renderTrendChart = () => { xAxis: { type: "category", data: months, - axisLine: { lineStyle: { color: "#E0E6ED" } }, - axisLabel: { color: "#606266", fontSize: 11 }, + axisLine: { lineStyle: { color: axisLineColor } }, + axisLabel: { color: labelColor, fontSize: 11 }, }, yAxis: { type: "value", axisLine: { show: false }, axisTick: { show: false }, - splitLine: { lineStyle: { color: "#F2F6FC" } }, - axisLabel: { color: "#606266", fontSize: 11 }, + splitLine: { lineStyle: { color: splitColor } }, + axisLabel: { color: labelColor, fontSize: 11 }, }, series: [ { @@ -476,6 +482,22 @@ const scheduleDateTag = (item) => { return `${m}/${d} 周${week}`; }; +// 判断日程是否关联了图片(与详情页 parseImages 语义一致),用于列表标题前展示图片图标 +function hasImages(item) { + const imgs = item?.images; + if (!imgs) return false; + if (Array.isArray(imgs)) return imgs.some(img => img && img.url); + if (typeof imgs === "string" && imgs.trim()) { + try { + const parsed = JSON.parse(imgs); + return Array.isArray(parsed) && parsed.some(img => img && img.url); + } catch { + return false; + } + } + return false; +} + const scheduleTimeText = (item) => item.all_day === 1 ? "全天" @@ -609,10 +631,18 @@ const statusType = (value) => onMounted(async () => { await loadDashboard(); window.addEventListener("resize", handleResize); + // 主题切换时重绘图表,使坐标轴/文字颜色跟随暗色 + themeObserver = new MutationObserver(() => renderTrendChart()); + themeObserver.observe(document.documentElement, { + attributes: true, + attributeFilter: ["class"], + }); }); onBeforeUnmount(() => { window.removeEventListener("resize", handleResize); + themeObserver?.disconnect(); + themeObserver = null; trendChart?.dispose(); trendChart = null; }); @@ -969,6 +999,12 @@ onBeforeUnmount(() => { } } + .schedule-image-icon { + flex-shrink: 0; + margin-right: 4px; + color: var(--el-text-color-secondary); + } + .schedule-item-time { display: flex; gap: 10px; @@ -1128,4 +1164,149 @@ onBeforeUnmount(() => { width: auto; } } + +// ===== 暗色主题适配 ===== +html.dark & { + .oa-dashboard { + background: var(--el-bg-color-page); + } + + .page-header { + h2 { + color: var(--el-text-color-primary); + } + p { + color: var(--el-text-color-secondary); + } + } + + .stat-card { + background: var(--el-bg-color); + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + + .stat-title { + color: var(--el-text-color-secondary); + } + + .stat-amount { + .currency, + .number { + color: var(--el-text-color-primary); + } + } + + .stat-meta { + color: var(--el-text-color-secondary); + } + } + + .chart-card { + background: var(--el-bg-color); + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + + .chart-title { + color: var(--el-text-color-primary); + border-bottom-color: var(--el-border-color-lighter); + } + } + + .notice-item { + background: var(--el-fill-color-light); + + &:hover { + background: var(--el-fill-color); + } + + .notice-text { + color: var(--el-text-color-primary); + } + + .notice-time { + color: var(--el-text-color-placeholder); + } + } + + .notice-detail-meta { + border-bottom-color: var(--el-border-color-lighter); + } + + .notice-detail-text { + color: var(--el-text-color-secondary); + } + + .notice-detail-content { + color: var(--el-text-color-primary); + } + + .tab-count { + background: var(--el-fill-color); + color: var(--el-text-color-secondary); + } + + .schedule-item { + background: var(--el-fill-color-light); + + &:hover { + background: var(--el-fill-color); + } + + &.done { + background: var(--el-bg-color-page); + border-left-color: var(--el-border-color); + } + + .item-status-dot.done { + background: var(--el-text-color-placeholder); + } + + .schedule-item-title { + color: var(--el-text-color-primary); + + &.done { + color: var(--el-text-color-placeholder); + } + } + + .schedule-item-time { + color: var(--el-text-color-secondary); + + &.done { + color: var(--el-text-color-placeholder); + } + } + } + + .schedule-action-title { + color: var(--el-text-color-primary); + } + + .schedule-action-meta { + color: var(--el-text-color-regular); + } + + .schedule-action-tip { + color: var(--el-text-color-secondary); + } + + .dist-bar-wrap, + .type-bar-wrap { + background: var(--el-fill-color); + } + + .dist-count { + color: var(--el-text-color-secondary); + } + + .type-name { + color: var(--el-text-color-regular); + } + + .type-count { + color: var(--el-text-color-secondary); + } + + .type-amount { + color: var(--el-text-color-primary); + } +} diff --git a/backend/src/views/apps/oa/employeefile/index.vue b/backend/src/views/apps/oa/employeefile/index.vue index 9f692a6..385f525 100644 --- a/backend/src/views/apps/oa/employeefile/index.vue +++ b/backend/src/views/apps/oa/employeefile/index.vue @@ -340,4 +340,36 @@ onMounted(() => { justify-content: flex-end; margin-top: 14px; } +// ===== 暗色主题适配 ===== +html.dark & { + .page-header { + h2 { + color: var(--el-text-color-primary); + } + p { + color: var(--el-text-color-secondary); + } + } + + .stat-card { + background: var(--el-bg-color); + border-color: var(--el-border-color-lighter); + + .stat-value { + color: var(--el-text-color-primary); + } + + .stat-label { + color: var(--el-text-color-secondary); + } + + &.info .stat-value { + color: var(--el-text-color-secondary); + } + } + + .name-link { + color: var(--el-color-primary); + } +} diff --git a/backend/src/views/apps/oa/notice/index.vue b/backend/src/views/apps/oa/notice/index.vue index ce2be6f..7207749 100644 --- a/backend/src/views/apps/oa/notice/index.vue +++ b/backend/src/views/apps/oa/notice/index.vue @@ -298,7 +298,7 @@ function formatDateTime(value) { onMounted(loadList); - diff --git a/backend/src/views/apps/oa/reimburse/index.vue b/backend/src/views/apps/oa/reimburse/index.vue index fb1c6cf..78f38ec 100644 --- a/backend/src/views/apps/oa/reimburse/index.vue +++ b/backend/src/views/apps/oa/reimburse/index.vue @@ -511,4 +511,25 @@ onBeforeUnmount(() => { margin-top: 12px; } } +// ===== 暗色主题适配 ===== +html.dark & { + .reimburse-page { + background: var(--el-bg-color-page); + } + + .page-header { + h2 { + color: var(--el-text-color-primary); + } + p { + color: var(--el-text-color-secondary); + } + } + + .filter-card, + .table-card { + background: var(--el-bg-color); + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + } +} diff --git a/backend/src/views/apps/oa/schedule/components/detail.vue b/backend/src/views/apps/oa/schedule/components/detail.vue index 62fdb7d..22faf23 100644 --- a/backend/src/views/apps/oa/schedule/components/detail.vue +++ b/backend/src/views/apps/oa/schedule/components/detail.vue @@ -43,6 +43,36 @@ 延期 {{ carryText }} +
+ 提醒 + + 已关闭 + 提醒中 + {{ channelText(ch) }} + 提前 {{ reminderInfo.advance_minutes }} 分 + 结束提醒 + 加载中… + +
备注 {{ schedule.content }} @@ -144,6 +174,7 @@ import { } from "@element-plus/icons-vue"; import { uploadFile } from "@/api/file"; import { updateScheduleImages } from "@/api/oaSchedule"; +import { getReminderDetail, finishReminder } from "@/api/reminder"; const props = defineProps({ modelValue: { @@ -270,10 +301,63 @@ watch( () => props.schedule, schedule => { imageList.value = parseImages(schedule?.images); + loadReminderInfo(schedule?.reminder_id); }, { immediate: true } ); +// ---------- 绑定提醒的状态回显与反馈 ---------- +const reminderInfo = ref(null); +const reminderLoading = ref(false); + +const channelTextMap = { + SMS: "短信", + EMAIL: "邮件", + BARK: "Bark", + SITE_MSG: "站内信" +}; +function channelText(ch) { + return channelTextMap[ch] || ch; +} + +async function loadReminderInfo(reminderId) { + reminderInfo.value = null; + if (!reminderId) { + return; + } + reminderLoading.value = true; + try { + const res = await getReminderDetail(reminderId); + if (res?.code === 200) { + reminderInfo.value = res.data || null; + } + } catch { + reminderInfo.value = null; + } finally { + reminderLoading.value = false; + } +} + +// 在日程详情直接关闭绑定的提醒任务;关闭后下方标签会回显"已关闭" +async function endReminder() { + const id = props.schedule?.reminder_id; + if (!id) { + return; + } + try { + const res = await finishReminder(id); + if (res?.code === 200) { + ElMessage.success("提醒已关闭"); + await loadReminderInfo(id); + emit("changed", props.schedule.id); + } else { + ElMessage.error(res?.msg || "操作失败"); + } + } catch { + ElMessage.error("操作失败"); + } +} + function validateImage(file) { if (file.type && !file.type.startsWith("image/")) { ElMessage.error("只支持上传图片文件"); @@ -418,7 +502,7 @@ function formatDateTime(value) { } - diff --git a/backend/src/views/apps/oa/schedule/components/edit.vue b/backend/src/views/apps/oa/schedule/components/edit.vue index 9bd75e4..c265874 100644 --- a/backend/src/views/apps/oa/schedule/components/edit.vue +++ b/backend/src/views/apps/oa/schedule/components/edit.vue @@ -77,6 +77,68 @@ placeholder="补充说明(可选)" /> + + 提醒设置 + + + + 开启后将在「提醒工具」生成一条提醒任务,到点时按渠道通知你 + + + @@ -341,7 +357,9 @@ v-model="editVisible" :schedule="editingItem" :default-date="selectedDate" + :draft="pendingDraft" @saved="reloadAll" + @draft-applied="pendingDraft = null" /> @@ -648,6 +666,8 @@ const editVisible = ref(false); const editingItem = ref(null); const detailVisible = ref(false); const detailItem = ref(null); +// 从通知设置页返回时恢复的草稿(含提醒设置) +const pendingDraft = ref(null); function openCreate(date) { editingItem.value = null; @@ -842,6 +862,22 @@ function priorityTagType(priority) { return { 1: "warning", 2: "danger" }[priority] || "info"; } +// 判断日程是否关联了图片(与详情页 parseImages 语义一致),用于列表标题前展示图片图标 +function hasImages(item) { + const imgs = item?.images; + if (!imgs) return false; + if (Array.isArray(imgs)) return imgs.some(img => img && img.url); + if (typeof imgs === "string" && imgs.trim()) { + try { + const parsed = JSON.parse(imgs); + return Array.isArray(parsed) && parsed.some(img => img && img.url); + } catch { + return false; + } + } + return false; +} + // 排序权重:紧急(2) > 重要(1) > 普通(0) function priorityWeight(priority) { return { 2: 3, 1: 2 }[priority] || 1; @@ -864,10 +900,23 @@ function compareByPriority(a, b) { onMounted(() => { loadStats(); loadCalendarData(); + // 从通知设置页返回:恢复上次暂存的新建日程草稿 + const raw = sessionStorage.getItem("oa_schedule_draft"); + if (raw) { + try { + pendingDraft.value = JSON.parse(raw); + sessionStorage.removeItem("oa_schedule_draft"); + editingItem.value = null; + editVisible.value = true; + ElMessage.info("已从暂存恢复未提交的新建日程"); + } catch { + sessionStorage.removeItem("oa_schedule_draft"); + } + } }); - diff --git a/backend/src/views/apps/organization/components/OrganizationPage.vue b/backend/src/views/apps/organization/components/OrganizationPage.vue index 0d1f79f..1a97534 100644 --- a/backend/src/views/apps/organization/components/OrganizationPage.vue +++ b/backend/src/views/apps/organization/components/OrganizationPage.vue @@ -614,4 +614,32 @@ onMounted(() => { width: 100%; } } + +// ===== 暗色主题适配 ===== +html.dark & { + .page-header { + h2 { + color: var(--el-text-color-primary); + } + p { + color: var(--el-text-color-secondary); + } + } + + .stat-value { + color: var(--el-text-color-primary); + } + + .stat-label { + color: var(--el-text-color-secondary); + } + + .node-label { + color: var(--el-text-color-primary); + } + + .section-title { + color: var(--el-text-color-primary); + } +} diff --git a/backend/src/views/basicSettings/roles/components/detail.vue b/backend/src/views/basicSettings/roles/components/detail.vue index 188f23c..c9a97dd 100644 --- a/backend/src/views/basicSettings/roles/components/detail.vue +++ b/backend/src/views/basicSettings/roles/components/detail.vue @@ -13,12 +13,17 @@ - - {{ roleDetail.cid === 1 ? "平台角色" : "租户角色" }} + 平台角色 + + 自定义角色 + 租户角色 -
+
+ 全权限(未做限制) +
+
import { ref, watch, computed } from "vue"; import { ElMessage } from "element-plus"; -import { getPlatformRoleById } from "@/api/platformRole"; +import { getRoleById } from "@/api/role"; import { getAllMenus } from "@/api/menu"; interface Props { @@ -76,7 +81,7 @@ watch( visible.value = val; if (val && props.roleId) { await loadRoleDetail(); - await loadMenus(roleDetail.value.cid); + await loadMenus(); } } ); @@ -132,7 +137,7 @@ const loadRoleDetail = async () => { loading.value = true; try { - const res = await getPlatformRoleById(props.roleId); + const res = await getRoleById(props.roleId); if (res.code === 200) { roleDetail.value = res.data || {}; } else { @@ -146,12 +151,10 @@ const loadRoleDetail = async () => { } }; -// 加载菜单树:租户角色(cid=2)只展示租户端菜单;平台角色展示全部权限 -const loadMenus = async (cid?: number) => { +// 加载菜单树:租户端只展示租户端菜单(cid=2) +const loadMenus = async () => { try { - const isTenant = cid === undefined || cid === 2; - const params = isTenant ? { cid: 2 } : {}; - const res = await getAllMenus(params); + const res = await getAllMenus({ cid: 2 }); if (res.code === 200) { allMenus.value = res.data || []; } diff --git a/backend/src/views/basicSettings/roles/components/edit.vue b/backend/src/views/basicSettings/roles/components/edit.vue index d3bbe49..f28e048 100644 --- a/backend/src/views/basicSettings/roles/components/edit.vue +++ b/backend/src/views/basicSettings/roles/components/edit.vue @@ -1,5 +1,10 @@