index.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. <template>
  2. <div class="app-container home">
  3. <!-- 太浦闸 -->
  4. <div class="section-header">
  5. <svg-icon icon-class="monitor" class="section-icon" />
  6. <span class="section-title">太浦闸</span>
  7. <span class="section-line"></span>
  8. <el-button link type="primary" icon="Plus" @click="openAdd('tpz')">添加模块</el-button>
  9. </div>
  10. <el-card class="home-card" shadow="never">
  11. <el-row :gutter="12">
  12. <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-for="(item, index) in tpzItems" :key="item.id">
  13. <div class="card-item" @click="goToPage('/more/tpz?folderId=' + item.id)">
  14. <span class="card-index">{{ index + 1 }}</span>
  15. <span class="card-text" :title="item.name">{{ item.name }}</span>
  16. </div>
  17. </el-col>
  18. <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-if="!loadingTpz && tpzItems.length === 0">
  19. <div class="empty-text">暂无数据</div>
  20. </el-col>
  21. </el-row>
  22. </el-card>
  23. <!-- 望亭闸 -->
  24. <div class="section-header">
  25. <svg-icon icon-class="example" class="section-icon" />
  26. <span class="section-title">望亭闸</span>
  27. <span class="section-line"></span>
  28. <el-button link type="primary" icon="Plus" @click="openAdd('wyh')">添加模块</el-button>
  29. </div>
  30. <el-card class="home-card" shadow="never">
  31. <el-row :gutter="12">
  32. <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-for="(item, index) in wyhItems" :key="item.id">
  33. <div class="card-item" @click="goToPage('/more/wyh?folderId=' + item.id)">
  34. <span class="card-index">{{ index + 1 }}</span>
  35. <span class="card-text" :title="item.name">{{ item.name }}</span>
  36. </div>
  37. </el-col>
  38. <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-if="!loadingWyh && wyhItems.length === 0">
  39. <div class="empty-text">暂无数据</div>
  40. </el-col>
  41. </el-row>
  42. </el-card>
  43. <!-- 直管工程 -->
  44. <div class="section-header">
  45. <svg-icon icon-class="build" class="section-icon" />
  46. <span class="section-title">直管工程</span>
  47. <span class="section-line"></span>
  48. <el-button link type="primary" icon="Plus" @click="openAdd('zggc')">添加项目</el-button>
  49. </div>
  50. <el-card class="home-card" shadow="never">
  51. <el-row :gutter="12">
  52. <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-for="(item, index) in zggcList" :key="item.id">
  53. <div class="project-item zggc-card" @click="goToPage('/zggc/detail/' + item.id)">
  54. <div class="zggc-bar" :style="{ background: 'linear-gradient(to right, rgba(15,105,220,0.7) ' + (item.planValue || 0) + '%, rgb(212,212,212) ' + (item.planValue || 0) + '%)' }"></div>
  55. <div class="zggc-text" :title="item.name">
  56. <svg-icon icon-class="build" class="project-icon" />
  57. <span class="zggc-name">{{ item.name }}</span>
  58. <span class="zggc-pct">{{ item.planValue || 0 }}%</span>
  59. </div>
  60. <span class="status-tag" :class="getZggcStatusClass(item.planValue)">{{ getZggcStatus(item.planValue) }}</span>
  61. </div>
  62. </el-col>
  63. <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-if="!loadingZggc && zggcList.length === 0">
  64. <div class="empty-text">暂无数据</div>
  65. </el-col>
  66. </el-row>
  67. </el-card>
  68. <!-- 重点工程 -->
  69. <div class="section-header">
  70. <svg-icon icon-class="star" class="section-icon" />
  71. <span class="section-title">重点工程</span>
  72. <span class="section-line"></span>
  73. <el-button link type="primary" icon="Plus" @click="openAdd('zdgc')">添加分类</el-button>
  74. </div>
  75. <el-card class="home-card" shadow="never">
  76. <el-row :gutter="12">
  77. <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-for="(item, index) in zdgcList" :key="item.id">
  78. <div class="project-item zdgc-card" @click="goToPage('/zdgc/index?folderId=' + item.id)">
  79. <span class="zdgc-name" :title="item.name">{{ item.name }}</span>
  80. </div>
  81. </el-col>
  82. <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-if="!loadingZdgc && zdgcList.length === 0">
  83. <div class="empty-text">暂无数据</div>
  84. </el-col>
  85. </el-row>
  86. </el-card>
  87. <!-- 督导检查 -->
  88. <div class="section-header">
  89. <svg-icon icon-class="search" class="section-icon" />
  90. <span class="section-title">督导检查</span>
  91. <span class="section-line"></span>
  92. <el-button link type="primary" icon="Plus" @click="openAdd('ddjc')">添加检查</el-button>
  93. </div>
  94. <el-card class="home-card" shadow="never">
  95. <el-row :gutter="12">
  96. <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-for="(item, index) in ddjcList" :key="item.id">
  97. <div class="card-item" @click="goToPage('/ddjc/index?folderId=' + item.id)">
  98. <span class="card-index">{{ index + 1 }}</span>
  99. <span class="card-text" :title="item.name">{{ item.name }}</span>
  100. </div>
  101. </el-col>
  102. <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-if="!loadingDdjc && ddjcList.length === 0">
  103. <div class="empty-text">暂无数据</div>
  104. </el-col>
  105. </el-row>
  106. </el-card>
  107. <!-- 规章制度 -->
  108. <div class="section-header">
  109. <svg-icon icon-class="documentation" class="section-icon" />
  110. <span class="section-title">规章制度</span>
  111. <span class="section-line"></span>
  112. <el-button link type="primary" icon="Plus" @click="openAdd('gzzd')">添加记录</el-button>
  113. </div>
  114. <el-card class="home-card" shadow="never">
  115. <el-row :gutter="12">
  116. <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-for="(item, index) in gzzdList" :key="item.id">
  117. <div class="card-item" @click="goToPage('/gzzd/index?folderId=' + item.id)">
  118. <svg-icon icon-class="documentation" class="card-item-icon" />
  119. <span class="card-text" :title="item.name">{{ item.name }}</span>
  120. </div>
  121. </el-col>
  122. <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-if="!loadingGzzd && gzzdList.length === 0">
  123. <div class="empty-text">暂无数据</div>
  124. </el-col>
  125. </el-row>
  126. </el-card>
  127. <!-- 通用添加对话框 -->
  128. <el-dialog :title="addTitle" v-model="addVisible" width="480px" append-to-body>
  129. <el-form ref="addFormRef" :model="addForm" :rules="addRules" label-width="90px">
  130. <el-form-item :label="nameLabel" prop="name">
  131. <el-input v-model="addForm.name" placeholder="必填项,请输入名称" maxlength="100" />
  132. </el-form-item>
  133. <el-form-item v-if="showPlanInput" label="设置进度">
  134. <el-input-number v-model="addForm.planValue" :min="0" :max="100" style="width: 150px" />
  135. <span style="margin-left: 8px;">%</span>
  136. </el-form-item>
  137. </el-form>
  138. <template #footer>
  139. <div class="dialog-footer">
  140. <el-button type="primary" :loading="addSubmitting" @click="submitAdd">立即提交</el-button>
  141. <el-button @click="resetAddForm">重置</el-button>
  142. </div>
  143. </template>
  144. </el-dialog>
  145. </div>
  146. </template>
  147. <script setup>
  148. import { ref, onMounted, onActivated } from 'vue'
  149. import { useRouter } from 'vue-router'
  150. import { folderList, mkdir } from '@/api/efinder/finder'
  151. import { addProject } from '@/api/pms/addProject'
  152. const router = useRouter()
  153. const { proxy } = getCurrentInstance()
  154. const tpzItems = ref([])
  155. const wyhItems = ref([])
  156. const zggcList = ref([])
  157. const zdgcList = ref([])
  158. const ddjcList = ref([])
  159. const gzzdList = ref([])
  160. const loadingTpz = ref(false)
  161. const loadingWyh = ref(false)
  162. const loadingZggc = ref(false)
  163. const loadingZdgc = ref(false)
  164. const loadingDdjc = ref(false)
  165. const loadingGzzd = ref(false)
  166. const goToPage = (path) => {
  167. router.push(path)
  168. }
  169. // 添加对话框
  170. const addVisible = ref(false)
  171. const addCate = ref('')
  172. const addSubmitting = ref(false)
  173. const addFormRef = ref(null)
  174. const addForm = reactive({ name: '', planValue: 0 })
  175. const addRules = { name: [{ required: true, message: '名称不能为空', trigger: 'blur' }] }
  176. const addTitleMap = { tpz: '添加模块', wyh: '添加模块', zggc: '添加项目', zdgc: '添加分类', ddjc: '添加检查', gzzd: '添加记录' }
  177. const addTitle = computed(() => addTitleMap[addCate.value] || '添加')
  178. // 标签文字对齐旧版:项目(直管)/ 分类(重点/督导/规章)/ 文件夹(太浦/望亭)
  179. const nameLabelMap = { tpz: '文件夹名称', wyh: '文件夹名称', zggc: '项目名称', zdgc: '分类名称', ddjc: '分类名称', gzzd: '分类名称' }
  180. const nameLabel = computed(() => nameLabelMap[addCate.value] || '项目名称')
  181. // 进度仅直管工程显示(旧版 zggcPrarms=1 才显示)
  182. const showPlanInput = computed(() => addCate.value === 'zggc')
  183. function openAdd(cate) {
  184. addCate.value = cate
  185. addSubmitting.value = false
  186. resetAddForm()
  187. addVisible.value = true
  188. nextTick(() => proxy.resetForm('addFormRef'))
  189. }
  190. function resetAddForm() {
  191. addForm.name = ''
  192. addForm.planValue = 0
  193. }
  194. function submitAdd() {
  195. if (addSubmitting.value) return
  196. addFormRef.value.validate(valid => {
  197. if (!valid) return
  198. addSubmitting.value = true
  199. // 直管工程:走 addProject 接口,自动创建子目录(工程概况/程序管理/... 对齐旧版)
  200. if (addCate.value === 'zggc') {
  201. addProject({
  202. folderId: 0,
  203. name: addForm.name,
  204. zggcPrarms: '1',
  205. planValues: String(addForm.planValue)
  206. }).then(res => {
  207. addSubmitting.value = false
  208. if (res.code === 200) {
  209. proxy.$modal.msgSuccess('添加成功')
  210. addVisible.value = false
  211. refreshByCate('zggc')
  212. } else {
  213. proxy.$modal.msgError(res.msg || '添加失败')
  214. }
  215. }).catch(() => {
  216. addSubmitting.value = false
  217. proxy.$modal.msgError('添加失败')
  218. })
  219. return
  220. }
  221. // 其余模块(重点工程分类/督导检查/规章制度/太浦闸/望亭闸):直接创建顶层文件夹
  222. const data = { name: addForm.name, fdCate: addCate.value, pid: 0 }
  223. mkdir(data).then(res => {
  224. addSubmitting.value = false
  225. if (res.code === 200) {
  226. proxy.$modal.msgSuccess('添加成功')
  227. addVisible.value = false
  228. refreshByCate(addCate.value)
  229. } else {
  230. proxy.$modal.msgError(res.msg || '添加失败')
  231. }
  232. }).catch(() => {
  233. addSubmitting.value = false
  234. proxy.$modal.msgError('添加失败')
  235. })
  236. })
  237. }
  238. function refreshByCate(cate) {
  239. const map = {
  240. tpz: [tpzItems, loadingTpz],
  241. wyh: [wyhItems, loadingWyh],
  242. zggc: [zggcList, loadingZggc],
  243. zdgc: [zdgcList, loadingZdgc],
  244. ddjc: [ddjcList, loadingDdjc],
  245. gzzd: [gzzdList, loadingGzzd]
  246. }
  247. const [listRef, loadingRef] = map[cate] || []
  248. // 首页统一只展示顶层(pid=0),子级目录(如阶段目录)不作为一级模块展示
  249. if (listRef) loadByCate(cate, listRef, loadingRef, isTopLevel)
  250. }
  251. // 直管工程:状态判断(对齐原版 tba-slgc)
  252. function getZggcStatus(val) {
  253. if (!val || val === 0) return '未开工'
  254. if (val >= 100) return '已完工'
  255. return '在建'
  256. }
  257. function getZggcStatusClass(val) {
  258. if (!val || val === 0) return 'status-gray'
  259. if (val >= 100) return 'status-blue'
  260. return 'status-green'
  261. }
  262. // 重点工程:首页展示顶层分类(一轮治太/二轮治太/太湖流域片重大水利工程)
  263. const isTopLevel = (item) => !item.pid || item.pid === 0
  264. const loadByCate = (cate, listRef, loadingRef, filterFn) => {
  265. loadingRef.value = true
  266. // 拉取该分类全量,前端用 isTopLevel 过滤顶层(兼容 FD_PID=0 与 FD_PID IS NULL 两种数据)
  267. folderList({ fdCate: cate, pageSize: 999 }).then(res => {
  268. const list = res.rows || res.data || []
  269. listRef.value = filterFn ? list.filter(item => filterFn(item, list)) : list
  270. loadingRef.value = false
  271. }).catch(() => {
  272. listRef.value = []
  273. loadingRef.value = false
  274. })
  275. }
  276. function loadAll() {
  277. // 首页所有模块统一只展示顶层(pid=0),子级目录不作为一级模块展示
  278. loadByCate('tpz', tpzItems, loadingTpz, isTopLevel)
  279. loadByCate('wyh', wyhItems, loadingWyh, isTopLevel)
  280. loadByCate('zggc', zggcList, loadingZggc, isTopLevel)
  281. loadByCate('zdgc', zdgcList, loadingZdgc, isTopLevel)
  282. loadByCate('ddjc', ddjcList, loadingDdjc, isTopLevel)
  283. loadByCate('gzzd', gzzdList, loadingGzzd, isTopLevel)
  284. }
  285. // 首次挂载:onMounted 必定触发加载(onActivated 只在 keep-alive 缓存组件时触发,首次可能不触发)
  286. // 缓存返回时:onActivated 触发刷新;mounted 标志防止首次挂载时两个钩子重复加载
  287. let dataLoaded = false
  288. onMounted(() => {
  289. dataLoaded = true
  290. loadAll()
  291. })
  292. onActivated(() => {
  293. if (dataLoaded) loadAll()
  294. })
  295. </script>
  296. <style scoped>
  297. .home {
  298. padding: 20px;
  299. }
  300. /* 分区标题:图标 + 标题 + 渐变分隔线 */
  301. .section-header {
  302. display: flex;
  303. align-items: center;
  304. gap: 8px;
  305. margin-bottom: 14px;
  306. }
  307. .section-icon {
  308. width: 20px;
  309. height: 20px;
  310. color: #409eff;
  311. flex-shrink: 0;
  312. }
  313. .section-title {
  314. font-size: 18px;
  315. font-weight: 600;
  316. color: #303133;
  317. white-space: nowrap;
  318. }
  319. .section-line {
  320. flex: 1;
  321. height: 1px;
  322. background: linear-gradient(to right, #e4e7ed, transparent);
  323. }
  324. .home-card {
  325. margin-bottom: 24px;
  326. border-radius: 10px;
  327. border: 1px solid #ebeef5;
  328. }
  329. .home-card :deep(.el-card__body) {
  330. padding: 16px 16px 12px;
  331. }
  332. /* 通用卡片:白底 + 左色条 + hover 上浮 */
  333. .card-item {
  334. height: 44px;
  335. line-height: 44px;
  336. display: flex;
  337. align-items: center;
  338. justify-content: center;
  339. gap: 6px;
  340. background: #fff;
  341. border: 1px solid #ebeef5;
  342. border-left: 3px solid #409eff;
  343. border-radius: 8px;
  344. cursor: pointer;
  345. transition: all 0.25s;
  346. font-size: 13px;
  347. color: #606266;
  348. white-space: nowrap;
  349. overflow: hidden;
  350. text-overflow: ellipsis;
  351. margin: 4px 0;
  352. padding: 0 8px;
  353. }
  354. .card-item:hover {
  355. transform: translateY(-2px);
  356. box-shadow: 0 4px 12px rgba(64, 158, 255, 0.15);
  357. border-color: #409eff;
  358. color: #409eff;
  359. }
  360. .card-index {
  361. font-size: 12px;
  362. color: #a8abb2;
  363. flex-shrink: 0;
  364. }
  365. .card-text {
  366. overflow: hidden;
  367. text-overflow: ellipsis;
  368. white-space: nowrap;
  369. }
  370. .card-item-icon {
  371. width: 14px;
  372. height: 14px;
  373. flex-shrink: 0;
  374. color: #409eff;
  375. }
  376. /* 项目卡片容器(直管工程与重点工程高度统一为 60px) */
  377. .project-item {
  378. height: 60px;
  379. background: #fff;
  380. border: 1px solid #e4e7ed;
  381. border-radius: 10px;
  382. cursor: pointer;
  383. transition: all 0.25s;
  384. position: relative;
  385. margin: 4px 0;
  386. overflow: hidden;
  387. }
  388. .project-item:hover {
  389. transform: translateY(-3px);
  390. box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
  391. }
  392. /* 直管工程进度条:蓝灰双色条铺满整个卡片 */
  393. .zggc-bar {
  394. position: absolute;
  395. top: 0;
  396. left: 0;
  397. width: 100%;
  398. height: 100%;
  399. }
  400. .zggc-text {
  401. position: absolute;
  402. left: 8%;
  403. top: 50%;
  404. transform: translateY(-50%);
  405. color: #434342;
  406. font-size: 13px;
  407. font-weight: 600;
  408. white-space: nowrap;
  409. overflow: hidden;
  410. text-overflow: ellipsis;
  411. max-width: 68%;
  412. display: flex;
  413. align-items: center;
  414. gap: 3px;
  415. z-index: 1;
  416. background: rgba(255, 255, 255, 0.75);
  417. border-radius: 12px;
  418. padding: 3px 10px;
  419. }
  420. .zggc-name {
  421. overflow: hidden;
  422. text-overflow: ellipsis;
  423. white-space: nowrap;
  424. }
  425. .zggc-pct {
  426. flex-shrink: 0;
  427. color: #fff;
  428. font-size: 12px;
  429. background: rgba(0, 0, 0, 0.25);
  430. border-radius: 8px;
  431. padding: 1px 6px;
  432. }
  433. .project-icon {
  434. width: 14px;
  435. height: 14px;
  436. flex-shrink: 0;
  437. color: #0f69dc;
  438. }
  439. /* 状态标签(右上角) */
  440. .status-tag {
  441. position: absolute;
  442. top: 8px;
  443. right: 8px;
  444. font-size: 11px;
  445. padding: 1px 8px;
  446. border-radius: 10px;
  447. color: #fff;
  448. z-index: 2;
  449. }
  450. .status-blue {
  451. background: rgba(15, 105, 220, 0.8);
  452. }
  453. .status-green {
  454. background: #6cdcf0;
  455. }
  456. .status-gray {
  457. background: #c0c4cc;
  458. }
  459. /* 重点工程:分类卡片(旧版逻辑:分类无 type 走 else 分支 → 灰色) */
  460. .zdgc-card {
  461. height: 60px;
  462. display: flex;
  463. align-items: center;
  464. padding: 0 10px;
  465. border: none;
  466. background: rgb(212, 212, 212);
  467. }
  468. .zdgc-name {
  469. color: #434342;
  470. font-size: 14px;
  471. font-weight: 600;
  472. margin-left: 8%;
  473. white-space: nowrap;
  474. overflow: hidden;
  475. text-overflow: ellipsis;
  476. width: 88%;
  477. }
  478. .empty-text {
  479. height: 44px;
  480. line-height: 44px;
  481. text-align: center;
  482. color: #909399;
  483. font-size: 14px;
  484. }
  485. </style>