feat: 会议批量审核 + 公示按发布时间倒序 + 现场照片 dialog 预览 + 重复报错精确

- 会议列表 manager 批量合规审核 (批量通过/退回, best-effort 逐条回执)
- 公示列表按 publish_time 倒序, 发布/开通写本地 publish_time (修 toISOString UTC 时区 bug)
- 签到表/前全景/后全景「查看照片」改 dialog 预览 (复用 Preview.vue)
- 手机号/用户名重复报错精确提示 (后端 fieldMap + 前端 duplicateTip)
- 医生首页欢迎语改「欢迎使用合规系统」
This commit is contained in:
郭庆泰
2026-08-26 00:41:33 +08:00
parent ea2024d083
commit b5b3e3a213
15 changed files with 387 additions and 115 deletions
+161 -92
View File
@@ -42,30 +42,48 @@
</div>
<div class="filter-bar">
<input v-model="currentKeyword" @keydown.enter="applyFilter" type="text" class="filter-input" placeholder="">
<div class="filter-input-wrap">
<svg class="filter-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="11" cy="11" r="7"/>
<path d="M21 21l-4.3-4.3"/>
</svg>
<input v-model="currentKeyword" @keydown.enter="applyFilter" type="text" class="filter-input" placeholder="搜索项目名称">
</div>
<button class="filter-btn" @click="applyFilter">查询</button>
<button class="filter-btn" style="background: #fff; color: var(--brand-primary); border: 1px solid var(--brand-primary);" @click="resetFilter">重置</button>
<button class="filter-btn filter-btn-plain" @click="resetFilter">重置</button>
</div>
<div class="notice-list">
<template v-if="pageItems.length === 0">
<div style="padding: 40px 0; text-align: center; color: #9ca3af; font-size: 13px;">暂无数据</div>
<div class="notice-empty">
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<path d="M14 2v6h6"/>
</svg>
<span>暂无公示项目</span>
</div>
</template>
<template v-else>
<div class="notice-header">
<span class="notice-header-no">序号</span>
<span class="notice-header-title">项目</span>
<span class="notice-header-date">开始日期</span>
<span class="notice-header-end">截止日期</span>
<span class="notice-header-title">项目名称</span>
<span class="notice-header-date">发布日期</span>
</div>
<div v-for="(n, i) in pageItems" :key="n.annId || i" class="notice-item" @click="goDetail(n)">
<span class="notice-item-no">{{ String((currentPage - 1) * PAGE_SIZE + i + 1).padStart(3, '0') }}</span>
<span class="notice-title">
<span v-if="n.projectNo" style="color:#909399;margin-right:8px;font-size:13px">{{ n.projectNo }}</span>
{{ n.title }}
<svg class="notice-arrow" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 6l6 6-6 6"/>
</svg>
</span>
<span class="notice-date">
<svg class="notice-date-icon" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="4" width="18" height="18" rx="2"/>
<line x1="16" y1="2" x2="16" y2="6"/>
<line x1="8" y1="2" x2="8" y2="6"/>
<line x1="3" y1="10" x2="21" y2="10"/>
</svg>
{{ fmtDate(n.publishTime) }}
</span>
<span class="notice-date">{{ fmtDate(n.date) }}</span>
<span class="notice-end">{{ fmtDate(n.endTime) }}</span>
</div>
</template>
</div>
@@ -121,8 +139,7 @@ async function load() {
allRows.value = list.map(r => {
return {
annId: r.projectId,
date: r.startTime,
endTime: r.endTime,
publishTime: r.publishTime,
type: '公示',
projectNo: r.projectNo || '',
projectName: r.projectName,
@@ -430,30 +447,47 @@ main.container {
/* ========== 筛选条 ========== */
.filter-bar {
padding: 12px 0;
border-bottom: 1px solid #f3f4f6;
margin-bottom: 0;
padding: 14px 0;
border-bottom: 1px solid #f0f2f5;
display: flex;
align-items: center;
gap: 14px;
gap: 12px;
font-size: 13px;
}
.filter-input-wrap {
position: relative;
display: flex;
align-items: center;
}
.filter-icon {
position: absolute;
left: 13px;
top: 50%;
transform: translateY(-50%);
color: #9ca3af;
pointer-events: none;
}
.filter-input {
height: 30px;
padding: 0 10px;
border: 1px solid #d1d5db;
background: #fff;
height: 34px;
padding: 0 12px 0 34px;
border: 1px solid #dde1e6;
border-radius: 17px;
background: #f7f8fa;
font-size: 13px;
color: #1f2937;
font-family: inherit;
outline: none;
width: 220px;
transition: border-color 0.2s;
width: 240px;
transition: border-color 0.2s, background 0.2s, box-shadow 0.2s;
}
.filter-input:focus {
border-color: var(--brand-primary);
background: #fff;
box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.05);
}
.filter-input::placeholder {
@@ -461,20 +495,33 @@ main.container {
}
.filter-btn {
height: 30px;
padding: 0 18px;
height: 34px;
padding: 0 20px;
background: var(--brand-primary);
color: #fff;
border: none;
font-size: 12px;
border: 1px solid var(--brand-primary);
border-radius: 17px;
font-size: 13px;
cursor: pointer;
font-family: inherit;
letter-spacing: 1px;
transition: background 0.2s;
transition: background 0.2s, border-color 0.2s, color 0.2s;
}
.filter-btn:hover {
background: var(--brand-primary-deep);
border-color: var(--brand-primary-deep);
}
.filter-btn-plain {
background: #fff;
color: var(--brand-primary);
}
.filter-btn-plain:hover {
background: #f7f8fa;
border-color: var(--brand-primary);
color: var(--brand-primary);
}
/* ========== 公示列表 ========== */
@@ -482,40 +529,14 @@ main.container {
padding: 0;
}
.notice-item {
.notice-empty {
display: flex;
align-items: flex-start;
padding: 14px 0;
border-bottom: 1px solid #f3f4f6;
cursor: pointer;
transition: background 0.2s;
gap: 24px;
}
.notice-item:hover {
background: #fafbfc;
}
.notice-item:hover .notice-title {
color: var(--brand-primary);
}
.notice-date {
flex-shrink: 0;
width: 100px;
font-size: 13px;
color: #6b7280;
font-family: ui-monospace, "Courier New", monospace;
padding-top: 2px;
}
.notice-title {
flex: 1;
flex-direction: column;
align-items: center;
gap: 12px;
padding: 64px 0;
color: #c0c6cf;
font-size: 14px;
color: #1f2937;
line-height: 1.7;
transition: color 0.2s;
min-width: 0;
}
/* 表头: 与 notice-item 相同的 flex + gap + 列宽, 保证列对齐 */
@@ -523,23 +544,12 @@ main.container {
display: flex;
align-items: center;
gap: 24px;
padding: 12px 0;
border-bottom: 1px solid #e5e7eb;
background: #f9fafb;
font-size: 13px;
padding: 14px 20px;
border-bottom: 1px solid #eef0f3;
font-size: 12px;
font-weight: 600;
color: #4b5563;
}
.notice-header-no {
flex-shrink: 0;
width: 32px;
text-align: center;
}
.notice-header-date {
flex-shrink: 0;
width: 100px;
color: #9ca3af;
letter-spacing: 1px;
}
.notice-header-title {
@@ -547,29 +557,88 @@ main.container {
min-width: 0;
}
.notice-header-end {
.notice-header-date {
flex-shrink: 0;
width: 100px;
width: 130px;
text-align: right;
}
/* 右侧截止时间: 等宽数字 + 灰色, 与日期列同宽对齐 */
.notice-end {
flex-shrink: 0;
width: 100px;
font-size: 13px;
color: #6b7280;
font-family: ui-monospace, "Courier New", monospace;
padding-top: 2px;
.notice-item {
position: relative;
display: flex;
align-items: center;
gap: 24px;
padding: 16px 20px;
border-bottom: 1px solid #f3f4f6;
cursor: pointer;
transition: background 0.2s;
}
.notice-item-no {
.notice-item::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 52%;
border-radius: 2px;
background: var(--brand-primary);
opacity: 0;
transition: opacity 0.2s ease;
}
.notice-item:hover {
background: #f7f9fb;
}
.notice-item:hover::before {
opacity: 1;
}
.notice-item:hover .notice-title {
color: var(--brand-primary);
}
.notice-title {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
gap: 8px;
font-size: 15px;
font-weight: 500;
color: #1f2937;
line-height: 1.6;
transition: color 0.2s;
}
.notice-arrow {
flex-shrink: 0;
width: 32px;
text-align: center;
color: var(--brand-primary);
opacity: 0;
transform: translateX(-6px);
transition: opacity 0.2s ease, transform 0.2s ease;
}
.notice-item:hover .notice-arrow {
opacity: 1;
transform: translateX(0);
}
.notice-date {
flex-shrink: 0;
width: 130px;
display: inline-flex;
align-items: center;
justify-content: flex-end;
gap: 6px;
font-size: 12px;
color: #9ca3af;
font-family: ui-monospace, "Courier New", monospace;
padding-top: 2px;
color: #8a919c;
}
.notice-date-icon {
color: #b6bcc6;
}
/* ========== 分页 ========== */