style: 重塑政务风格 + 操作列统一 el-link + 表格紧凑化

业务背景
- ry-vue3 为政务/卫健委风合规管理系统 (医疗机构合规系统), 不要可爱配色、不要插画
- 品牌色: 主色 #15803D (低调深绿) + 次色 #0E7490 (青蓝), 不要蓝色系主色

变更概要
1. 全局地基
   - theme.scss: Element Plus 主色改为 #15803D, slate 9 档, 联动组件库
   - main.scss: 字体/页面骨架/筛选栏/表格/控件/滚动条 全套样式
   - AdminLayout.vue: 政务顶栏 + 浅色侧栏
   - Workbench.vue: 替换原绿底大字卡为 KPI 白底色条 + 快捷入口 + 进度条

2. 表格操作列改造
   - el-button link 全面替换为 el-link (:underline="false" + type="primary/danger")
   - 36 文件 132 处替换, 操作列外加 .table-actions 容器 (内含分隔线)
   - 修复 el-link 不支持的属性 (size="small"/text/loading)

3. 筛选栏
   - 弹性布局 + gap 14px 16px + align-items: flex-start + min-height 32px
   - 删除所有 scoped .filter-form 覆盖 (28 处) 让全局样式生效
   - 控件宽度 180px 统一, 标签 32px 居中, 折行不紧贴

4. 表格紧凑化
   - cell-padding 10px → 6px, 表头高度 36px
   - 行高 ~46px → ~36px, 字号 13px

5. 每页默认 20 条
   - 21 文件 pageSize: 10 → 20

6. meetings 操作列折叠
   - 11 操作 → 外置 3 个主操作 + 1 个"更多" el-dropdown (8 个次操作)
   - width 500px → 260px
   - 配 .op-dropdown / .op-caret 样式 (hover 旋转)
   - 新增 onMoreAction dispatch 函数 (复用原 handler)

7. 操作列文字防拆行
   - .table-actions flex-wrap: nowrap + white-space: nowrap
   - el-link padding 0 8px → 0 6px, flex-shrink: 0

8. 环境配置
   - .env.development: 直连远端 https://risingdoctor.com/hg-api (绕过 vite proxy 卡顿)
   - vite.config.js: proxy fallback 保留
This commit is contained in:
guoju0808
2026-08-26 11:04:15 +00:00
parent b0256ccac3
commit d2fe5a4035
46 changed files with 975 additions and 451 deletions
+2 -2
View File
@@ -1,2 +1,2 @@
# 开发环境 API 基路径 (Vite dev server proxy 前缀)
VITE_APP_BASE_API = '/dev-api'
# 开发环境 API 基路径: 直连远程后端 (绕过 Vite proxy, 避免 HTTPS 代理卡住)
VITE_APP_BASE_API = 'https://risingdoctor.com/hg-api'
+269 -7
View File
@@ -1,7 +1,269 @@
html, body, #app { height: 100%; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; }
:root { --theme-color: #409eff; --theme-color-light: #66b1ff; --theme-color-dark: #3a8ee6; }
.page-wrap { padding: 16px; }
.page-card { background: #fff; border-radius: 6px; padding: 16px; box-shadow: 0 1px 4px rgba(0,21,41,.08); margin-bottom: 16px; }
.page-title { font-size: 18px; font-weight: 600; margin-bottom: 12px; color: #1f1f1f; }
.filter-bar { margin-bottom: 12px; }
.toolbar { margin-bottom: 12px; display: flex; gap: 8px; }
/* =============================================================
全局样式 — 医疗机构合规系统
风格:政务稳重 / 信息密度合理 / 中文优先 / 浅灰底 + 卡片
============================================================= */
html, body, #app {
height: 100%;
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
"Hiragino Sans GB", "Microsoft YaHei", "微软雅黑",
"Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.6;
color: var(--el-text-color-primary);
background: var(--brand-slate-50);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
*, *::before, *::after { box-sizing: border-box; }
a { color: var(--brand-primary-text); text-decoration: none; }
a:hover { color: var(--brand-primary); }
h1, h2, h3, h4, h5, h6 { margin: 0; font-weight: 600; color: var(--el-text-color-primary); }
/* —— 滚动条 (政务: 低调, 不发光) —— */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--brand-slate-300); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--brand-slate-400); }
/* —— 页面骨架 —— */
.page-wrap { padding: 16px 20px; }
/* 卡片容器 (所有后台列表/详情页统一用这个) */
.page-card {
background: #fff;
border-radius: 6px;
padding: 20px 24px;
border: 1px solid var(--brand-slate-200);
margin-bottom: 16px;
}
/* 页面标题 — 政务: 顶部蓝色细横线 + 粗体, 不靠图标 */
.page-title {
font-size: 18px;
font-weight: 600;
color: var(--el-text-color-primary);
margin: 0 0 16px;
padding-left: 12px;
border-left: 4px solid var(--brand-primary);
line-height: 1.2;
}
/* 面包屑 — 政务标准 */
.breadcrumb {
font-size: 13px;
color: var(--el-text-color-secondary);
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 8px;
}
.breadcrumb .sep { color: var(--brand-slate-300); margin: 0 2px; }
.breadcrumb a { color: var(--el-text-color-secondary); cursor: pointer; }
.breadcrumb a:hover { color: var(--brand-primary); }
.breadcrumb .current { color: var(--el-text-color-primary); font-weight: 500; }
/* 筛选区 */
.filter-bar { margin-bottom: 16px; }
/* 筛选区 — 弹性换行 + 控件最小宽度, 上下不紧贴 */
.filter-form {
background: var(--brand-slate-50);
padding: 16px 20px;
border-radius: 4px;
margin-bottom: 16px;
border: 1px solid var(--brand-slate-200);
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: 14px 16px;
}
.filter-form .el-form-item {
margin-bottom: 0 !important;
margin-right: 0 !important;
display: flex;
align-items: center;
}
.filter-form .el-form-item .el-form-item__label {
height: 32px;
line-height: 32px;
padding: 0 8px 0 0;
font-size: 13px;
color: var(--el-text-color-regular);
}
.filter-form .el-form-item .el-form-item__content { line-height: 32px; }
.filter-form .el-input,
.filter-form .el-select,
.filter-form .el-date-editor { width: 180px; }
.filter-form .el-input__wrapper { min-height: 32px; }
.filter-form .el-select__wrapper { min-height: 32px; }
.toolbar { margin-bottom: 16px; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
/* 按钮: 默认带细边框阴影 hover 明显 */
.el-button { font-weight: 500; border-radius: 4px; }
.el-button--primary { box-shadow: 0 1px 2px rgba(21, 128, 61, 0.20); }
.el-button--primary:hover { box-shadow: 0 2px 6px rgba(21, 128, 61, 0.30); }
/* link 按钮 — 修复 link+primary 撞色 (全局) */
:deep(.el-button--primary.is-link),
:deep(.el-button--primary.is-link:hover) {
background: transparent !important;
border-color: transparent !important;
color: var(--brand-primary-text) !important;
box-shadow: none !important;
}
:deep(.el-button--primary.is-link:hover) { color: var(--brand-primary) !important; }
/* el-link 表格操作 — 行内链接, 取代 el-button link */
.el-link {
font-weight: 500;
}
/* 表格操作列容器 — 不换行, 防止外置按钮文字拆行 */
.table-actions {
display: inline-flex;
align-items: center;
flex-wrap: nowrap;
white-space: nowrap;
}
.table-actions .el-link,
.table-actions .el-button.is-link {
font-size: 13px;
font-weight: 500;
padding: 0 6px;
white-space: nowrap;
flex-shrink: 0;
}
.table-actions .el-link + .el-link,
.table-actions .el-button.is-link + .el-button.is-link {
border-left: 1px solid var(--brand-slate-200);
}
.table-actions .el-link.is-disabled {
color: var(--brand-slate-300);
cursor: not-allowed;
}
.table-actions .el-link.el-link--danger { color: var(--el-color-danger); }
.table-actions .el-link.el-link--warning { color: var(--el-color-warning); }
.table-actions .el-link.el-link--success { color: var(--el-color-success); }
/* 表格操作列 — 折叠"更多"下拉触发器 */
.table-actions .op-dropdown {
display: inline-flex;
align-items: center;
gap: 2px;
border-left: 1px solid var(--brand-slate-200);
}
.table-actions .op-caret {
font-size: 10px;
margin-left: 2px;
transition: transform .2s;
}
.table-actions .op-dropdown:hover .op-caret {
transform: rotate(180deg);
}
/* 表格 — 政务: 表头灰底, 行高紧凑, 边框清晰 */
.el-table {
--el-table-border-color: var(--brand-slate-200);
--el-table-header-bg-color: var(--brand-slate-100);
--el-table-header-text-color: var(--el-text-color-primary);
--el-table-row-hover-bg-color: var(--brand-slate-50);
--el-table-cell-padding: 6px 0;
font-size: 13px;
}
.el-table th.el-table__cell {
font-weight: 600;
background: var(--brand-slate-100) !important;
color: var(--el-text-color-primary) !important;
border-bottom: 1px solid var(--brand-slate-300) !important;
padding: 8px 0 !important;
height: 36px !important;
}
.el-table td.el-table__cell { padding: 6px 0; }
.el-table--striped .el-table__body tr.el-table__row--striped td { background: #FCFDFE; }
/* 分页 */
.el-pagination { justify-content: flex-end; margin-top: 16px; padding: 0; }
/* 输入框: 政务感, 边框清晰 */
.el-input__wrapper, .el-textarea__inner, .el-select__wrapper {
box-shadow: 0 0 0 1px var(--brand-slate-300) inset !important;
border-radius: 4px;
}
.el-input__wrapper:hover, .el-select__wrapper:hover {
box-shadow: 0 0 0 1px var(--brand-primary-light) inset !important;
}
.el-input__wrapper.is-focus, .el-select__wrapper.is-focused {
box-shadow: 0 0 0 1px var(--brand-primary) inset !important;
}
/* Tag 标签 — 政务: 圆角小, 颜色低饱和 */
.el-tag {
border-radius: 3px;
font-weight: 500;
}
/* Dialog 标题: 政务感 */
.el-dialog__header { padding: 16px 24px; border-bottom: 1px solid var(--brand-slate-200); margin-right: 0; }
.el-dialog__title { font-size: 16px; font-weight: 600; color: var(--el-text-color-primary); }
.el-dialog__headerbtn { top: 16px; }
.el-dialog__body { padding: 20px 24px; }
.el-dialog__footer { padding: 12px 24px; border-top: 1px solid var(--brand-slate-200); }
/* Menu 侧边栏 (AdminLayout 重写, 这里只是兜底) */
.el-menu { border-right: none !important; }
/* Descriptions 查看页 */
.el-descriptions__label { color: var(--el-text-color-secondary); font-weight: 500; background: var(--brand-slate-50); }
.el-descriptions__content { color: var(--el-text-color-primary); }
/* KPI 卡片 (Workbench 用) */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.stat-card {
background: #fff;
border: 1px solid var(--brand-slate-200);
border-radius: 6px;
padding: 20px;
display: flex;
flex-direction: column;
gap: 8px;
transition: border-color 0.18s, box-shadow 0.18s, transform 0.18s;
cursor: pointer;
text-decoration: none;
color: inherit;
}
.stat-card:hover {
border-color: var(--brand-primary-light);
box-shadow: 0 4px 12px rgba(30, 64, 175, 0.08);
transform: translateY(-1px);
}
.stat-card .stat-label { font-size: 13px; color: var(--el-text-color-secondary); }
.stat-card .stat-value { font-size: 28px; font-weight: 600; color: var(--brand-primary); line-height: 1.1; }
.stat-card .stat-extra { font-size: 12px; color: var(--el-text-color-secondary); }
/* 二级标题 (页面内 section) */
.section-title {
font-size: 15px;
font-weight: 600;
color: var(--el-text-color-primary);
margin: 0 0 14px;
padding-bottom: 10px;
border-bottom: 1px solid var(--brand-slate-200);
}
/* 空状态 */
.empty-block {
padding: 48px 20px;
text-align: center;
color: var(--el-text-color-secondary);
font-size: 13px;
}
+94 -57
View File
@@ -1,70 +1,107 @@
/* =============================================================
品牌主题色(Brand Theme
品牌主题色(Brand Theme— 低调绿色主调
------------------------------------------------------------
所有色值都是 HSL/HEX 函数动态计算的派生色。
改 :root 里的 --brand-primary / --brand-secondary 即可全局换色。
派生色算法(双轨):
深色 (deep/darker) — HSL 调亮度
--brand-primary-deep 暗 12% hover / active / 标题强调)
--brand-primary-darker 暗 22% (顶部导航 / 深底)
浅色 (light/lighter/text/mix) — HSL 降饱和 + 提亮度
之前试过 Element mix(white) 算法, 但 S=100% 的主色提亮后
跟主色对比度太低 (例 #4da2b5 / #4db892), 文字与背景难辨认,
不适合做选中态/hover-bg.
现在: 同时降饱和 (S-N) 和提亮度 (L+N), 色相不变, 但灰度提升,
是 Element 在 S 低基色上的实际效果模拟, 适合政务网站.
--brand-primary-text S-40 L+15 (链接默认, 中蓝可读)
--brand-primary-lighter S-65 L+50 (hover-bg / 选中态, 浅灰蓝)
--brand-primary-light S-65 L+40 (背景 / disabled, 灰中蓝)
--brand-primary-mix S-75 L+60 (输入框 focus border, 近白)
次色 (绿色 #009963) 走同一套算法, 命名 -secondary.
语义上对应 Element Plus success (绿=成功/已批准/已完成), 自动
绑定到 --el-color-success*.
Element Plus 同步:main.js 在启动时读取 --brand-primary 并联动
ElMessage / ElButton 的 --el-color-primary.
主色:#15803D 低饱和深绿 (政务/卫健委稳重型, 不刺眼)
次色:#0E7490 青蓝 (保留原品牌青蓝, 用作次要强调/链接)
配色逻辑:
- 主色绿 = 品牌色 (按钮/标题/侧栏高亮/KPI)
- 次色蓝 = 链接/链接按钮 (政务感链接蓝)
- 成功色继续用次色蓝, 不用重复绿色 (避免色相冲突)
============================================================= */
:root {
/* 主色 — 青蓝 */
--brand-primary: #007A95;
--brand-primary-deep: #004858;
--brand-primary-darker: #002129;
--brand-primary-text: #2d9cb4;
--brand-primary-lighter: #b7d6dd;
--brand-primary-light: #95c2cc;
--brand-primary-mix: #dde8ea;
/* —— 主色:低调深绿 (政务/卫健委风格) —— */
--brand-primary: #15803D;
--brand-primary-deep: #0F5F2E; /* hover / active */
--brand-primary-darker: #073D1D; /* 深底 (登录页 banner / 关键装饰) */
--brand-primary-text: #16A34A; /* 链接/文字绿 (中等可读) */
--brand-primary-lighter: #C8E4D2; /* hover-bg / 选中态底 */
--brand-primary-light: #95C2A8; /* 边框 / disabled */
--brand-primary-mix: #E5F0E9; /* 输入框 focus border / 近白底 */
/* 次色 — 翠绿 */
--brand-secondary: #009963;
--brand-secondary-deep: #005c3b;
--brand-secondary-darker: #00291a;
--brand-secondary-text: #2eb887;
--brand-secondary-lighter: #baded1;
--brand-secondary-light: #98cdba;
--brand-secondary-mix: #dfece7;
/* —— 次色:青蓝 (原品牌色, 用作链接/次要强调) —— */
--brand-secondary: #0E7490;
--brand-secondary-deep: #084454;
--brand-secondary-darker: #04222A;
--brand-secondary-text: #2196BC;
--brand-secondary-lighter: #B7D6DD;
--brand-secondary-light: #95C2CC;
--brand-secondary-mix: #DDE8EA;
/* —— 中性色 slate (政务灰蓝) —— */
--brand-slate-50: #F8FAFC;
--brand-slate-100: #F1F5F9;
--brand-slate-200: #E2E8F0;
--brand-slate-300: #CBD5E1;
--brand-slate-400: #94A3B8;
--brand-slate-500: #64748B;
--brand-slate-600: #475569;
--brand-slate-700: #334155;
--brand-slate-800: #1E293B;
--brand-slate-900: #0F172A;
}
/* Element Plus 联动:把品牌色同步给 Element Plus 内置变量。
这样所有 <el-button type="primary"> / type="success"> 等都跟主题色。 */
/* Element Plus 联动 */
:root {
/* 主色 → primary */
/* 主色绿 → primary */
--el-color-primary: var(--brand-primary);
--el-color-primary-light-3: var(--brand-primary-text); /* text (中蓝可读) */
--el-color-primary-light-5: var(--brand-primary-light); /* light (灰中) */
--el-color-primary-light-7: var(--brand-primary-lighter); /* lighter (灰浅) */
--el-color-primary-light-8: var(--brand-primary-lighter); /* lighter */
--el-color-primary-light-9: var(--brand-primary-mix); /* mix (近白) */
--el-color-primary-light-3: var(--brand-primary-text);
--el-color-primary-light-5: var(--brand-primary-light);
--el-color-primary-light-7: var(--brand-primary-lighter);
--el-color-primary-light-8: var(--brand-primary-lighter);
--el-color-primary-light-9: var(--brand-primary-mix);
--el-color-primary-dark-2: var(--brand-primary-deep);
/* 次色 → success (绿色语义: 已批准 / 已完成) */
--el-color-success: var(--brand-secondary);
--el-color-success-light-3: var(--brand-secondary-text);
--el-color-success-light-5: var(--brand-secondary-light);
--el-color-success-light-7: var(--brand-secondary-lighter);
--el-color-success-light-8: var(--brand-secondary-lighter);
--el-color-success-light-9: var(--brand-secondary-mix);
--el-color-success-dark-2: var(--brand-secondary-deep);
/* 次色info (链接/提示, 不再绑 success 避免色相冲突) */
--el-color-info: var(--brand-secondary);
--el-color-info-light-3: var(--brand-secondary-text);
--el-color-info-light-5: var(--brand-secondary-light);
--el-color-info-light-7: var(--brand-secondary-lighter);
--el-color-info-light-9: var(--brand-secondary-mix);
/* success: 保留 Element Plus 默认绿 (#67c23a → #22c55e 系), 但低饱和 */
--el-color-success: #16A34A;
--el-color-success-light-3: #4ADE80;
--el-color-success-light-5: #A7E0BC;
--el-color-success-light-7: #D1EDD9;
--el-color-success-light-8: #D1EDD9;
--el-color-success-light-9: #EBF6EE;
--el-color-success-dark-2: #15803D;
/* danger: 收紧饱和度, 政务感 */
--el-color-danger-light-5: #F4D6D6;
--el-color-danger-light-7: #FBE9E9;
--el-color-danger-light-9: #FCF1F1;
/* warning: 偏琥珀, 不刺眼 */
--el-color-warning: #B45309;
--el-color-warning-light-3: #D97706;
--el-color-warning-light-5: #F4D9A8;
--el-color-warning-light-7: #FAEAD0;
--el-color-warning-light-9: #FCF4E5;
/* 边框/文字/背景: 政务低对比度, 偏冷灰 */
--el-border-color: var(--brand-slate-200);
--el-border-color-light: var(--brand-slate-100);
--el-border-color-lighter: var(--brand-slate-100);
--el-border-color-extra-light: #FAFBFC;
--el-border-color-dark: var(--brand-slate-300);
--el-border-color-darker: var(--brand-slate-400);
--el-text-color-primary: var(--brand-slate-800);
--el-text-color-regular: var(--brand-slate-700);
--el-text-color-secondary: var(--brand-slate-500);
--el-text-color-placeholder: var(--brand-slate-400);
--el-text-color-disabled: var(--brand-slate-400);
--el-fill-color-blank: #FFFFFF;
--el-fill-color-light: var(--brand-slate-50);
--el-fill-color-lighter: var(--brand-slate-100);
--el-fill-color-extra-light: #FAFBFC;
--el-fill-color-dark: var(--brand-slate-200);
--el-fill-color-darker: var(--brand-slate-300);
--el-bg-color: #FFFFFF;
--el-bg-color-page: var(--brand-slate-50);
--el-bg-color-overlay: #FFFFFF;
}
+1 -1
View File
@@ -21,7 +21,7 @@
<el-button type="primary" size="small" :disabled="readonly" @click="openDialog">
<el-icon><CameraFilled /></el-icon>&nbsp;扫码拍照
</el-button>
<el-button v-if="modelValue" link type="primary" size="small" @click="openPreview">查看照片</el-button>
<el-link :underline="false" v-if="modelValue" type="primary" @click="openPreview">查看照片</el-link>
<span v-else class="cam-empty">未上传</span>
<span v-if="polling" class="cam-polling">等待手机回传</span>
</div>
+1 -1
View File
@@ -18,7 +18,7 @@
<template>
<div class="notice-list-wrap">
<div v-if="showHeader" class="notice-list-header">
<el-button v-if="hasUnread" link size="small" type="primary" @click="markAllRead">全部已读</el-button>
<el-link :underline="false" v-if="hasUnread" type="primary" @click="markAllRead">全部已读</el-link>
</div>
<ul class="notice-list">
<li
+1 -1
View File
@@ -26,7 +26,7 @@
<a class="file-name" :href="modelValue" target="_blank" @click.stop>{{ fileName }}</a>
<span class="file-type">{{ ext.toUpperCase() }} · {{ fileSizeText }}</span>
</div>
<el-button v-if="!readonly" link type="danger" size="small" class="remove-btn" @click.stop="onRemove">移除</el-button>
<el-link :underline="false" v-if="!readonly" type="danger" class="remove-btn" @click.stop="onRemove">移除</el-link>
<!-- 右侧预览框 (图片用 el-image, 其他用 icon + 点击新窗口打开) -->
<div v-if="showPreview" class="preview-box">
<el-image v-if="isImage" :src="modelValue" :preview-src-list="[modelValue]" :initial-index="0" fit="cover" class="preview-img" />
+142 -48
View File
@@ -7,7 +7,10 @@
>
<div class="aside-logo">
<div class="logo-icon"><img src="/logo.png" alt="logo" /></div>
<div class="logo-text">{{ roleLabel }}</div>
<div class="logo-text">
<div class="logo-name">合规管理系统</div>
<div class="logo-role">{{ roleLabel }}</div>
</div>
</div>
<el-menu :default-active="route.path" router class="aside-menu">
<template v-for="m in menu" :key="m.path">
@@ -31,26 +34,36 @@
</el-menu>
</el-aside>
<el-container>
<el-header v-if="!route.meta?.hideMenu" class="topbar">
<el-header v-if="!route.meta?.hideMenu" class="topbar" height="56px">
<div class="topbar-left">
<span class="page-title">{{ headerTitle }}</span>
<el-icon class="topbar-back" @click="$router.back()"><ArrowLeft /></el-icon>
<span class="page-title-inline">{{ headerTitle }}</span>
</div>
<div class="topbar-right">
<el-button text @click="$router.push('/')">门户首页</el-button>
<el-link :underline="false" class="topbar-portal" @click="$router.push('/')">
<el-icon><HomeFilled /></el-icon>
<span>门户首页</span>
</el-link>
<el-divider direction="vertical" />
<!-- 消息入口 (admin/manager/doctor/sponsor/executor 各自路由, 通过 meta.noticePath 配置) -->
<el-badge v-if="unreadCount > 0" :value="unreadCount" :max="99" class="notice-badge">
<el-button text @click="goNotice">
<el-button text class="topbar-icon-btn" @click="goNotice">
<el-icon><Bell /></el-icon>
</el-button>
</el-badge>
<el-button v-else text @click="goNotice">
<el-button v-else text class="topbar-icon-btn" @click="goNotice">
<el-icon><Bell /></el-icon>
</el-button>
<el-dropdown trigger="click" @command="onCommand">
<span class="user-name">{{ store.user?.userName || store.user?.displayName }}<el-icon><CaretBottom /></el-icon></span>
<span class="user-name">
<el-icon class="user-icon"><UserFilled /></el-icon>
<span>{{ store.user?.userName || store.user?.displayName }}</span>
<el-icon class="caret"><CaretBottom /></el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
<el-dropdown-item command="account" @click="$router.push('/' + (role || 'admin') + '/account')">账号信息</el-dropdown-item>
<el-dropdown-item command="logout" divided>退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -73,7 +86,7 @@ import { listMyMessages } from '@/api/public'
import { usePageTitle } from '@/utils/pageTitle'
import { getMyExpertProfile } from '@/api/business/expert'
import { ElNotification } from 'element-plus'
import { House, Document, Calendar, User, List, OfficeBuilding, Setting, Bell, EditPen, DataAnalysis, Tickets, CaretBottom, Folder, Medal, UserFilled, Connection, Box, Star, Grid, Files, Compass, Collection } from '@element-plus/icons-vue'
import { House, Document, Calendar, User, List, OfficeBuilding, Setting, Bell, EditPen, DataAnalysis, Tickets, CaretBottom, Folder, Medal, UserFilled, Connection, Box, Star, Grid, Files, Compass, Collection, ArrowLeft, HomeFilled } from '@element-plus/icons-vue'
const route = useRoute()
const router = useRouter()
@@ -241,73 +254,154 @@ const onCommand = (cmd) => { if (cmd === 'logout') logout() }
<style scoped>
.admin-layout { height: 100vh; }
.aside { background: #001529; color: #fff; }
.aside-logo {
padding: 20px 16px 16px;
text-align: center;
border-bottom: 1px solid var(--brand-primary-darker);
/* —— 侧边栏:浅色政务风(避免深绿主色冲突, 政务网站通用样式) —— */
.aside {
background: #FFFFFF;
border-right: 1px solid var(--brand-slate-200);
display: flex;
flex-direction: column;
}
.aside-logo {
padding: 18px 16px 16px;
border-bottom: 1px solid var(--brand-slate-200);
display: flex;
align-items: center;
gap: 10px;
gap: 12px;
background: linear-gradient(180deg, var(--brand-primary-mix) 0%, #FFFFFF 100%);
}
.logo-icon {
width: 44px;
height: 44px;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
border-radius: 6px;
overflow: hidden;
background: #fff;
border: 1px solid var(--brand-slate-200);
flex-shrink: 0;
}
.logo-icon img {
width: 100%;
height: 100%;
object-fit: contain;
display: block;
}
.logo-text { color: #fff; font-size: 14px; font-weight: 600; }
.aside-menu { background: #001529; border: none; }
.logo-icon img { width: 100%; height: 100%; object-fit: contain; display: block; }
.logo-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.logo-name { color: var(--el-text-color-primary); font-size: 15px; font-weight: 600; white-space: nowrap; }
.logo-role { color: var(--el-text-color-secondary); font-size: 12px; white-space: nowrap; }
/* 一级菜单 + 二级父标题: 统一默认/hover/active 样式 */
.aside-menu { background: transparent; border: none; flex: 1; }
/* 一级菜单 + 二级父标题: 中性灰文字, hover 浅绿, active 实心绿 */
:deep(.el-menu-item),
:deep(.el-sub-menu__title) {
color: #c0c4cc;
color: var(--el-text-color-regular);
height: 44px;
line-height: 44px;
}
:deep(.el-menu-item .el-icon),
:deep(.el-sub-menu__title .el-icon) {
color: var(--brand-slate-500);
}
:deep(.el-menu-item:hover),
:deep(.el-sub-menu__title:hover) {
background-color: #1f3a5f;
color: #fff;
background-color: var(--brand-primary-mix);
color: var(--brand-primary);
}
/* 子项激活: 蓝底白字 */
:deep(.el-menu-item:hover .el-icon),
:deep(.el-sub-menu__title:hover .el-icon) { color: var(--brand-primary); }
/* 选中态: 政务感 — 实心绿底白字 + 左侧 4px 高亮条 */
:deep(.el-menu-item.is-active) {
background: var(--brand-primary);
color: #fff;
color: #FFFFFF;
position: relative;
}
:deep(.el-menu-item.is-active .el-icon) { color: #FFFFFF; }
:deep(.el-menu-item.is-active::before) {
content: '';
position: absolute;
left: 0; top: 0; bottom: 0;
width: 4px;
background: var(--brand-primary-darker);
}
/* 父标题在子项激活时高亮 (Element Plus 自动给 el-sub-menu 加 is-active) */
:deep(.el-sub-menu.is-active > .el-sub-menu__title) {
color: #fff;
color: var(--brand-primary);
}
:deep(.el-sub-menu.is-active > .el-sub-menu__title .el-icon) {
color: var(--brand-primary);
}
/* 二级菜单容器: 跟随侧边栏深蓝底, 不要 Element Plus 默认白底 */
:deep(.el-menu--inline),
:deep(.el-menu--inline .el-menu-item) {
background-color: #001529;
/* 二级菜单缩进 */
:deep(.el-menu .el-menu .el-menu-item) {
padding-left: 48px !important;
background-color: var(--brand-slate-50);
}
:deep(.el-menu--inline .el-menu-item:hover) {
background-color: #1f3a5f;
:deep(.el-menu .el-menu .el-menu-item:hover) {
background-color: var(--brand-primary-mix);
}
:deep(.el-menu--inline .el-menu-item.is-active) {
:deep(.el-menu .el-menu .el-menu-item.is-active) {
background: var(--brand-primary);
color: #FFFFFF;
}
.topbar { background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; border-bottom: 1px solid #ebeef5; }
.topbar-left { display: flex; align-items: center; }
.page-title { font-size: 16px; font-weight: 600; }
.topbar-right { display: flex; align-items: center; gap: 12px; }
.user-name { cursor: pointer; display: inline-flex; align-items: center; gap: 4px; }
/* navbar bell 角标: 紧凑, 不撑大 topbar */
:deep(.el-menu .el-menu .el-menu-item.is-active .el-icon) { color: #FFFFFF; }
/* 顶栏:白底 + 边框 */
.topbar {
background: #FFFFFF;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
border-bottom: 1px solid var(--brand-slate-200);
height: 56px;
}
.topbar-left { display: flex; align-items: center; gap: 8px; }
.topbar-back {
font-size: 18px;
color: var(--el-text-color-secondary);
cursor: pointer;
padding: 6px;
border-radius: 4px;
transition: background-color 0.18s, color 0.18s;
}
.topbar-back:hover { background: var(--brand-slate-100); color: var(--brand-primary); }
.page-title-inline {
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.topbar-right { display: flex; align-items: center; gap: 4px; }
.topbar-link {
display: inline-flex;
align-items: center;
gap: 4px;
color: var(--el-text-color-regular);
font-size: 13px;
}
.topbar-link:hover { color: var(--brand-primary); }
.topbar-icon-btn {
font-size: 18px;
color: var(--el-text-color-regular);
padding: 6px;
}
.topbar-icon-btn:hover { color: var(--brand-primary); }
.user-name {
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 6px;
padding: 0 8px;
height: 32px;
border-radius: 4px;
font-size: 13px;
color: var(--el-text-color-regular);
transition: background-color 0.18s;
}
.user-name:hover { background: var(--brand-slate-100); color: var(--brand-primary); }
.user-name .user-icon { font-size: 16px; color: var(--brand-slate-500); }
.user-name .caret { font-size: 12px; color: var(--brand-slate-400); }
.notice-badge :deep(.el-badge__content) { transform: translate(50%, -10%); }
.main { background: #f5f7fa; padding: 16px; }
/* 主体: 浅灰底 + 卡片自动吸附 */
.main { background: var(--brand-slate-50); padding: 16px 20px; min-height: calc(100vh - 56px); }
/* 手机页面 (hideMenu): 白底 + 0 padding, 让 form 直接撑满 */
.main-full { background: #fff; padding: 0; }
</style>
+4 -5
View File
@@ -39,10 +39,10 @@
</el-table-column>
<el-table-column prop="updateTime" label="更新时间" width="170" />
<el-table-column label="操作" width="160" fixed="right">
<template #default="{ row }">
<el-button size="small" link type="primary" @click="$router.push('/admin/article/edit/' + row.id)">编辑</el-button>
<el-button size="small" link type="primary" :disabled="row.status !== '0'" @click="preview(row)" target="_blank">预览</el-button>
</template>
<template #default="{ row }"><div class="table-actions">
<el-link :underline="false" size="small" type="primary" @click="$router.push('/admin/article/edit/' + row.id)">编辑</el-link>
<el-link :underline="false" size="small" type="primary" :disabled="row.status !== '0'" @click="preview(row)" target="_blank">预览</el-link>
</div></template>
</el-table-column>
</el-table>
@@ -97,6 +97,5 @@ onMounted(load)
<style scoped>
.admin-article { padding: 16px; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.filter-form { margin-bottom: 12px; }
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
</style>
@@ -40,10 +40,10 @@
</el-table-column>
<el-table-column prop="updateTime" label="更新时间" width="170" />
<el-table-column label="操作" width="140" fixed="right">
<template #default="{ row }">
<el-button size="small" link type="primary" @click="$router.push('/admin/special-plan/edit/' + row.id)">编辑</el-button>
<el-button size="small" link type="primary" :disabled="row.status !== '0'" @click="preview(row)">预览</el-button>
</template>
<template #default="{ row }"><div class="table-actions">
<el-link :underline="false" size="small" type="primary" @click="$router.push('/admin/special-plan/edit/' + row.id)">编辑</el-link>
<el-link :underline="false" size="small" type="primary" :disabled="row.status !== '0'" @click="preview(row)">预览</el-link>
</div></template>
</el-table-column>
</el-table>
@@ -97,6 +97,5 @@ onMounted(load)
<style scoped>
.admin-plan { padding: 16px; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.filter-form { margin-bottom: 12px; }
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
</style>
+6 -7
View File
@@ -30,13 +30,13 @@
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="170" />
<el-table-column label="操作" width="220" fixed="right">
<template #default="{ row }">
<el-button size="small" link type="primary" @click="openEdit(row)">编辑</el-button>
<el-button size="small" link :type="row.status === '0' ? 'warning' : 'success'" @click="toggleStatus(row)">
<template #default="{ row }"><div class="table-actions">
<el-link :underline="false" type="primary" @click="openEdit(row)">编辑</el-link>
<el-link :underline="false" :type="row.status === '0' ? 'warning' : 'success'" @click="toggleStatus(row)">
{{ row.status === '0' ? '停用' : '启用' }}
</el-button>
<el-button size="small" link type="danger" @click="removeOne(row)">删除</el-button>
</template>
</el-link>
<el-link :underline="false" type="danger" @click="removeOne(row)">删除</el-link>
</div></template>
</el-table-column>
</el-table>
@@ -165,6 +165,5 @@ onMounted(load)
/* 整页面板 (与 People.vue 风格一致) */
.admin-department { padding: 16px; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.filter-form { margin-bottom: 12px; }
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
</style>
+5 -6
View File
@@ -43,11 +43,11 @@
<el-table-column prop="remark" label="备注" min-width="160" show-overflow-tooltip />
<el-table-column prop="updateTime" label="更新时间" width="170" />
<el-table-column label="操作" width="240" fixed="right">
<template #default="{ row }">
<el-button size="small" link type="primary" @click="goEdit(row)">编辑</el-button>
<el-button size="small" link type="warning" :disabled="row.defaultFlag === 'Y'" @click="setDefault(row)">设为默认</el-button>
<el-button size="small" link type="danger" @click="removeRow(row)">删除</el-button>
</template>
<template #default="{ row }"><div class="table-actions">
<el-link :underline="false" size="small" type="primary" @click="goEdit(row)">编辑</el-link>
<el-link :underline="false" size="small" type="warning" :disabled="row.defaultFlag === 'Y'" @click="setDefault(row)">设为默认</el-link>
<el-link :underline="false" size="small" type="danger" @click="removeRow(row)">删除</el-link>
</div></template>
</el-table-column>
</el-table>
@@ -134,7 +134,6 @@ onMounted(load)
/* 整页面板 (与 People.vue 风格一致) */
.admin-labor-protocol { padding: 16px; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.filter-form { margin-bottom: 12px; }
.batch-bar { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; }
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
+6 -7
View File
@@ -28,13 +28,13 @@
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="170" />
<el-table-column label="操作" width="240" fixed="right">
<template #default="{ row }">
<el-button size="small" link type="primary" @click="openEdit(row)">编辑</el-button>
<el-button size="small" link :type="row.status === '0' ? 'warning' : 'success'" @click="toggleStatus(row)">
<template #default="{ row }"><div class="table-actions">
<el-link :underline="false" type="primary" @click="openEdit(row)">编辑</el-link>
<el-link :underline="false" :type="row.status === '0' ? 'warning' : 'success'" @click="toggleStatus(row)">
{{ row.status === '0' ? '停用' : '启用' }}
</el-button>
<el-button size="small" link type="danger" @click="removeOne(row)">删除</el-button>
</template>
</el-link>
<el-link :underline="false" type="danger" @click="removeOne(row)">删除</el-link>
</div></template>
</el-table-column>
</el-table>
@@ -182,6 +182,5 @@ onMounted(load)
<style scoped>
.admin-project-category { padding: 16px; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.filter-form { margin-bottom: 12px; }
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
</style>
+4 -4
View File
@@ -28,11 +28,11 @@
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="170" />
<el-table-column label="操作" width="100" fixed="right">
<template #default="{ row }">
<el-button size="small" link :type="row.status === '0' ? 'danger' : 'success'" @click="toggleStatus(row)">
<template #default="{ row }"><div class="table-actions">
<el-link :underline="false" :type="row.status === '0' ? 'danger' : 'success'" @click="toggleStatus(row)">
{{ row.status === '0' ? '停用' : '启用' }}
</el-button>
</template>
</el-link>
</div></template>
</el-table-column>
</el-table>
</template>
+6 -7
View File
@@ -30,13 +30,13 @@
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="170" />
<el-table-column label="操作" width="220" fixed="right">
<template #default="{ row }">
<el-button size="small" link type="primary" @click="openEdit(row)">编辑</el-button>
<el-button size="small" link :type="row.status === '0' ? 'warning' : 'success'" @click="toggleStatus(row)">
<template #default="{ row }"><div class="table-actions">
<el-link :underline="false" type="primary" @click="openEdit(row)">编辑</el-link>
<el-link :underline="false" :type="row.status === '0' ? 'warning' : 'success'" @click="toggleStatus(row)">
{{ row.status === '0' ? '停用' : '启用' }}
</el-button>
<el-button size="small" link type="danger" @click="removeOne(row)">删除</el-button>
</template>
</el-link>
<el-link :underline="false" type="danger" @click="removeOne(row)">删除</el-link>
</div></template>
</el-table-column>
</el-table>
@@ -165,6 +165,5 @@ onMounted(load)
/* 整页面板 (与 People.vue 风格一致) */
.admin-title { padding: 16px; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.filter-form { margin-bottom: 12px; }
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
</style>
+7 -14
View File
@@ -47,20 +47,14 @@
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="170" />
<el-table-column label="操作" width="330" fixed="right">
<template #default="{ row }">
<el-button size="small" link type="primary" @click="openEdit(row)">编辑</el-button>
<el-button size="small" link type="primary" @click="onResetPwd(row)">重置密码</el-button>
<el-button size="small" link :type="row.status === '0' ? 'danger' : 'success'" @click="toggleStatus(row)">
<template #default="{ row }"><div class="table-actions">
<el-link :underline="false" type="primary" @click="openEdit(row)">编辑</el-link>
<el-link :underline="false" type="primary" @click="onResetPwd(row)">重置密码</el-link>
<el-link :underline="false" :type="row.status === '0' ? 'danger' : 'success'" @click="toggleStatus(row)">
{{ row.status === '0' ? '停用' : '启用' }}
</el-button>
<el-button
size="small"
link
type="danger"
:disabled="row.userId === userStore.user?.userId"
@click="delUser(row)"
>删除</el-button>
</template>
</el-link>
<el-link :underline="false" type="danger" :disabled="row.userId === userStore.user?.userId" @click="delUser(row)">删除</el-link>
</div></template>
</el-table-column>
</el-table>
@@ -367,7 +361,6 @@ onMounted(load)
/* 整页面板 (与 People.vue 风格一致) */
.admin-users { padding: 16px; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.filter-form { margin-bottom: 12px; }
.toolbar { margin-bottom: 12px; }
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
</style>
+197 -54
View File
@@ -1,41 +1,94 @@
<template>
<div class="page-card admin-workbench">
<div class="breadcrumb">首页</div>
<div class="admin-workbench">
<div class="breadcrumb">
<span class="current">工作台</span>
</div>
<!-- KPI : 用户/项目/会议/已结题 -->
<!-- KPI : 白底 + 顶部色条 + 数字, 政务网标准 -->
<div class="stats-grid">
<router-link class="stat-card" to="/admin/users">
<div class="stat-bar"></div>
<div class="stat-label">系统用户总数</div>
<div class="stat-value">{{ stats.totalUsers }}</div>
<div class="stat-extra">查看详情 </div>
</router-link>
<router-link class="stat-card" to="/admin/roles">
<div class="stat-bar"></div>
<div class="stat-label">角色类型数</div>
<div class="stat-value">{{ stats.roleCount }}</div>
<div class="stat-extra">查看详情 </div>
</router-link>
<router-link class="stat-card" to="/admin/projects">
<div class="stat-bar"></div>
<div class="stat-label">项目总数量</div>
<div class="stat-value">{{ stats.totalProjects }}</div>
<div class="stat-extra">查看详情 </div>
</router-link>
<router-link class="stat-card" to="/admin/projects">
<div class="stat-bar"></div>
<div class="stat-label">已结题项目</div>
<div class="stat-value">{{ stats.finishedProjects }}</div>
<div class="stat-extra">查看详情 </div>
</router-link>
<router-link class="stat-card" to="/admin/meetings">
<div class="stat-bar"></div>
<div class="stat-label">会议总数量</div>
<div class="stat-value">{{ stats.totalMeetings }}</div>
<div class="stat-extra">查看详情 </div>
</router-link>
</div>
<!-- 各角色用户数分布 -->
<section class="section">
<h2 class="section-title">各角色用户数</h2>
<div class="role-grid">
<div v-for="r in roleStats" :key="r.code" class="role-card">
<div class="role-name">{{ r.label }}</div>
<div class="role-count">{{ r.count }}</div>
<!-- 各角色用户数分布 + 快捷入口, 双栏布局 -->
<div class="content-grid">
<section class="content-card">
<h2 class="section-title">各角色用户数</h2>
<div class="role-grid">
<div v-for="r in roleStats" :key="r.code" class="role-card">
<div class="role-name">{{ r.label }}</div>
<div class="role-count">{{ r.count }}</div>
<div class="role-bar" :style="{ width: r.pct + '%' }"></div>
</div>
</div>
</div>
</section>
</section>
<section class="content-card">
<h2 class="section-title">快捷入口</h2>
<div class="quick-grid">
<router-link class="quick-item" to="/admin/users">
<el-icon class="quick-icon"><User /></el-icon>
<div class="quick-text">用户管理</div>
</router-link>
<router-link class="quick-item" to="/admin/projects">
<el-icon class="quick-icon"><Document /></el-icon>
<div class="quick-text">项目管理</div>
</router-link>
<router-link class="quick-item" to="/admin/meetings">
<el-icon class="quick-icon"><Calendar /></el-icon>
<div class="quick-text">会议管理</div>
</router-link>
<router-link class="quick-item" to="/admin/experts">
<el-icon class="quick-icon"><UserFilled /></el-icon>
<div class="quick-text">专家管理</div>
</router-link>
<router-link class="quick-item" to="/admin/sponsor-orgs">
<el-icon class="quick-icon"><Star /></el-icon>
<div class="quick-text">支持单位</div>
</router-link>
<router-link class="quick-item" to="/admin/executor-orgs">
<el-icon class="quick-icon"><OfficeBuilding /></el-icon>
<div class="quick-text">执行单位</div>
</router-link>
<router-link class="quick-item" to="/admin/article">
<el-icon class="quick-icon"><Files /></el-icon>
<div class="quick-text">协议管理</div>
</router-link>
<router-link class="quick-item" to="/admin/messages">
<el-icon class="quick-icon"><Bell /></el-icon>
<div class="quick-text">消息通知</div>
</router-link>
</div>
</section>
</div>
</div>
</template>
@@ -43,6 +96,7 @@
import { ref, onMounted } from 'vue'
import { listUser, listByRole } from '@/api/system'
import { bizList } from '@/api/public'
import { User, Document, Calendar, UserFilled, Star, OfficeBuilding, Files, Bell } from '@element-plus/icons-vue'
const stats = ref({
totalUsers: 0, roleCount: 0, totalProjects: 0, finishedProjects: 0, totalMeetings: 0
@@ -71,8 +125,9 @@ async function load() {
all.push({ code, label: ROLE_LABEL[code], count: 0 })
}
}
roleStats.value = all
// 角色类型数 = 实际定义的业务角色数 (admin/manager/doctor/executor/sponsor = 5)
// 计算占比 (用于进度条)
const max = Math.max(1, ...all.map(r => r.count))
roleStats.value = all.map(r => ({ ...r, pct: Math.round((r.count / max) * 100) }))
stats.value.roleCount = codes.length
// 项目数
@@ -93,67 +148,155 @@ onMounted(load)
</script>
<style scoped>
/* 整页面板 (与 People.vue 风格一致) */
.admin-workbench { padding: 16px; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.admin-workbench { padding: 0; }
.stats-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 16px; margin-bottom: 12px; }
/* —— KPI 网格: 5 列等宽 —— */
.stats-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 16px;
margin-bottom: 20px;
}
.stat-card {
background: var(--brand-primary);
border: 1px solid var(--brand-primary);
border-radius: 8px;
padding: 24px;
background: #FFFFFF;
border: 1px solid var(--brand-slate-200);
border-radius: 6px;
padding: 0;
cursor: pointer;
text-decoration: none;
color: #fff;
color: inherit;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
transition: opacity .15s, transform .15s;
min-height: 120px;
position: relative;
overflow: hidden;
transition: border-color 0.18s, box-shadow 0.18s, transform 0.18s;
}
.stat-card:hover {
border-color: var(--brand-primary-light);
box-shadow: 0 4px 12px rgba(21, 128, 61, 0.10);
transform: translateY(-1px);
}
.stat-bar {
height: 4px;
background: linear-gradient(90deg, var(--brand-primary) 0%, var(--brand-primary-text) 100%);
}
.stat-card > .stat-label,
.stat-card > .stat-value,
.stat-card > .stat-extra { padding: 0 20px; }
.stat-label {
font-size: 13px;
color: var(--el-text-color-secondary);
padding-top: 18px;
padding-bottom: 6px;
}
.stat-card:hover { opacity: .9; transform: translateY(-2px); }
.stat-label { font-size: 14px; color: rgba(255, 255, 255, 0.85); margin-bottom: 16px; }
.stat-value {
font-size: 32px;
font-size: 30px;
font-weight: 600;
line-height: 1.2;
color: #fff;
line-height: 1.1;
color: var(--brand-primary);
padding-bottom: 14px;
}
.stat-extra {
font-size: 12px;
color: var(--brand-primary-text);
padding: 10px 20px;
border-top: 1px dashed var(--brand-slate-200);
background: var(--brand-slate-50);
}
.section {
background: #fff;
border: 1px solid #f0f0f0;
border-radius: 8px;
padding: 20px 24px;
margin-top: 16px;
/* —— 双栏内容区 —— */
.content-grid {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 16px;
}
.content-card {
background: #FFFFFF;
border: 1px solid var(--brand-slate-200);
border-radius: 6px;
padding: 20px 24px;
}
/* —— 二级标题(政务: 蓝色细线 + 粗体) —— */
.section-title {
font-size: 15px;
font-weight: 600;
color: #1a1a1a;
margin-bottom: 16px;
color: var(--el-text-color-primary);
margin: 0 0 16px;
padding-left: 10px;
border-left: 3px solid var(--brand-primary);
line-height: 1;
border-left: 4px solid var(--brand-primary);
line-height: 1.2;
}
.role-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 12px; }
/* —— 角色分布: 6 列网格 —— */
.role-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
}
.role-card {
background: #fafbfc;
border: 1px solid #f0f0f0;
border-radius: 8px;
padding: 16px;
text-align: center;
background: var(--brand-slate-50);
border: 1px solid var(--brand-slate-200);
border-radius: 4px;
padding: 14px 16px;
position: relative;
overflow: hidden;
}
.role-name {
font-size: 12px;
color: var(--el-text-color-secondary);
margin-bottom: 6px;
}
.role-name { font-size: 13px; color: #8c8c8c; margin-bottom: 8px; }
.role-count {
font-size: 24px;
font-size: 22px;
font-weight: 600;
background: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-primary-deep) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
color: var(--el-text-color-primary);
line-height: 1.1;
margin-bottom: 8px;
}
.role-bar {
height: 4px;
background: var(--brand-primary);
border-radius: 2px;
transition: width 0.3s;
}
/* —— 快捷入口: 4 列网格, 图标 + 文字 —— */
.quick-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
.quick-item {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 14px;
border-radius: 4px;
border: 1px solid var(--brand-slate-200);
text-decoration: none;
color: var(--el-text-color-regular);
font-size: 13px;
transition: border-color 0.18s, background-color 0.18s, color 0.18s;
}
.quick-item:hover {
border-color: var(--brand-primary-light);
background: var(--brand-primary-mix);
color: var(--brand-primary);
}
.quick-icon {
font-size: 18px;
color: var(--brand-primary);
}
.quick-text { font-weight: 500; }
/* —— 响应式: 5 卡变 2 列 —— */
@media (max-width: 1200px) {
.stats-grid { grid-template-columns: repeat(3, 1fr); }
.content-grid { grid-template-columns: 1fr; }
}
@media (max-width: 768px) {
.stats-grid { grid-template-columns: repeat(2, 1fr); }
.role-grid { grid-template-columns: repeat(2, 1fr); }
}
</style>
+2 -2
View File
@@ -24,9 +24,9 @@
<el-form-item label="联系电话">
<div class="phone-row">
<span class="phone-value">{{ form.phone || '未设置' }}</span>
<el-button link type="primary" @click="openPhoneDialog">
<el-link :underline="false" type="primary" @click="openPhoneDialog">
<el-icon><EditPen /></el-icon> 修改
</el-button>
</el-link>
</div>
</el-form-item>
<el-form-item label="科室" :required="true">
+9 -14
View File
@@ -23,26 +23,22 @@
</el-table-column>
<el-table-column label="日程" width="130" align="center">
<template #default="{ row }">
<el-button link type="primary" :disabled="!row.scheduleUrl" @click="onPreview(row.scheduleUrl, '日程海报')">查看</el-button>
<el-button link :disabled="!row.scheduleUrl" @click="onDownload(row.scheduleUrl, `${row.meetingName || '会议'}_日程海报`)">下载</el-button>
</template>
</el-table-column>
<el-link :underline="false" type="primary" :disabled="!row.scheduleUrl" @click="onPreview(row.scheduleUrl, '日程海报')">查看</el-link>
<el-link :underline="false" :disabled="!row.scheduleUrl" @click="onDownload(row.scheduleUrl, `${row.meetingName || '会议'}_日程海报`)">下载</el-link></template></el-table-column>
<el-table-column label="邀请函" width="130" align="center">
<template #default="{ row }">
<el-button link type="primary" :disabled="!row.projectInvitationUrl" @click="onPreview(row.projectInvitationUrl, '邀请函')">查看</el-button>
<el-button link :disabled="!row.projectInvitationUrl" @click="onDownload(row.projectInvitationUrl, `${row.meetingName || '会议'}_邀请函`)">下载</el-button>
</template>
</el-table-column>
<el-link :underline="false" type="primary" :disabled="!row.projectInvitationUrl" @click="onPreview(row.projectInvitationUrl, '邀请函')">查看</el-link>
<el-link :underline="false" :disabled="!row.projectInvitationUrl" @click="onDownload(row.projectInvitationUrl, `${row.meetingName || '会议'}_邀请函`)">下载</el-link></template></el-table-column>
<el-table-column label="签署状态" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.attendeeLaborProtocol ? 'success' : 'info'" size="small">{{ row.attendeeLaborProtocol ? '已签署' : '未签署' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<template #default="{ row }">
<el-button v-if="!row.attendeeLaborProtocol" link type="primary" @click="onSign(row)">签署劳务</el-button>
<el-button v-else link type="primary" @click="onViewLabor(row)">查看劳务</el-button>
</template>
<template #default="{ row }"><div class="table-actions">
<el-link :underline="false" v-if="!row.attendeeLaborProtocol" type="primary" @click="onSign(row)">签署劳务</el-link>
<el-link :underline="false" v-else type="primary" @click="onViewLabor(row)">查看劳务</el-link>
</div></template>
</el-table-column>
</el-table>
@@ -109,7 +105,7 @@ import { bizList } from '@/api/public'
import { stageLabel, stageTag, STAGE_OPTIONS } from '@/utils/meetingStage'
const q = reactive({ projectNo: '', meetingName: '', currentStage: '' })
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
const page = reactive({ pageNum: 1, pageSize: 20, total: 0 })
const rows = ref([])
const loading = ref(false)
const detail = ref({})
@@ -253,7 +249,6 @@ load()
<style scoped>
.page-card { background: #fff; padding: 16px 20px; border-radius: 6px; border: 1px solid #f0f0f0; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.filter-form { display: flex; flex-wrap: wrap; gap: 0 16px; padding-bottom: 12px; border-bottom: 1px solid #f0f0f0; margin-bottom: 12px; }
.pdf-preview { margin-top: 12px; }
.file-preview { display: flex; justify-content: center; align-items: flex-start; min-height: 480px; }
.file-preview-iframe { width: 100%; height: 70vh; border: 1px solid #ebeef5; border-radius: 4px; }
+6 -7
View File
@@ -19,11 +19,11 @@
</template>
</el-table-column>
<el-table-column label="操作" width="220" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="onView(row)">查看</el-button>
<el-button link type="primary" @click="onPublicity(row)">查看公示</el-button>
<el-button link type="primary" :disabled="!hasFile(row)" @click="onDownload(row)">下载</el-button>
</template>
<template #default="{ row }"><div class="table-actions">
<el-link :underline="false" type="primary" @click="onView(row)">查看</el-link>
<el-link :underline="false" type="primary" @click="onPublicity(row)">查看公示</el-link>
<el-link :underline="false" type="primary" :disabled="!hasFile(row)" @click="onDownload(row)">下载</el-link>
</div></template>
</el-table-column>
</el-table>
@@ -67,7 +67,7 @@ import { useUserStore } from '@/store/user'
const router = useRouter()
const userStore = useUserStore()
const q = reactive({ projectNo: '', projectName: '', meetingName: '' })
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
const page = reactive({ pageNum: 1, pageSize: 20, total: 0 })
const rows = ref([])
const loading = ref(false)
const detail = ref({})
@@ -136,7 +136,6 @@ load()
<style scoped>
.page-card { background: #fff; padding: 16px 20px; border-radius: 6px; border: 1px solid #f0f0f0; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.filter-form { display: flex; flex-wrap: wrap; gap: 0 16px; padding-bottom: 12px; border-bottom: 1px solid #f0f0f0; margin-bottom: 12px; }
.status { font-size: 13px; color: #595959; }
:deep(.el-button--primary) { background: var(--brand-primary); border-color: var(--brand-primary); border-radius: 4px; }
:deep(.el-button--primary:hover) { background: var(--brand-primary-deep); border-color: var(--brand-primary-deep); }
+1 -1
View File
@@ -28,7 +28,7 @@
<el-option v-for="t in titleOptions" :key="t" :value="t" :label="t" />
<template #footer>
<el-input v-if="showTitleOther" v-model="titleOther" placeholder="其他职称" maxlength="50" @blur="form.title = titleOther" />
<el-button v-else link type="primary" @click="showTitleOther = true">+ 其他</el-button>
<el-link :underline="false" v-else type="primary" @click="showTitleOther = true">+ 其他</el-link>
</template>
</el-select>
</el-form-item>
+7 -8
View File
@@ -48,7 +48,7 @@
<el-table-column label="设计文件" width="140">
<template #default="{ row }">
<template v-if="row.designFileUrl">
<el-button link type="primary" @click="openPreview(row.designFileUrl, '设计文件')">查看</el-button>
<el-link :underline="false" type="primary" @click="openPreview(row.designFileUrl, '设计文件')">查看</el-link>
<el-link type="primary" :href="row.designFileUrl" target="_blank">下载</el-link>
</template>
<span v-else>-</span>
@@ -61,11 +61,11 @@
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="180" show-overflow-tooltip />
<el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="onView(row)">查看</el-button>
<el-button v-if="canModify(row)" link type="primary" @click="onEdit(row)">修改</el-button>
<el-button v-if="canSubmit(row)" link type="primary" @click="onSubmit(row)">提交</el-button>
</template>
<template #default="{ row }"><div class="table-actions">
<el-link :underline="false" type="primary" @click="onView(row)">查看</el-link>
<el-link :underline="false" v-if="canModify(row)" type="primary" @click="onEdit(row)">修改</el-link>
<el-link :underline="false" v-if="canSubmit(row)" type="primary" @click="onSubmit(row)">提交</el-link>
</div></template>
</el-table-column>
</el-table>
@@ -107,7 +107,7 @@ const q = reactive({
status: '',
remark: ''
})
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
const page = reactive({ pageNum: 1, pageSize: 20, total: 0 })
const rows = ref([])
const loading = ref(false)
const selected = ref([])
@@ -209,7 +209,6 @@ onMounted(() => {
<style scoped>
.page-card { background: #fff; padding: 16px 20px; border-radius: 6px; border: 1px solid #f0f0f0; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.filter-form { display: flex; flex-wrap: wrap; gap: 0 16px; padding-bottom: 12px; border-bottom: 1px solid #f0f0f0; margin-bottom: 12px; }
.toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
.status { font-size: 13px; color: #595959; }
:deep(.el-button--primary) { background: var(--brand-primary); border-color: var(--brand-primary); border-radius: 4px; }
@@ -54,18 +54,18 @@
<el-table-column prop="contactName" label="联系人" width="100" align="center" />
<el-table-column prop="contactPhone" label="联系电话" width="130" align="center" />
<el-table-column label="操作" :width="isAdmin ? 320 : 240" fixed="right">
<template #default="{ row }">
<template #default="{ row }"><div class="table-actions">
<!-- 查看: 两角色都有 (manager 原版本有, admin 原版本用 alert, 这里统一用 dialog 更清晰) -->
<el-button link type="primary" @click="onView(row)">查看</el-button>
<el-link :underline="false" type="primary" @click="onView(row)">查看</el-link>
<!-- 编辑: admin -->
<el-button v-if="isAdmin" link type="primary" @click="onEdit(row)">编辑</el-button>
<el-link :underline="false" v-if="isAdmin" type="primary" @click="onEdit(row)">编辑</el-link>
<!-- 人员管理: 两角色都有, 按角色跳 -->
<el-button link type="primary" @click="onPeople(row)">人员管理</el-button>
<el-link :underline="false" type="primary" @click="onPeople(row)">人员管理</el-link>
<!-- 启用/禁用: 两角色都有 (沿用原行为) -->
<el-button link :type="isDisabled(row) ? 'success' : 'danger'" @click="onToggleStatus(row)">
<el-link :underline="false" :type="isDisabled(row) ? 'success' : 'danger'" @click="onToggleStatus(row)">
{{ isDisabled(row) ? '恢复' : '禁用' }}
</el-button>
</template>
</el-link>
</div></template>
</el-table-column>
</el-table>
@@ -149,7 +149,7 @@ const peopleBasePath = computed(() => isAdmin.value ? '/admin/executor-people' :
const q = reactive({ orgType: 'executor', orgName: '', taxNo: '', status: '' })
const rows = ref([])
const loading = ref(false)
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
const page = reactive({ pageNum: 1, pageSize: 20, total: 0 })
function statusTagType(s) {
// 统一: 0=正常(success), 1=禁用(danger)
@@ -261,7 +261,6 @@ onMounted(load)
<style scoped>
.executor-orgs { padding: 16px; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.filter-form { margin-bottom: 12px; }
.batch-bar { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; }
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
</style>
@@ -66,18 +66,18 @@
</template>
</el-table-column>
<el-table-column label="操作" width="340" fixed="right">
<template #default="{ row }">
<template #default="{ row }"><div class="table-actions">
<!-- 查看 (只读详情): 两角色都有, 跳独立详情页 -->
<el-button link type="primary" @click="goView(row)">查看</el-button>
<el-link :underline="false" type="primary" @click="goView(row)">查看</el-link>
<!-- 编辑: 两角色都有, 跳独立编辑页 -->
<el-button link type="primary" @click="goEdit(row)">编辑</el-button>
<el-link :underline="false" type="primary" @click="goEdit(row)">编辑</el-link>
<!-- 启用/禁用: 两角色都有 (沿用原行为) -->
<el-button link :type="isDisabled(row) ? 'success' : 'danger'" @click="onToggleStatus(row)">
<el-link :underline="false" :type="isDisabled(row) ? 'success' : 'danger'" @click="onToggleStatus(row)">
{{ isDisabled(row) ? '启用' : '禁用' }}
</el-button>
</el-link>
<!-- 重置密码: 重置为默认 123456 (与新建人员默认密码一致) -->
<el-button v-if="row.userId" link type="primary" @click="onResetPwd(row)">重置密码</el-button>
</template>
<el-link :underline="false" v-if="row.userId" type="primary" @click="onResetPwd(row)">重置密码</el-link>
</div></template>
</el-table-column>
</el-table>
@@ -143,7 +143,7 @@ const orgName = computed(() => route.query.orgName || '')
const q = reactive({ name: '', phone: '', email: '', orgName: '', department: '', position: '' })
const rows = ref([])
const loading = ref(false)
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
const page = reactive({ pageNum: 1, pageSize: 20, total: 0 })
function statusTagType(s) {
// 统一: 0=正常(success), 1=禁用(danger)
@@ -310,7 +310,6 @@ watch(() => route.fullPath, () => {
<style scoped>
.executor-people { padding: 16px; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.filter-form { margin-bottom: 12px; }
.batch-bar { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; }
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
</style>
+5 -6
View File
@@ -37,10 +37,10 @@
</template>
</el-table-column>
<el-table-column label="操作" width="220" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="onView(row)">查看</el-button>
<el-button link type="success" @click="onUploadVoucher(row)">上传付款凭证</el-button>
</template>
<template #default="{ row }"><div class="table-actions">
<el-link :underline="false" type="primary" @click="onView(row)">查看</el-link>
<el-link :underline="false" type="success" @click="onUploadVoucher(row)">上传付款凭证</el-link>
</div></template>
</el-table-column>
</el-table>
<div class="pager">
@@ -104,7 +104,7 @@ import { uploadToOss } from '@/utils/oss'
const q = ref({ meetingName: '', expertName: '' })
const rows = ref([])
const loading = ref(false)
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
const page = reactive({ pageNum: 1, pageSize: 20, total: 0 })
const detail = ref({})
const detailOpen = ref(false)
@@ -200,7 +200,6 @@ onMounted(load)
.executor-labor { padding: 16px; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.page-sub { font-size: 13px; color: #909399; margin-bottom: 16px; }
.filter-form { margin-bottom: 12px; }
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
/* 红字提示 (与 People.vue 一致) */
+6 -7
View File
@@ -56,11 +56,11 @@
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="120" show-overflow-tooltip />
<el-table-column label="操作" width="200" fixed="right" align="center">
<template #default="{ row }">
<el-button link type="primary" @click="onView(row)">查看</el-button>
<el-button link type="primary" @click="onUpload(row)">编辑材料</el-button>
<el-button link type="primary" @click="onEdit(row)">修改</el-button>
</template>
<template #default="{ row }"><div class="table-actions">
<el-link :underline="false" type="primary" @click="onView(row)">查看</el-link>
<el-link :underline="false" type="primary" @click="onUpload(row)">编辑材料</el-link>
<el-link :underline="false" type="primary" @click="onEdit(row)">修改</el-link>
</div></template>
</el-table-column>
</el-table>
<div class="pager">
@@ -85,7 +85,7 @@ const q = ref({
})
const rows = ref([])
const loading = ref(false)
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
const page = reactive({ pageNum: 1, pageSize: 20, total: 0 })
// 工具: 时间格式 (yyyy.MM.dd HH:mm), 与 manager 列表一致
function fmtTime(d) {
@@ -139,7 +139,6 @@ onMounted(() => { readQueryFromRoute(); load() })
/* 整页面板 (仿 manager/meetings/Meetings.vue 风格) */
.executor-meetings { padding: 16px; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.filter-form { margin-bottom: 12px; }
.date-sep { margin: 0 4px; color: #606266; }
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
+8 -9
View File
@@ -42,17 +42,17 @@
</template>
</el-table-column>
<el-table-column label="操作" width="300" fixed="right">
<template #default="{ row }">
<el-button size="small" link type="primary" @click="onView(row)">查看</el-button>
<el-button size="small" link type="primary" @click="goEdit(row)">编辑</el-button>
<template #default="{ row }"><div class="table-actions">
<el-link :underline="false" size="small" type="primary" @click="onView(row)">查看</el-link>
<el-link :underline="false" size="small" type="primary" @click="goEdit(row)">编辑</el-link>
<!-- 重置密码: 重置为默认 123456 (与新建人员默认密码一致) -->
<el-button v-if="row.userId" size="small" link type="primary" @click="onResetPwd(row)">重置密码</el-button>
<el-link :underline="false" v-if="row.userId" size="small" type="primary" @click="onResetPwd(row)">重置密码</el-link>
<!-- 本人不显示禁用/恢复按钮 ( sponsor 一样, 避免主账号把自己禁用) -->
<template v-if="row.userId !== store.user?.userId">
<el-button v-if="row.status === '0'" size="small" link type="danger" @click="onToggleStatus(row, '禁用')">禁用</el-button>
<el-button v-else size="small" link type="success" @click="onToggleStatus(row, '恢复')">恢复</el-button>
<el-link :underline="false" v-if="row.status === '0'" size="small" type="danger" @click="onToggleStatus(row, '禁用')">禁用</el-link>
<el-link :underline="false" v-else size="small" type="success" @click="onToggleStatus(row, '恢复')">恢复</el-link>
</template>
</template>
</div></template>
</el-table-column>
</el-table>
<div class="pager">
@@ -122,7 +122,7 @@ const store = useUserStore()
const list = ref([])
const loading = ref(false)
const q = reactive({ name: '', phone: '', orgName: '', department: '', position: '' })
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
const page = reactive({ pageNum: 1, pageSize: 20, total: 0 })
const importOpen = ref(false)
@@ -246,5 +246,4 @@ onMounted(loadList)
.hint-link { color: var(--brand-primary); cursor: pointer; font-size: 13px; }
.hint-link:hover { color: var(--brand-primary-deep); }
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
.filter-form { margin-bottom: 12px; }
</style>
+6 -7
View File
@@ -77,11 +77,11 @@
<el-table-column prop="startTime" label="项目开始时间" width="150" />
<el-table-column prop="endTime" label="项目结束时间" width="150" />
<el-table-column label="操作" width="250" fixed="right">
<template #default="{ row }">
<el-button link type="primary" :disabled="atCap(row)" :title="atCap(row) ? '已建会议数已达分配场次上限' : ''" @click="onCreateMeeting(row)">建会</el-button>
<el-button link type="primary" @click="viewDetail(row)">查看</el-button>
<el-button v-if="!isSub" link type="primary" @click="onAssign(row)">分配</el-button>
</template>
<template #default="{ row }"><div class="table-actions">
<el-link :underline="false" type="primary" :disabled="atCap(row)" :title="atCap(row) ? '已建会议数已达分配场次上限' : ''" @click="onCreateMeeting(row)">建会</el-link>
<el-link :underline="false" type="primary" @click="viewDetail(row)">查看</el-link>
<el-link :underline="false" v-if="!isSub" type="primary" @click="onAssign(row)">分配</el-link>
</div></template>
</el-table-column>
</el-table>
<div class="pager">
@@ -137,7 +137,7 @@ const list = ref([])
const loading = ref(false)
const query = reactive({ projectNo: '', projectName: '', isFinished: '', isSettled: '' })
const dateRange = ref([])
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
const page = reactive({ pageNum: 1, pageSize: 20, total: 0 })
const selected = ref([])
// 项目分配 (执行人): 仿 sponsor/SponsorProjects.vue
@@ -284,7 +284,6 @@ onMounted(() => { readQueryFromRoute(); loadList() })
.batch-bar { margin-bottom: 12px; display: flex; gap: 8px; align-items: center; }
.filter-tip { color: #909399; font-size: 12px; }
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
.filter-form { margin-bottom: 12px; }
.assign-project-info { background: #f5f7fa; padding: 12px 16px; border-radius: 4px; margin-bottom: 12px; }
.assign-project-info > div { display: flex; line-height: 22px; }
.assign-project-info .lbl { color: #909399; width: 90px; flex-shrink: 0; }
+13 -14
View File
@@ -63,8 +63,8 @@
<el-table-column label="执业证书" width="170">
<template #default="{ row }">
<template v-if="row.practiceCertUrl">
<el-button link type="primary" @click="onPreviewCert(row.practiceCertUrl)">查看</el-button>
<el-button link type="primary" @click="onDownloadCert(row.practiceCertUrl, row.phone)">下载</el-button>
<el-link :underline="false" type="primary" @click="onPreviewCert(row.practiceCertUrl)">查看</el-link>
<el-link :underline="false" type="primary" @click="onDownloadCert(row.practiceCertUrl, row.phone)">下载</el-link>
</template>
<span v-else style="color:#c0c4cc">-</span>
</template>
@@ -72,8 +72,8 @@
<el-table-column label="职称证明" width="170">
<template #default="{ row }">
<template v-if="row.titleCertUrl">
<el-button link type="primary" @click="onPreviewCert(row.titleCertUrl)">查看</el-button>
<el-button link type="primary" @click="onDownloadCert(row.titleCertUrl, row.phone)">下载</el-button>
<el-link :underline="false" type="primary" @click="onPreviewCert(row.titleCertUrl)">查看</el-link>
<el-link :underline="false" type="primary" @click="onDownloadCert(row.titleCertUrl, row.phone)">下载</el-link>
</template>
<span v-else style="color:#c0c4cc">-</span>
</template>
@@ -93,20 +93,20 @@
</el-table-column>
<el-table-column prop="auditBy" label="审批人" width="120" align="center" />
<el-table-column label="操作" :width="isAdmin ? 320 : 240" fixed="right">
<template #default="{ row }">
<template #default="{ row }"><div class="table-actions">
<!-- 审核 (通过/拒绝): 两角色都有, manager 主业, admin 可兜底 -->
<el-button link type="primary" @click="onAudit(row)">审核</el-button>
<el-link :underline="false" type="primary" @click="onAudit(row)">审核</el-link>
<!-- 修改: 两角色都有 (独立页编辑, route 自动决定回跳) -->
<el-button link type="primary" @click="goEdit(row)">修改</el-button>
<el-link :underline="false" type="primary" @click="goEdit(row)">修改</el-link>
<!-- 查看 (只读详情): admin, manager 直接在审核 dialog -->
<el-button v-if="isAdmin" link type="primary" @click="goView(row)">查看</el-button>
<el-link :underline="false" v-if="isAdmin" type="primary" @click="goView(row)">查看</el-link>
<!-- 启用/禁用: admin, 是账号管理动作 (同步 sys_user.status) -->
<el-button v-if="isAdmin" link :type="row.status === 'N' ? 'success' : 'danger'" @click="row.status === 'N' ? onEnable(row) : onDisable(row)">
<el-link :underline="false" v-if="isAdmin" :type="row.status === 'N' ? 'success' : 'danger'" @click="row.status === 'N' ? onEnable(row) : onDisable(row)">
{{ row.status === 'N' ? '恢复' : '禁用' }}
</el-button>
</el-link>
<!-- 已拒绝 (查意见): auditStatus=3 时显示, 两角色都可见 -->
<el-button v-if="row.auditStatus === '3'" link type="info" @click="onViewAuditOpinion(row)">已拒绝</el-button>
</template>
<el-link :underline="false" v-if="row.auditStatus === '3'" type="info" @click="onViewAuditOpinion(row)">已拒绝</el-link>
</div></template>
</el-table-column>
</el-table>
@@ -239,7 +239,7 @@ const rows = ref([])
const selection = ref([])
const loading = ref(false)
const submitting = ref(false)
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
const page = reactive({ pageNum: 1, pageSize: 20, total: 0 })
function goNew() { router.push(listBasePath.value + '/new') }
function goEdit(row) { router.push(listBasePath.value + '/edit/' + row.expertId) }
@@ -464,7 +464,6 @@ watch(() => route.fullPath, () => {
<style scoped>
.experts-list { padding: 16px; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.filter-form { margin-bottom: 12px; }
.batch-bar { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; flex-wrap: wrap; }
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
+5 -6
View File
@@ -46,10 +46,10 @@
<el-table-column prop="createTime" label="创建时间" width="170" />
<el-table-column v-if="false" label="操作" width="220" align="center" fixed="right">
<template #default="{ row }">
<el-button v-if="row.intentStatus !== '已通过'" type="primary" link size="small" @click="changeStatus(row, '已通过')">通过</el-button>
<el-button v-if="row.intentStatus !== '已拒绝'" type="danger" link size="small" @click="changeStatus(row, '已拒绝')">拒绝</el-button>
<el-button v-if="row.intentStatus !== '待审核'" type="warning" link size="small" @click="changeStatus(row, '待审核')">重置</el-button>
<el-button type="danger" link size="small" @click="removeOne(row)">删除</el-button>
<el-link :underline="false" v-if="row.intentStatus !== '已通过'" type="primary" @click="changeStatus(row, '已通过')">通过</el-link>
<el-link :underline="false" v-if="row.intentStatus !== '已拒绝'" type="danger" @click="changeStatus(row, '已拒绝')">拒绝</el-link>
<el-link :underline="false" v-if="row.intentStatus !== '待审核'" type="warning" @click="changeStatus(row, '待审核')">重置</el-link>
<el-link :underline="false" type="danger" @click="removeOne(row)">删除</el-link>
</template>
</el-table-column>
</el-table>
@@ -72,7 +72,7 @@ import {
const q = ref({ projectNo: '', projectName: '', name: '', workUnit: '', phone: '', intentStatus: '' })
const rows = ref([])
const loading = ref(false)
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
const page = reactive({ pageNum: 1, pageSize: 20, total: 0 })
async function load() {
loading.value = true
@@ -145,6 +145,5 @@ onMounted(load)
<style scoped>
.manager-exec-intent { padding: 16px; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.filter-form { margin-bottom: 12px; }
.pager { display: flex; justify-content: flex-end; align-items: center; margin-top: 12px; }
</style>
@@ -96,7 +96,7 @@
<span class="notice-label">{{ n.label }}</span>
<span v-if="n.url" class="notice-file">
<span class="file-name">{{ n.name || '已上传' }}</span>
<el-button link type="primary" @click="openPreview(n.url, n.label)">查看</el-button>
<el-link :underline="false" type="primary" @click="openPreview(n.url, n.label)">查看</el-link>
</span>
<span v-else class="file-empty">未上传</span>
</div>
@@ -102,12 +102,12 @@
</el-table-column>
<el-table-column label="操作" width="80">
<template #default="{ $index }">
<el-button link type="danger" :disabled="$index === 0 && assignForm.execRows.length === 1" @click="assignForm.execRows.splice($index, 1)">删除</el-button>
<el-link :underline="false" type="danger" :disabled="$index === 0 && assignForm.execRows.length === 1" @click="assignForm.execRows.splice($index, 1)">删除</el-link>
</template>
</el-table-column>
</el-table>
<div style="margin-top:8px">
<el-button type="primary" link @click="addExecRow">+ 增加执行方</el-button>
<el-link :underline="false" type="primary" @click="addExecRow">+ 增加执行方</el-link>
</div>
<el-form-item label="提交截止()" style="margin-top:12px">
+8 -9
View File
@@ -48,8 +48,8 @@
<el-table-column label="设计文件" width="160" align="center">
<template #default="{ row }">
<template v-if="row.designFileUrl">
<el-button link type="primary" @click="openDesign(row)">查看</el-button>
<el-button link type="primary" @click="downloadFile(row)">下载</el-button>
<el-link :underline="false" type="primary" @click="openDesign(row)">查看</el-link>
<el-link :underline="false" type="primary" @click="downloadFile(row)">下载</el-link>
</template>
<span v-else style="color:#c0c4cc">-</span>
</template>
@@ -69,11 +69,11 @@
<el-table-column prop="projectNo" label="项目编号" width="140" align="center" />
<el-table-column prop="remark" label="备注" min-width="160" show-overflow-tooltip />
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="onAudit(row)">审核</el-button>
<el-button link type="primary" @click="goEdit(row)">修改</el-button>
<el-button link type="primary" @click="onSettle(row)" v-if="row.isSettled!=='Y'">结算</el-button>
</template>
<template #default="{ row }"><div class="table-actions">
<el-link :underline="false" type="primary" @click="onAudit(row)">审核</el-link>
<el-link :underline="false" type="primary" @click="goEdit(row)">修改</el-link>
<el-link :underline="false" type="primary" @click="onSettle(row)" v-if="row.isSettled!=='Y'">结算</el-link>
</div></template>
</el-table-column>
</el-table>
<div class="pager">
@@ -173,7 +173,7 @@ const q = ref({ planName: '', projectNo: '', planDirectionId: '', planCategory:
const rows = ref([]); const selection = ref([])
const loading = ref(false)
const submitting = ref(false)
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
const page = reactive({ pageNum: 1, pageSize: 20, total: 0 })
async function load() {
loading.value = true
@@ -350,7 +350,6 @@ onMounted(() => { loadPlanDirectionOptions(); load() })
border-left: 3px solid var(--brand-primary);
}
.page-sub { font-size: 13px; color: #909399; margin: 0 0 16px; }
.filter-form { margin-bottom: 12px; }
.batch-bar { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; }
.filter-tip { color: #909399; font-size: 12px; }
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
+7 -8
View File
@@ -116,14 +116,14 @@
<el-table-column prop="startTime" label="项目开始时间" width="120" align="center" :formatter="fmtDate" />
<el-table-column prop="endTime" label="项目结束时间" width="120" align="center" :formatter="fmtDate" />
<el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }">
<template #default="{ row }"><div class="table-actions">
<div class="op-cell">
<el-button link type="primary" @click="doCreateMeeting(row)">建会</el-button>
<el-button link type="primary" @click="doAssign(row)">分配</el-button>
<el-link :underline="false" type="primary" @click="doCreateMeeting(row)">建会</el-link>
<el-link :underline="false" type="primary" @click="doAssign(row)">分配</el-link>
<el-dropdown trigger="click" @command="(cmd) => onAction(cmd, row)">
<el-button link type="primary" class="op-dropdown">
<el-link :underline="false" type="primary" class="op-dropdown">
更多<el-icon class="op-caret"><ArrowDown /></el-icon>
</el-button>
</el-link>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-if="row.isFinished !== '1' && row.isFinished !== 1" command="close">结题</el-dropdown-item>
@@ -138,7 +138,7 @@
</template>
</el-dropdown>
</div>
</template>
</div></template>
</el-table-column>
</el-table>
<el-pagination v-model:current-page="page.pageNum" v-model:page-size="page.pageSize" :total="page.total" :page-sizes="[10,20,50]" layout="total, sizes, prev, pager, next, jumper" @current-change="load" @size-change="load" />
@@ -253,7 +253,7 @@ const route = useRoute()
const q = ref({ sponsorOrgIds: [], execOrgIds: [], projectNo: '', projectName: '', projectForm: '', startTime: '', endTime: '', isFinished: '', isSettled: '' })
const rows = ref([])
const loading = ref(false)
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
const page = reactive({ pageNum: 1, pageSize: 20, total: 0 })
async function load() {
loading.value = true
@@ -695,7 +695,6 @@ watch(() => route.path + (route.query._t || ''), () => load())
/* 1:1 抄 sponsor/SponsorProjects.vue */
.page-card { background: #fff; padding: 16px 20px; border-radius: 6px; border: 1px solid #f0f0f0; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.filter-form { display: flex; flex-wrap: wrap; gap: 0 16px; padding-bottom: 12px; border-bottom: 1px solid #f0f0f0; margin-bottom: 12px; }
.toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
/* 操作列下拉 */
+2 -2
View File
@@ -94,10 +94,10 @@
<el-input v-if="r.role === '其他'" v-model="r.customName" placeholder="角色名" class="role-custom" />
<el-input-number v-model="r.amount" :min="0" :precision="2" placeholder="劳务金额" class="role-amount" />
<div class="row-actions">
<el-button link type="danger" :disabled="form.roleRows.length<=1" @click="removeRoleRowAt(idx)">删除角色</el-button>
<el-link :underline="false" type="danger" :disabled="form.roleRows.length<=1" @click="removeRoleRowAt(idx)">删除角色</el-link>
</div>
</div>
<el-button link type="primary" @click="addRoleRow" style="margin-top: 8px;">+ 增加角色</el-button>
<el-link :underline="false" type="primary" @click="addRoleRow" style="margin-top: 8px;">+ 增加角色</el-link>
<!-- ========== 第三区块公告文件 ========== -->
<div class="new-card-title">公告文件</div>
+5 -6
View File
@@ -46,10 +46,10 @@
<el-table-column prop="createTime" label="创建时间" width="170" align="center" />
<el-table-column v-if="false" label="操作" width="220" align="center" fixed="right">
<template #default="{ row }">
<el-button v-if="row.intentStatus !== '已通过'" type="primary" link size="small" @click="changeStatus(row, '已通过')">通过</el-button>
<el-button v-if="row.intentStatus !== '已拒绝'" type="danger" link size="small" @click="changeStatus(row, '已拒绝')">拒绝</el-button>
<el-button v-if="row.intentStatus !== '待审核'" type="warning" link size="small" @click="changeStatus(row, '待审核')">重置</el-button>
<el-button type="danger" link size="small" @click="removeOne(row)">删除</el-button>
<el-link :underline="false" v-if="row.intentStatus !== '已通过'" type="primary" @click="changeStatus(row, '已通过')">通过</el-link>
<el-link :underline="false" v-if="row.intentStatus !== '已拒绝'" type="danger" @click="changeStatus(row, '已拒绝')">拒绝</el-link>
<el-link :underline="false" v-if="row.intentStatus !== '待审核'" type="warning" @click="changeStatus(row, '待审核')">重置</el-link>
<el-link :underline="false" type="danger" @click="removeOne(row)">删除</el-link>
</template>
</el-table-column>
</el-table>
@@ -72,7 +72,7 @@ import {
const q = ref({ projectNo: '', projectName: '', name: '', workUnit: '', phone: '', intentStatus: '' })
const rows = ref([])
const loading = ref(false)
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
const page = reactive({ pageNum: 1, pageSize: 20, total: 0 })
async function load() {
loading.value = true
@@ -145,7 +145,6 @@ onMounted(load)
<style scoped>
.manager-support-intent { padding: 16px; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.filter-form { margin-bottom: 12px; }
.pager { display: flex; justify-content: flex-end; align-items: center; margin-top: 12px; }
</style>
+7 -7
View File
@@ -41,7 +41,7 @@
<div class="tag-list">
<el-tag v-for="u in supervisors" :key="u.sponsorOrgId" type="info" size="small">{{ u.orgName }}</el-tag>
<span v-if="!supervisors.length" class="text-muted">- 未分配 -</span>
<el-button v-if="isManager && !isReadonly" link type="primary" size="small" @click="openAssignDialog('supervisor')">分配</el-button>
<el-link :underline="false" v-if="isManager && !isReadonly" type="primary" @click="openAssignDialog('supervisor')">分配</el-link>
</div>
</div>
<div class="info-row info-row-full">
@@ -49,7 +49,7 @@
<div class="tag-list">
<el-tag v-for="u in executors" :key="u.executorOrgId" type="success" size="small">{{ u.orgName }}</el-tag>
<span v-if="!executors.length" class="text-muted">- 未分配 -</span>
<el-button v-if="isManager && !isReadonly" link type="primary" size="small" @click="openAssignDialog('executor')">分配</el-button>
<el-link :underline="false" v-if="isManager && !isReadonly" type="primary" @click="openAssignDialog('executor')">分配</el-link>
</div>
</div>
</template>
@@ -101,7 +101,7 @@
</el-table-column>
<el-table-column v-if="!isSponsor" label="身份证附件" min-width="90" align="center">
<template #default="{ row }">
<el-button v-if="idCardFiles(row).length" link type="primary" size="small" @click="openPreview(idCardFiles(row), '身份证')">查看</el-button>
<el-link :underline="false" v-if="idCardFiles(row).length" type="primary" @click="openPreview(idCardFiles(row), '身份证')">查看</el-link>
<span v-else class="text-muted">-</span>
</template>
</el-table-column>
@@ -121,7 +121,7 @@
<el-table-column prop="laborForm" label="角色" min-width="90" show-overflow-tooltip />
<el-table-column label="劳务协议" width="120" align="center">
<template #default="{ row }">
<el-button v-if="protocolUrl(row)" link type="primary" size="small" @click="openPreview([protocolUrl(row)], '劳务协议')">查看</el-button>
<el-link :underline="false" v-if="protocolUrl(row)" type="primary" @click="openPreview([protocolUrl(row)], '劳务协议')">查看</el-link>
<span v-else class="text-muted">-</span>
</template>
</el-table-column>
@@ -152,11 +152,11 @@
<el-table-column v-if="!isSponsor && !isReadonly" label="操作" width="100" align="center" fixed="right">
<template #default="{ row }">
<div class="row-actions">
<el-button link type="primary" size="small" @click="openAttendeeDialog(row)">编辑</el-button>
<el-link :underline="false" type="primary" @click="openAttendeeDialog(row)">编辑</el-link>
<el-dropdown trigger="click" @command="(cmd) => onAttendeeCommand(cmd, row)">
<el-button link type="primary" size="small" :loading="inviteLoadingId === row.id || esignLoadingId === row.id">
<el-link :underline="false" type="primary" :disabled="inviteLoadingId === row.id || esignLoadingId === row.id">
更多<el-icon class="el-icon--right"><arrow-down /></el-icon>
</el-button>
</el-link>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="invite" :disabled="inviteLoadingId === row.id">邀请参会</el-dropdown-item>
+42 -17
View File
@@ -69,21 +69,35 @@
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="120" show-overflow-tooltip />
<el-table-column label="操作" width="500" fixed="right" align="center">
<template #default="{ row }">
<el-button link type="primary" @click="viewOnly(row)">查看</el-button>
<el-button v-if="isRole('manager') && isCompliancePending(row)" link type="warning" @click="onAudit(row)">审核</el-button>
<el-button v-if="isRole('admin', 'manager')" link type="primary" @click="viewDetail(row)">编辑材料</el-button>
<el-button v-if="isRole('admin', 'manager')" link type="primary" @click="onEdit(row)">修改</el-button>
<el-button v-if="isRole('admin', 'manager')" link type="primary" @click="onCopy(row)">复制</el-button>
<el-button v-if="isRole('admin', 'manager') && canSettleRow(row)" link type="success" @click="onSettle(row)">结算</el-button>
<el-button v-if="isRole('admin', 'manager') && canFinishRow(row)" link type="primary" @click="onFinish(row)">完结</el-button>
<el-button v-if="isRole('admin', 'manager') && isOneVal(row.isFrozen)" link type="warning" @click="onUnfreeze(row)">解冻</el-button>
<el-button v-if="isRole('admin', 'manager')" link type="primary" @click="onDownloadService(row)">会务下载</el-button>
<el-button v-if="isRole('admin', 'manager')" link type="primary" @click="onDownloadLabor(row)">劳务下载</el-button>
<el-button v-if="isRole('admin', 'manager')" link type="danger" @click="onDelete(row)">删除</el-button>
<el-button v-if="isRole('sponsor') && isSponsorPending(row)" link type="warning" @click="onAudit(row)">审核</el-button>
</template>
<el-table-column label="操作" width="260" fixed="right" align="center">
<template #default="{ row }"><div class="table-actions">
<!-- 主操作: 查看 / 审核 / 编辑材料 (按角色显隐) -->
<el-link :underline="false" type="primary" @click="viewOnly(row)">查看</el-link>
<el-link :underline="false" v-if="isRole('manager') && isCompliancePending(row)" type="warning" @click="onAudit(row)">审核</el-link>
<el-link :underline="false" v-if="isRole('admin', 'manager')" type="primary" @click="viewDetail(row)">编辑材料</el-link>
<!-- 更多: admin/manager 视角 7 个次操作折叠 (sponsor 视角只 1 个审核, 始终走外置) -->
<el-dropdown v-if="isRole('admin', 'manager')" trigger="click" @command="(cmd) => onMoreAction(cmd, row)">
<el-link :underline="false" type="primary" class="op-dropdown">
更多<el-icon class="op-caret"><ArrowDown /></el-icon>
</el-link>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="edit">修改</el-dropdown-item>
<el-dropdown-item command="copy">复制</el-dropdown-item>
<el-dropdown-item v-if="canSettleRow(row)" command="settle">结算</el-dropdown-item>
<el-dropdown-item v-if="canFinishRow(row)" command="finish">完结</el-dropdown-item>
<el-dropdown-item v-if="isOneVal(row.isFrozen)" command="unfreeze">解冻</el-dropdown-item>
<el-dropdown-item command="downloadService">会务下载</el-dropdown-item>
<el-dropdown-item command="downloadLabor">劳务下载</el-dropdown-item>
<el-dropdown-item divided command="delete">
<span style="color:#DC2626">删除</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<!-- sponsor 视角: 额外只看合规审核 -->
<el-link :underline="false" v-if="isRole('sponsor') && isSponsorPending(row)" type="warning" @click="onAudit(row)">审核</el-link>
</div></template>
</el-table-column>
</el-table>
<div class="pager">
@@ -132,6 +146,7 @@ import { bizList, bizDelete } from '@/api/public'
import request from '@/utils/request'
import { stageLabel, STAGE_OPTIONS, stageClass } from '@/utils/meetingStage'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ArrowDown } from '@element-plus/icons-vue'
// ========== 角色感知 (admin/manager 共用此页, 按 URL 第 1 段识别) ==========
const router = useRouter()
@@ -159,7 +174,7 @@ const q = ref({
})
const rows = ref([])
const loading = ref(false)
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
const page = reactive({ pageNum: 1, pageSize: 20, total: 0 })
// ========== 表格多选 ==========
const selectedIds = ref([])
@@ -211,6 +226,17 @@ function viewDetail(row) {
function viewOnly(row) {
router.push(`${detailBase.value}/view/${row.meetingId}`)
}
// 操作列"更多"下拉分发: 把 cmd 映射到现有 handler
function onMoreAction(cmd, row) {
if (cmd === 'edit') onEdit(row)
else if (cmd === 'copy') onCopy(row)
else if (cmd === 'settle') onSettle(row)
else if (cmd === 'finish') onFinish(row)
else if (cmd === 'unfreeze') onUnfreeze(row)
else if (cmd === 'downloadService') onDownloadService(row)
else if (cmd === 'downloadLabor') onDownloadLabor(row)
else if (cmd === 'delete') onDelete(row)
}
// 修改 / 复制: 按角色选 admin-meetings-new 或 manager-meetings-new (MeetingNew.vue 公共页)
function onEdit(row) {
router.push({ name: newRouteName.value, query: { meetingId: row.meetingId, projectId: row.projectId || '' } })
@@ -439,7 +465,6 @@ onMounted(() => { readQueryFromRoute(); load() })
/* 整页面板 (与 People.vue 风格一致, 双角色共用) */
.meetings-list { padding: 16px; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.filter-form { margin-bottom: 12px; }
.batch-bar { margin-bottom: 12px; display: flex; gap: 8px; align-items: center; }
.filter-tip { color: #909399; font-size: 12px; margin-left: auto; }
.date-sep { margin: 0 4px; color: #606266; }
+7 -7
View File
@@ -25,12 +25,12 @@
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="180" show-overflow-tooltip />
<el-table-column label="操作" width="320" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="openDetail(row)">详情</el-button>
<el-button link type="primary" :disabled="!row.templateUrl" @click="openPreview(row, 'template')">模板</el-button>
<el-button link type="primary" :disabled="!row.qrCodeUrl" @click="openPreview(row, 'qr')">QR</el-button>
<el-button link type="primary" :disabled="!row.shareUrl" @click="openPreview(row, 'share')">分享</el-button>
</template>
<template #default="{ row }"><div class="table-actions">
<el-link :underline="false" type="primary" @click="openDetail(row)">详情</el-link>
<el-link :underline="false" type="primary" :disabled="!row.templateUrl" @click="openPreview(row, 'template')">模板</el-link>
<el-link :underline="false" type="primary" :disabled="!row.qrCodeUrl" @click="openPreview(row, 'qr')">QR</el-link>
<el-link :underline="false" type="primary" :disabled="!row.shareUrl" @click="openPreview(row, 'share')">分享</el-link>
</div></template>
</el-table-column>
</el-table>
<el-pagination
@@ -77,7 +77,7 @@ import Preview from '@/components/Preview.vue'
const q = ref({ projectNo: '', title: '' })
const rows = ref([])
const loading = ref(false)
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
const page = reactive({ pageNum: 1, pageSize: 20, total: 0 })
async function load() {
loading.value = true
+5 -5
View File
@@ -25,10 +25,10 @@
</el-table-column>
<el-table-column prop="supportCount" label="支持单位数" width="120" align="center" />
<el-table-column label="操作" width="220" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="openDetail(row)">详情</el-button>
<el-button link type="primary" :disabled="!row.fileUrl" @click="openPreview(row)">预览</el-button>
</template>
<template #default="{ row }"><div class="table-actions">
<el-link :underline="false" type="primary" @click="openDetail(row)">详情</el-link>
<el-link :underline="false" type="primary" :disabled="!row.fileUrl" @click="openPreview(row)">预览</el-link>
</div></template>
</el-table-column>
</el-table>
<el-pagination
@@ -66,7 +66,7 @@ import Preview from '@/components/Preview.vue'
const q = ref({ projectNo: '', title: '' })
const rows = ref([])
const loading = ref(false)
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
const page = reactive({ pageNum: 1, pageSize: 20, total: 0 })
async function load() {
loading.value = true
@@ -54,18 +54,18 @@
<el-table-column prop="contactName" label="联系人" width="100" align="center" />
<el-table-column prop="contactPhone" label="联系电话" width="130" align="center" />
<el-table-column label="操作" :width="isAdmin ? 320 : 240" fixed="right">
<template #default="{ row }">
<template #default="{ row }"><div class="table-actions">
<!-- 查看: 两角色都有 (manager 原版本有, admin 原版本用 alert, 这里统一用 dialog 更清晰) -->
<el-button link type="primary" @click="onView(row)">查看</el-button>
<el-link :underline="false" type="primary" @click="onView(row)">查看</el-link>
<!-- 编辑: admin -->
<el-button v-if="isAdmin" link type="primary" @click="onEdit(row)">编辑</el-button>
<el-link :underline="false" v-if="isAdmin" type="primary" @click="onEdit(row)">编辑</el-link>
<!-- 人员管理: 两角色都有, 按角色跳 -->
<el-button link type="primary" @click="onPeople(row)">人员管理</el-button>
<el-link :underline="false" type="primary" @click="onPeople(row)">人员管理</el-link>
<!-- 启用/禁用: 两角色都有 (沿用原行为) -->
<el-button link :type="isDisabled(row) ? 'success' : 'danger'" @click="onToggleStatus(row)">
<el-link :underline="false" :type="isDisabled(row) ? 'success' : 'danger'" @click="onToggleStatus(row)">
{{ isDisabled(row) ? '恢复' : '禁用' }}
</el-button>
</template>
</el-link>
</div></template>
</el-table-column>
</el-table>
@@ -181,7 +181,7 @@ const peopleBasePath = computed(() => isAdmin.value ? '/admin/sponsor-people' :
const q = reactive({ orgType: 'sponsor', orgName: '', taxNo: '', status: '' })
const rows = ref([])
const loading = ref(false)
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
const page = reactive({ pageNum: 1, pageSize: 20, total: 0 })
function statusTagType(s) {
// 统一: 0=正常(success), 1=禁用(danger)
@@ -362,7 +362,6 @@ onMounted(load)
<style scoped>
.sponsor-orgs { padding: 16px; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.filter-form { margin-bottom: 12px; }
.batch-bar { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; }
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
</style>
@@ -62,18 +62,18 @@
</template>
</el-table-column>
<el-table-column label="操作" width="340" fixed="right">
<template #default="{ row }">
<template #default="{ row }"><div class="table-actions">
<!-- 查看 (只读详情): 两角色都有, 跳独立详情页 -->
<el-button link type="primary" @click="goView(row)">查看</el-button>
<el-link :underline="false" type="primary" @click="goView(row)">查看</el-link>
<!-- 编辑: 两角色都有, 跳独立编辑页 -->
<el-button link type="primary" @click="goEdit(row)">编辑</el-button>
<el-link :underline="false" type="primary" @click="goEdit(row)">编辑</el-link>
<!-- 启用/禁用: 两角色都有 (沿用原行为) -->
<el-button link :type="isDisabled(row) ? 'success' : 'danger'" @click="onToggleStatus(row)">
<el-link :underline="false" :type="isDisabled(row) ? 'success' : 'danger'" @click="onToggleStatus(row)">
{{ isDisabled(row) ? '启用' : '禁用' }}
</el-button>
</el-link>
<!-- 重置密码: 重置为默认 123456 (与新建人员默认密码一致) -->
<el-button v-if="row.userId" link type="primary" @click="onResetPwd(row)">重置密码</el-button>
</template>
<el-link :underline="false" v-if="row.userId" type="primary" @click="onResetPwd(row)">重置密码</el-link>
</div></template>
</el-table-column>
</el-table>
@@ -139,7 +139,7 @@ const orgName = computed(() => route.query.orgName || '')
const q = reactive({ name: '', phone: '', orgName: '', department: '', position: '' })
const rows = ref([])
const loading = ref(false)
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
const page = reactive({ pageNum: 1, pageSize: 20, total: 0 })
function statusTagType(s) {
// 统一: 0=正常(success), 1=禁用(danger)
@@ -319,7 +319,6 @@ watch(() => route.fullPath, () => {
<style scoped>
.sponsor-people { padding: 16px; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.filter-form { margin-bottom: 12px; }
.batch-bar { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; }
.filter-tip { color: #909399; font-size: 12px; }
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
+10 -11
View File
@@ -70,15 +70,15 @@
</el-table-column>
<el-table-column label="项目形式" prop="projectForm" width="100" />
<el-table-column label="操作" width="420" fixed="right">
<template #default="{ row }">
<el-button size="small" link type="primary" @click="onView(row)">查看</el-button>
<el-button size="small" link type="primary" @click="onEdit(row)">修改</el-button>
<el-button size="small" link type="warning" :disabled="!canSettle(row)" @click="onSettle(row)">结算</el-button>
<el-button size="small" link type="primary" @click="onAudit(row)">审核</el-button>
<el-button size="small" link @click="onDownload(row, 'labor')">劳务下载</el-button>
<el-button size="small" link @click="onDownload(row, 'meeting')">会务下载</el-button>
<el-button size="small" link type="danger" :disabled="row.currentStage !== 'FROZEN'" @click="onUnfreeze(row)">解冻</el-button>
</template>
<template #default="{ row }"><div class="table-actions">
<el-link :underline="false" size="small" type="primary" @click="onView(row)">查看</el-link>
<el-link :underline="false" size="small" type="primary" @click="onEdit(row)">修改</el-link>
<el-link :underline="false" size="small" type="warning" :disabled="!canSettle(row)" @click="onSettle(row)">结算</el-link>
<el-link :underline="false" size="small" type="primary" @click="onAudit(row)">审核</el-link>
<el-link :underline="false" size="small" @click="onDownload(row, 'labor')">劳务下载</el-link>
<el-link :underline="false" size="small" @click="onDownload(row, 'meeting')">会务下载</el-link>
<el-link :underline="false" size="small" type="danger" :disabled="row.currentStage !== 'FROZEN'" @click="onUnfreeze(row)">解冻</el-link>
</div></template>
</el-table-column>
</el-table>
<div class="pager">
@@ -174,7 +174,7 @@ const q = reactive({
projectNo: '', meetingId: '', meetingName: '', periodNo: null,
startTimeRange: null, projectForm: '', currentStage: '', remark: ''
})
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
const page = reactive({ pageNum: 1, pageSize: 20, total: 0 })
const view = ref({})
const viewOpen = ref(false)
@@ -323,7 +323,6 @@ onMounted(() => { readQueryFromRoute(); loadList() })
/* 1:1 抄 doctor/Submissions.vue + SponsorPeople.vue */
.page-card { background: #fff; padding: 16px 20px; border-radius: 6px; border: 1px solid #f0f0f0; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.filter-form { display: flex; flex-wrap: wrap; gap: 0 16px; padding-bottom: 12px; border-bottom: 1px solid #f0f0f0; margin-bottom: 12px; }
.batch-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
</style>
+13 -16
View File
@@ -59,9 +59,7 @@
<el-table-column type="selection" width="48" />
<el-table-column prop="projectNo" label="项目编号" min-width="140" fixed>
<template #default="{ row }">
<el-link type="primary" :underline="false" @click="openDetail(row)">{{ row.projectNo }}</el-link>
</template>
</el-table-column>
<el-link type="primary" :underline="false" @click="openDetail(row)">{{ row.projectNo }}</el-link></template></el-table-column>
<el-table-column prop="projectName" label="项目名称" min-width="200" show-overflow-tooltip />
<el-table-column prop="totalSessions" label="总场次/总期数" width="120" align="center" />
<el-table-column prop="doneSessions" label="已执行" width="80" align="center" />
@@ -94,17 +92,17 @@
<el-table-column prop="startTime" label="项目开始时间" min-width="160" />
<el-table-column prop="endTime" label="项目结束时间" min-width="160" />
<el-table-column label="操作" width="480" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="onCreateMeeting(row)">建会</el-button>
<el-button link type="primary" @click="openAssign(row)">分配</el-button>
<el-button link type="primary" @click="onFinish(row)">结题</el-button>
<el-button link type="primary" @click="openDetail(row)">查看</el-button>
<el-button link type="primary" @click="openEdit(row)">编辑</el-button>
<el-button link type="primary" @click="onOpen(row)">开通</el-button>
<el-button link type="danger" @click="onDeleteNotice(row)">删除公告</el-button>
<el-button link type="primary" @click="onSupportUnit(row)">支持单位</el-button>
<el-button link type="primary" @click="openRate(row)">执行单位评分</el-button>
</template>
<template #default="{ row }"><div class="table-actions">
<el-link :underline="false" type="primary" @click="onCreateMeeting(row)">建会</el-link>
<el-link :underline="false" type="primary" @click="openAssign(row)">分配</el-link>
<el-link :underline="false" type="primary" @click="onFinish(row)">结题</el-link>
<el-link :underline="false" type="primary" @click="openDetail(row)">查看</el-link>
<el-link :underline="false" type="primary" @click="openEdit(row)">编辑</el-link>
<el-link :underline="false" type="primary" @click="onOpen(row)">开通</el-link>
<el-link :underline="false" type="danger" @click="onDeleteNotice(row)">删除公告</el-link>
<el-link :underline="false" type="primary" @click="onSupportUnit(row)">支持单位</el-link>
<el-link :underline="false" type="primary" @click="openRate(row)">执行单位评分</el-link>
</div></template>
</el-table-column>
</el-table>
@@ -220,7 +218,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
const rows = ref([])
const loading = ref(false)
const q = reactive({ projectNo: '', projectName: '', projectForm: '', isFinished: '', isSettled: '' })
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
const page = reactive({ pageNum: 1, pageSize: 20, total: 0 })
const selected = ref([])
const formOpen = ref(false)
@@ -456,7 +454,6 @@ onMounted(() => { readQueryFromRoute(); load(); loadPersons() })
/* 1:1 抄 doctor/Projects.vue + doctor/Submissions.vue */
.page-card { background: #fff; padding: 16px 20px; border-radius: 6px; border: 1px solid #f0f0f0; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.filter-form { display: flex; flex-wrap: wrap; gap: 0 16px; padding-bottom: 12px; border-bottom: 1px solid #f0f0f0; margin-bottom: 12px; }
.toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
.hint { color: #909399; font-size: 12px; margin-left: 8px; }
.score-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
+8 -9
View File
@@ -65,18 +65,18 @@
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="170" />
<el-table-column label="操作" width="320" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="onView(row)">查看</el-button>
<el-button link type="primary" @click="onEdit(row)">编辑</el-button>
<template #default="{ row }"><div class="table-actions">
<el-link :underline="false" type="primary" @click="onView(row)">查看</el-link>
<el-link :underline="false" type="primary" @click="onEdit(row)">编辑</el-link>
<!-- 重置密码: 重置为默认 123456 (与新建人员默认密码一致) -->
<el-button v-if="row.userId" link type="primary" @click="onResetPwd(row)">重置密码</el-button>
<el-link :underline="false" v-if="row.userId" type="primary" @click="onResetPwd(row)">重置密码</el-link>
<!-- 本人不显示禁用/恢复按钮 (避免主账号把自己禁用导致登录不上) -->
<el-button v-if="row.userId !== store.user?.userId" link :type="row.status === '0' ? 'warning' : 'success'" @click="onToggleStatus(row)">
<el-link :underline="false" v-if="row.userId !== store.user?.userId" :type="row.status === '0' ? 'warning' : 'success'" @click="onToggleStatus(row)">
{{ row.status === '0' ? '禁用' : '恢复' }}
</el-button>
</el-link>
<!-- 本人不显示删除按钮 (避免主账号误删自己的 biz_person) -->
<el-button v-if="row.userId !== store.user?.userId" link type="danger" @click="onRemoveOne(row)">删除</el-button>
</template>
<el-link :underline="false" v-if="row.userId !== store.user?.userId" type="danger" @click="onRemoveOne(row)">删除</el-link>
</div></template>
</el-table-column>
</el-table>
@@ -303,7 +303,6 @@ load()
/* 1:1 抄 doctor/Submissions.vue */
.page-card { background: #fff; padding: 16px 20px; border-radius: 6px; border: 1px solid #f0f0f0; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.filter-form { display: flex; flex-wrap: wrap; gap: 0 16px; padding-bottom: 12px; border-bottom: 1px solid #f0f0f0; margin-bottom: 12px; }
.toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
.hint-link { color: var(--brand-primary); cursor: pointer; font-size: 13px; }
@@ -88,12 +88,12 @@
</template>
</el-table-column>
<el-table-column label="操作" width="240" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="openSingleScore(row)">项目评价</el-button>
<template #default="{ row }"><div class="table-actions">
<el-link :underline="false" type="primary" @click="openSingleScore(row)">项目评价</el-link>
<!-- SUB 子账号仅查看, 不参与分配 -->
<el-button v-if="!isSub" link type="primary" @click="openAssign(row)">分配</el-button>
<el-button link type="primary" @click="onView(row)">查看</el-button>
</template>
<el-link :underline="false" v-if="!isSub" type="primary" @click="openAssign(row)">分配</el-link>
<el-link :underline="false" type="primary" @click="onView(row)">查看</el-link>
</div></template>
</el-table-column>
</el-table>
@@ -387,7 +387,6 @@ onMounted(load)
<style scoped>
.page-card { background: #fff; border: 1px solid #f0f0f0; border-radius: 6px; padding: 16px 20px; }
.breadcrumb { color: #909399; font-size: 13px; margin-bottom: 12px; }
.filter-form { margin-bottom: 12px; }
.toolbar { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
.link { color: var(--brand-primary); }
.link-bold { color: #303133; font-weight: 600; }
+4 -2
View File
@@ -29,9 +29,11 @@ export default defineConfig(({ mode }) => {
host: '0.0.0.0',
proxy: {
[apiPath]: {
target: 'http://127.0.0.1:8080',
// 远程后端: risingdoctor.com/hg-api (HTTPS, 远程 RuoYi v3.9.2)
target: 'https://risingdoctor.com',
changeOrigin: true,
rewrite: (p) => p.replace(new RegExp('^' + apiPath), '')
secure: true,
rewrite: (p) => p.replace(new RegExp('^' + apiPath), '/hg-api')
}
}
}