Files
X-Financial/src/views/PoliciesView.vue
WIN-JHFT4D3SIVT\caoxiaozhu 64537119e0 fix: adjust knowledge operations overview layout and add rank badges
- Change ops-grid from 3 columns to 2 for better metric card sizing
- Add rank badges (hot/warm/normal) to liked and disliked answer lists
- Unify row heights (min-height: 32px) across hot-list and feedback-list
2026-04-30 22:11:57 +08:00

917 lines
23 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<section class="knowledge-page">
<div class="knowledge-grid">
<section class="knowledge-main">
<div class="knowledge-metrics panel">
<article v-for="item in metrics" :key="item.label" class="metric-card" :style="{ '--accent': item.accent }">
<span class="metric-icon"><i :class="item.icon"></i></span>
<div>
<p>{{ item.label }} <i v-if="item.help" class="pi pi-question-circle"></i></p>
<strong>{{ item.value }}</strong>
<small>{{ item.meta }}</small>
</div>
</article>
</div>
<article class="library-panel panel">
<header class="panel-title">
<h2>文档库 / 文件夹</h2>
</header>
<div class="library-body">
<aside class="folder-rail">
<label class="folder-search">
<i class="pi pi-search"></i>
<input type="search" placeholder="搜索文件夹" />
<button type="button" aria-label="新增文件夹"><i class="pi pi-plus"></i></button>
</label>
<nav class="folder-tree" aria-label="知识库文件夹">
<button v-for="folder in folders" :key="folder.name" type="button" :class="{ active: folder.active }">
<i :class="folder.icon"></i>
<span>{{ folder.name }}</span>
<b>{{ folder.count }}</b>
</button>
</nav>
<button class="new-folder-btn" type="button">
<i class="pi pi-plus"></i>
<span>新建文件夹</span>
</button>
</aside>
<section class="document-area">
<div class="upload-zone">
<i class="pi pi-cloud-upload"></i>
<strong>拖拽文档到此处或点击上传</strong>
<span>支持 PDF / Word / Excel / PPT 文档单个文件不超过 100MB</span>
</div>
<div class="doc-table-wrap">
<table>
<thead>
<tr>
<th>文件名称</th>
<th>标签</th>
<th>上传时间 <i class="pi pi-arrow-down"></i></th>
<th>版本</th>
<th>状态</th>
<th>上传人</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="doc in documents" :key="doc.name">
<td>
<span class="file-name">
<i :class="doc.icon"></i>
{{ doc.name }}
</span>
</td>
<td>
<span class="doc-tag">{{ doc.tag }}</span>
</td>
<td>{{ doc.time }}</td>
<td>{{ doc.version }}</td>
<td><span class="state-tag" :class="doc.stateTone">{{ doc.state }}</span></td>
<td>{{ doc.owner }}</td>
<td><button class="more-btn" type="button" aria-label="更多操作"><i class="pi pi-ellipsis-h"></i></button></td>
</tr>
</tbody>
</table>
</div>
<footer class="list-foot">
<span> 18 </span>
<button type="button">10/ <i class="pi pi-angle-down"></i></button>
<div class="pager" aria-label="分页">
<button type="button" aria-label="上一页"><i class="pi pi-angle-left"></i></button>
<button class="active" type="button" aria-current="page">1</button>
<button type="button">2</button>
<button type="button" aria-label="下一页"><i class="pi pi-angle-right"></i></button>
</div>
<label>前往 <input value="1" aria-label="页码" /> </label>
</footer>
</section>
</div>
</article>
</section>
<aside class="analytics-column">
<article class="ops-card panel">
<header class="card-head">
<h2>知识运营概览 <i class="pi pi-question-circle"></i></h2>
</header>
<div class="ops-grid">
<div v-for="item in opsMetrics" :key="item.label" class="ops-item" :style="{ '--accent': item.accent }">
<span><i :class="item.icon"></i></span>
<div>
<strong>{{ item.value }}</strong>
<p>{{ item.label }}</p>
<small>{{ item.meta }}</small>
</div>
</div>
</div>
</article>
<article class="top-card panel">
<header class="card-head">
<h2>热门问题 TOP5 <i class="pi pi-question-circle"></i></h2>
<button type="button">更多 <i class="pi pi-angle-right"></i></button>
</header>
<ol class="hot-list">
<li v-for="item in hotQuestions" :key="item.title">
<span :class="item.tone">{{ item.rank }}</span>
<strong>{{ item.title }}</strong>
<b>{{ item.count }}</b>
</li>
</ol>
</article>
<article class="feedback-card panel">
<header class="card-head">
<h2>用户点赞最多 <i class="pi pi-question-circle"></i></h2>
<button type="button">更多 <i class="pi pi-angle-right"></i></button>
</header>
<ul class="feedback-list positive">
<li v-for="(item, idx) in likedAnswers" :key="item.title">
<span class="rank-badge" :class="idx === 0 ? 'hot' : idx < 3 ? 'warm' : 'normal'">{{ idx + 1 }}</span>
<span>{{ item.title }}</span>
<b><i class="pi pi-thumbs-up-fill"></i>{{ item.count }}</b>
</li>
</ul>
</article>
<article class="feedback-card panel">
<header class="card-head">
<h2>用户点踩较多 / 待优化 <i class="pi pi-question-circle"></i></h2>
<button type="button">更多 <i class="pi pi-angle-right"></i></button>
</header>
<ul class="feedback-list negative">
<li v-for="(item, idx) in dislikedAnswers" :key="item.title">
<span class="rank-badge" :class="idx === 0 ? 'hot' : idx < 3 ? 'warm' : 'normal'">{{ idx + 1 }}</span>
<span>{{ item.title }}</span>
<b><i class="pi pi-thumbs-down-fill"></i>{{ item.count }}</b>
<em>待优化</em>
</li>
</ul>
</article>
<article class="trend-card panel">
<header class="card-head">
<h2>近7天提问趋势 <i class="pi pi-question-circle"></i></h2>
<button type="button">更多 <i class="pi pi-angle-right"></i></button>
</header>
<svg class="trend-chart" viewBox="0 0 420 190" role="img" aria-label="近7天提问趋势">
<line v-for="tick in chartTicks" :key="tick" x1="38" :y1="tick.y" x2="398" :y2="tick.y" />
<text v-for="tick in chartTicks" :key="tick.label" x="8" :y="tick.y + 4">{{ tick.label }}</text>
<polyline :points="trendPoints" />
<circle v-for="point in trendData" :key="point.label" :cx="point.x" :cy="point.y" r="4" />
<text v-for="point in trendData" :key="point.label + '-label'" :x="point.x - 14" y="180">{{ point.label }}</text>
</svg>
</article>
<article class="recent-card panel">
<header class="card-head">
<h2>最近更新知识 <i class="pi pi-question-circle"></i></h2>
<button type="button">更多 <i class="pi pi-angle-right"></i></button>
</header>
<ul class="recent-list">
<li v-for="item in recentKnowledge" :key="item.name">
<i :class="item.icon"></i>
<strong>{{ item.name }}</strong>
<time>{{ item.time }}</time>
</li>
</ul>
</article>
</aside>
</div>
</section>
</template>
<script setup>
import { computed } from 'vue'
const metrics = [
{ label: '文档总数', value: '1,248', meta: '较上周 +68', icon: 'pi pi-file', accent: '#10b981', help: true },
{ label: '文件夹总数', value: '36', meta: '较上周 +2', icon: 'pi pi-folder', accent: '#3b82f6' },
{ label: '问答总量', value: '8,562', meta: '较上周 +321', icon: 'pi pi-comments', accent: '#8b5cf6' },
{ label: '知识命中率', value: '87.3%', meta: '较上周 +1.2%', icon: 'pi pi-bullseye', accent: '#f59e0b' }
]
const folders = [
{ name: '财务知识库', count: 36, icon: 'pi pi-folder', active: false },
{ name: '制度政策', count: 8, icon: 'pi pi-folder', active: false },
{ name: '报销制度', count: 12, icon: 'pi pi-folder-open', active: false },
{ name: '差旅规范', count: 18, icon: 'pi pi-folder', active: true },
{ name: '发票管理', count: 14, icon: 'pi pi-folder', active: false },
{ name: '税务合规', count: 16, icon: 'pi pi-folder', active: false },
{ name: '预算管理', count: 9, icon: 'pi pi-folder', active: false },
{ name: '财务共享', count: 7, icon: 'pi pi-folder', active: false },
{ name: '培训资料', count: 6, icon: 'pi pi-folder', active: false },
{ name: '常见问答', count: 11, icon: 'pi pi-folder', active: false }
]
const documents = [
{ name: '差旅报销管理办法2024版', tag: '差旅 / 制度', time: '2024-05-12 14:35', version: 'v3.2', state: '已生效', stateTone: 'success', owner: '张明', icon: 'pi pi-file-pdf pdf' },
{ name: '发票查验规范及操作指引', tag: '发票 / 操作', time: '2024-05-10 10:22', version: 'v1.5', state: '已生效', stateTone: 'success', owner: '李娜', icon: 'pi pi-file-word word' },
{ name: '费用报销标准细则2024', tag: '报销 / 标准', time: '2024-05-08 09:16', version: 'v2.1', state: '已生效', stateTone: 'success', owner: '王磊', icon: 'pi pi-file-pdf pdf' },
{ name: '差旅费用标准对照表(国内)', tag: '差旅 / 标准', time: '2024-05-05 08:20', version: 'v1.3', state: '审批中', stateTone: 'warning', owner: '陈杰', icon: 'pi pi-file-excel excel' },
{ name: '借款管理办法及流程', tag: '借款 / 流程', time: '2024-05-03 11:05', version: 'v1.0', state: '已生效', stateTone: 'success', owner: '刘洋', icon: 'pi pi-file-pdf pdf' }
]
const opsMetrics = [
{ label: '本周新增问题', value: '328', meta: '较上周 +46', icon: 'pi pi-question-circle', accent: '#3b82f6' },
{ label: '已解决问题', value: '286', meta: '解决率 87.2%', icon: 'pi pi-check', accent: '#10b981' },
{ label: '平均响应时长', value: '2.4h', meta: '较上周 -0.6h', icon: 'pi pi-clock', accent: '#3b82f6' },
{ label: '用户满意度', value: '91.2%', meta: '较上周 +2.1%', icon: 'pi pi-face-smile', accent: '#10b981' },
{ label: '收藏次数', value: '1,236', meta: '较上周 +128', icon: 'pi pi-star', accent: '#f59e0b' },
{ label: '点赞总数', value: '3,582', meta: '较上周 +312', icon: 'pi pi-thumbs-up', accent: '#f59e0b' }
]
const hotQuestions = [
{ rank: 1, title: '酒店住宿超标怎么办?', count: '1,256', tone: 'hot' },
{ rank: 2, title: '发票抬头不一致如何处理?', count: '1,023', tone: 'warm' },
{ rank: 3, title: '差旅补贴标准是多少?', count: '856', tone: 'warm' },
{ rank: 4, title: '借款申请后如何冲销?', count: '622', tone: 'normal' },
{ rank: 5, title: '预算占用失败怎么处理?', count: '485', tone: 'normal' }
]
const likedAnswers = [
{ title: '差旅报销标准及注意事项汇总', count: 326 },
{ title: '发票查验失败常见原因及处理', count: 285 },
{ title: '报销单据填写规范与示例', count: 241 },
{ title: '借款冲销操作流程详解', count: 198 },
{ title: '预算占用与释放操作指南', count: 176 }
]
const dislikedAnswers = [
{ title: '补贴发放延迟如何处理?', count: 68 },
{ title: '发票跨月入账如何处理?', count: 57 },
{ title: '预填预算申请流程复杂?', count: 43 },
{ title: '费用分类规则不清晰?', count: 38 },
{ title: '固定资产报废流程说明', count: 31 }
]
const trendData = [
{ label: '05-06', value: 220 },
{ label: '05-07', value: 270 },
{ label: '05-08', value: 200 },
{ label: '05-09', value: 250 },
{ label: '05-10', value: 150 },
{ label: '05-11', value: 210 }
].map((item, index) => ({
...item,
x: 48 + index * 66,
y: 164 - (item.value / 320) * 128
}))
const chartTicks = [
{ label: '320', y: 36 },
{ label: '240', y: 68 },
{ label: '160', y: 100 },
{ label: '80', y: 132 },
{ label: '0', y: 164 }
]
const trendPoints = computed(() => trendData.map((point) => `${point.x},${point.y}`).join(' '))
const recentKnowledge = [
{ name: '差旅报销管理办法2024版v3.2', time: '05-12 14:35', icon: 'pi pi-file-pdf pdf' },
{ name: '发票查验规范及操作指引 v1.5', time: '05-10 10:22', icon: 'pi pi-file-word word' },
{ name: '费用报销标准细则2024v2.1', time: '05-08 09:16', icon: 'pi pi-file-pdf pdf' }
]
</script>
<style scoped>
.knowledge-page {
height: 100%;
min-height: 0;
overflow: hidden;
animation: fadeUp 220ms var(--ease) both;
}
.knowledge-grid {
height: 100%;
min-height: 0;
display: grid;
grid-template-columns: minmax(620px, 1.08fr) minmax(560px, .92fr);
gap: 16px;
}
.knowledge-main,
.analytics-column {
min-width: 0;
min-height: 0;
display: grid;
gap: 16px;
}
.knowledge-main {
grid-template-rows: auto minmax(0, 1fr);
}
.analytics-column {
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-auto-rows: minmax(0, auto);
overflow-y: auto;
}
.knowledge-metrics {
min-height: 96px;
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
padding: 0;
overflow: hidden;
}
.metric-card {
min-height: 96px;
display: grid;
grid-template-columns: 54px minmax(0, 1fr);
align-items: center;
gap: 14px;
padding: 16px 20px;
border-right: 1px solid #edf2f7;
}
.metric-card:last-child {
border-right: 0;
}
.metric-icon {
width: 48px;
height: 48px;
display: grid;
place-items: center;
border-radius: 999px;
background: color-mix(in srgb, var(--accent) 14%, white);
color: var(--accent);
font-size: 22px;
}
.metric-card p {
color: #334155;
font-size: 14px;
font-weight: 650;
}
.metric-card p i,
.card-head h2 i {
color: #94a3b8;
font-size: 12px;
}
.metric-card strong {
display: block;
margin-top: 5px;
color: #0f172a;
font-size: 24px;
font-weight: 850;
line-height: 1;
}
.metric-card small {
display: block;
margin-top: 7px;
color: #64748b;
font-size: 13px;
}
.library-panel {
min-height: 0;
display: grid;
grid-template-rows: auto minmax(0, 1fr);
padding: 16px 18px;
overflow: hidden;
}
.panel-title h2,
.card-head h2 {
color: #0f172a;
font-size: 19px;
font-weight: 850;
}
.library-body {
min-height: 0;
display: grid;
grid-template-columns: 180px minmax(0, 1fr);
gap: 14px;
margin-top: 16px;
}
.folder-rail {
min-height: 0;
display: grid;
grid-template-rows: auto minmax(0, 1fr) auto;
gap: 12px;
border-right: 1px solid #edf2f7;
padding-right: 12px;
}
.folder-search {
height: 36px;
display: grid;
grid-template-columns: 18px minmax(0, 1fr) 24px;
align-items: center;
gap: 6px;
padding: 0 8px;
border: 1px solid #d7e0ea;
border-radius: 8px;
color: #64748b;
}
.folder-search input {
min-width: 0;
border: 0;
color: #0f172a;
font-size: 13px;
}
.folder-search input:focus {
outline: none;
}
.folder-search button {
border: 0;
background: transparent;
color: #64748b;
}
.folder-tree {
min-height: 0;
display: grid;
align-content: start;
gap: 6px;
overflow-y: auto;
}
.folder-tree button {
min-height: 34px;
display: grid;
grid-template-columns: 18px minmax(0, 1fr) auto;
align-items: center;
gap: 8px;
padding: 0 9px;
border: 0;
border-radius: 7px;
background: transparent;
color: #334155;
font-size: 13px;
text-align: left;
}
.folder-tree button.active {
background: #dcfce7;
color: #059669;
font-weight: 850;
}
.folder-tree b {
min-width: 24px;
height: 20px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 999px;
background: #f1f5f9;
color: #64748b;
font-size: 11px;
}
.new-folder-btn {
min-height: 36px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
border: 1px solid rgba(16, 185, 129, .28);
border-radius: 8px;
background: #f0fdf4;
color: #059669;
font-size: 13px;
font-weight: 850;
}
.new-folder-btn:hover {
background: #dcfce7;
}
.document-area {
min-width: 0;
min-height: 0;
display: grid;
grid-template-rows: auto minmax(0, 1fr) auto;
gap: 12px;
}
.upload-zone {
min-height: 112px;
display: grid;
place-items: center;
align-content: center;
gap: 8px;
border: 1px dashed #93c5fd;
border-radius: 10px;
background: #f8fbff;
color: #334155;
text-align: center;
}
.upload-zone i {
color: #2563eb;
font-size: 31px;
}
.upload-zone strong {
font-size: 13px;
font-weight: 850;
}
.upload-zone span {
color: #64748b;
font-size: 12px;
}
.doc-table-wrap {
min-height: 0;
overflow: auto;
}
table {
width: 100%;
min-width: 690px;
border-collapse: collapse;
}
th,
td {
padding: 12px 10px;
border-bottom: 1px solid #edf2f7;
color: #24324a;
font-size: 12px;
line-height: 1.35;
text-align: left;
vertical-align: middle;
}
th {
background: #f7fafc;
color: #64748b;
font-weight: 800;
}
.file-name {
display: inline-flex;
align-items: center;
gap: 7px;
font-weight: 750;
white-space: nowrap;
}
.file-name .pdf,
.recent-list .pdf { color: #ef4444; }
.file-name .word,
.recent-list .word { color: #2563eb; }
.file-name .excel { color: #10b981; }
.doc-tag {
display: inline-flex;
align-items: center;
min-height: 22px;
padding: 0 7px;
border-radius: 6px;
background: #f1f5f9;
color: #64748b;
font-size: 11px;
font-weight: 750;
}
.state-tag {
min-height: 22px;
display: inline-flex;
align-items: center;
padding: 0 8px;
border-radius: 6px;
font-size: 11px;
font-weight: 800;
white-space: nowrap;
}
.state-tag.success {
background: #dcfce7;
color: #059669;
}
.state-tag.warning {
background: #ffedd5;
color: #f97316;
}
.more-btn {
border: 0;
background: transparent;
color: #2563eb;
}
.list-foot {
display: grid;
grid-template-columns: auto auto 1fr auto;
align-items: center;
gap: 10px;
color: #64748b;
font-size: 13px;
}
.list-foot button {
min-height: 32px;
border: 1px solid #d7e0ea;
border-radius: 8px;
background: #fff;
color: #334155;
font-weight: 750;
}
.pager {
display: inline-flex;
gap: 6px;
}
.pager button {
width: 32px;
padding: 0;
}
.pager button.active {
border-color: #059669;
background: #059669;
color: #fff;
}
.list-foot input {
width: 42px;
height: 30px;
border: 1px solid #d7e0ea;
border-radius: 7px;
text-align: center;
}
.panel {
min-width: 0;
}
.card-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.card-head button {
border: 0;
background: transparent;
color: #64748b;
font-size: 12px;
font-weight: 850;
}
.ops-card {
grid-column: span 1;
padding: 16px 18px;
}
.ops-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px 12px;
margin-top: 18px;
}
.ops-item {
min-width: 0;
display: grid;
grid-template-columns: 28px minmax(0, 1fr);
gap: 9px;
align-items: start;
}
.ops-item > span {
width: 26px;
height: 26px;
display: grid;
place-items: center;
border-radius: 999px;
background: color-mix(in srgb, var(--accent) 12%, white);
color: var(--accent);
font-size: 16px;
}
.ops-item strong {
color: #0f172a;
font-size: 18px;
font-weight: 900;
}
.ops-item p {
margin-top: 3px;
color: #334155;
font-size: 12px;
font-weight: 750;
}
.ops-item small {
display: block;
margin-top: 8px;
color: #64748b;
font-size: 11px;
}
.top-card,
.feedback-card,
.trend-card,
.recent-card {
padding: 16px 18px;
}
.hot-list,
.feedback-list,
.recent-list {
display: grid;
gap: 13px;
margin: 16px 0 0;
padding: 0;
list-style: none;
}
.hot-list li {
min-height: 32px;
display: grid;
grid-template-columns: 24px minmax(0, 1fr) auto;
align-items: center;
gap: 10px;
}
.hot-list span {
width: 20px;
height: 20px;
display: grid;
place-items: center;
border-radius: 5px;
color: #fff;
font-size: 12px;
font-weight: 900;
}
.hot-list .hot { background: #ef4444; }
.hot-list .warm { background: #f59e0b; }
.hot-list .normal {
background: #f1f5f9;
color: #64748b;
}
.hot-list strong,
.feedback-list span {
min-width: 0;
color: #334155;
font-size: 13px;
font-weight: 800;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.hot-list b {
color: #334155;
font-size: 13px;
font-weight: 850;
}
.feedback-list li {
min-height: 32px;
display: grid;
grid-template-columns: 24px minmax(0, 1fr) auto;
align-items: center;
gap: 10px;
}
.feedback-list.negative li {
grid-template-columns: 24px minmax(0, 1fr) auto auto;
}
.feedback-list b {
display: inline-flex;
align-items: center;
gap: 6px;
color: #059669;
font-size: 13px;
font-weight: 900;
}
.feedback-list .rank-badge {
width: 20px;
height: 20px;
display: grid;
place-items: center;
border-radius: 5px;
color: #fff;
font-size: 12px;
font-weight: 900;
}
.feedback-list .rank-badge.hot { background: #ef4444; }
.feedback-list .rank-badge.warm { background: #f59e0b; }
.feedback-list .rank-badge.normal { background: #f1f5f9; color: #64748b; }
.feedback-list.negative b {
color: #ef4444;
}
.feedback-list em {
padding: 4px 8px;
border-radius: 7px;
background: #fee2e2;
color: #ef4444;
font-size: 12px;
font-style: normal;
font-weight: 850;
}
.trend-chart {
width: 100%;
height: 220px;
margin-top: 12px;
}
.trend-chart line {
stroke: #edf2f7;
}
.trend-chart text {
fill: #64748b;
font-size: 11px;
}
.trend-chart polyline {
fill: none;
stroke: #10b981;
stroke-width: 3;
}
.trend-chart circle {
fill: #fff;
stroke: #10b981;
stroke-width: 3;
}
.recent-list li {
display: grid;
grid-template-columns: 18px minmax(0, 1fr) auto;
align-items: center;
gap: 8px;
}
.recent-list strong {
color: #334155;
font-size: 12px;
font-weight: 850;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.recent-list time {
color: #64748b;
font-size: 12px;
font-weight: 750;
}
@media (max-width: 1380px) {
.knowledge-grid {
grid-template-columns: 1fr;
overflow-y: auto;
}
}
@media (max-width: 980px) {
.knowledge-metrics,
.analytics-column {
grid-template-columns: 1fr;
}
.library-body {
grid-template-columns: 1fr;
}
.folder-rail {
border-right: 0;
border-bottom: 1px solid #edf2f7;
padding: 0 0 12px;
}
.ops-grid {
grid-template-columns: 1fr;
}
}
</style>