index.vue 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. <template>
  2. <div class="gzfa-page">
  3. <!-- 左侧行政区划树 -->
  4. <div class="left-tree">
  5. <div class="tree-title">行政区划</div>
  6. <el-input v-model="treeFilter" placeholder="搜索" size="small" clearable style="margin:8px"/>
  7. <el-tree
  8. :data="treeData" :props="treeProps" node-key="adCode"
  9. :filter-node-method="filterNode" ref="treeRef"
  10. highlight-current lazy :load="loadTreeNode"
  11. @node-click="onNodeClick"
  12. />
  13. </div>
  14. <!-- 右侧内容 -->
  15. <div class="right-content" v-loading="loading">
  16. <!-- 当前选中区域 -->
  17. <div class="current-area" v-if="currentAdName">
  18. 当前区域:<strong>{{ currentAdName }}</strong>
  19. </div>
  20. <!-- 全国视图:中央/国务院/水利部文件 -->
  21. <template v-if="isNational">
  22. <div class="file-section" v-for="cat in fileCategories" :key="cat.title">
  23. <h3 class="section-title">{{ cat.title }}</h3>
  24. <el-table :data="cat.data" border size="small">
  25. <el-table-column label="文件名称" prop="fileName" show-overflow-tooltip>
  26. <template #default="{row}">
  27. <el-link type="primary" @click="showDetail(row)">{{ row.fileName || row.FILE_NAME }}</el-link>
  28. </template>
  29. </el-table-column>
  30. <el-table-column label="文号" prop="wh" width="200">
  31. <template #default="{row}">{{ row.wh || row.WH }}</template>
  32. </el-table-column>
  33. <el-table-column label="出台时间" width="140">
  34. <template #default="{row}">{{ fmtDate(row.startDate || row.START_DATE) }}</template>
  35. </el-table-column>
  36. </el-table>
  37. </div>
  38. </template>
  39. <!-- 地区视图:工作方案 + 工作制度 -->
  40. <template v-else>
  41. <div class="file-section">
  42. <h3 class="section-title">工作方案</h3>
  43. <el-table :data="workPlans" @row-click="showDetail">
  44. <el-table-column label="方案名称" prop="wpName" show-overflow-tooltip/>
  45. <el-table-column label="文号" prop="fileNum" width="200"/>
  46. <el-table-column label="发布日期" prop="rlsTm" width="140"/>
  47. <el-table-column label="发布单位" prop="releWiunName" width="180"/>
  48. </el-table>
  49. <el-empty v-if="!workPlans.length" description="暂无工作方案"/>
  50. </div>
  51. <div class="file-section" style="margin-top:20px">
  52. <h3 class="section-title">工作制度</h3>
  53. <el-table :data="ruleData">
  54. <el-table-column label="制度类别" prop="sysType" width="160">
  55. <template #default="scope">
  56. {{ sysTypeFilter(scope.row) }}
  57. </template>
  58. </el-table-column>
  59. <el-table-column label="制度名称" prop="sysName" show-overflow-tooltip/>
  60. <el-table-column label="状态" prop="ruleProgress" width="100">
  61. <template #default="scope">
  62. {{ ruleProgressFilter(scope.row) }}
  63. </template>
  64. </el-table-column>
  65. <el-table-column label="文号" prop="fileNum" width="200"/>
  66. <el-table-column label="出台时间" prop="rlsTm" width="140"/>
  67. </el-table>
  68. <el-empty v-if="!ruleData.length" description="暂无工作制度"/>
  69. </div>
  70. </template>
  71. </div>
  72. <!-- 文件详情弹窗 -->
  73. <el-dialog v-model="detailVisible" :title="detailItem?.fileName || detailItem?.wpName || detailItem?.sysName" width="700px">
  74. <iframe-view :url="detailItem?.url" style="height: 79vh;"></iframe-view>
  75. </el-dialog>
  76. </div>
  77. </template>
  78. <script setup>
  79. import {computed, onMounted, reactive, ref, watch} from 'vue'
  80. import {listFile, listRule} from '@/api/hzz/rule'
  81. import {getChildren} from '@/api/hzz/adtree'
  82. import {listWorkPlan} from '@/api/hzz/workplan'
  83. import IframeView from "@/views/hlgl/IframeView.vue";
  84. const loading = ref(false)
  85. const treeRef = ref(null)
  86. const treeFilter = ref('')
  87. const treeData = ref([])
  88. const treeProps = {label: 'adName', children: 'children'}
  89. const currentAdCode = ref('000000000000')
  90. const currentAdName = ref('太湖流域')
  91. const isNational = computed(() => currentAdCode.value === '000000000000')
  92. const fileCategories = reactive([
  93. {title: '中央文件', key: '0', data: []},
  94. {title: '国务院文件', key: '1', data: []},
  95. {title: '水利部文件', key: '2', data: []},
  96. ])
  97. const workPlans = ref([])
  98. const ruleData = ref([])
  99. const detailVisible = ref(false)
  100. const detailItem = ref(null)
  101. const fmtDate = (d) => d ? String(d).substring(0, 10) : '-'
  102. // 加载文件列表
  103. const loadFiles = async () => {
  104. try {
  105. const res = await listFile({types: 'gzzd', pageNum: 1, pageSize: 500})
  106. const rows = res.rows || []
  107. fileCategories[0].data = rows.filter(r => (r.FILE_TYPE || r.fileType) === '0')
  108. fileCategories[1].data = rows.filter(r => (r.FILE_TYPE || r.fileType) === '1')
  109. fileCategories[2].data = rows.filter(r => (r.FILE_TYPE || r.fileType) === '2')
  110. } catch { /* ok */
  111. }
  112. }
  113. // 加载工作方案 + 制度
  114. const loadAreaData = async () => {
  115. workPlans.value = []
  116. ruleData.value = []
  117. if (isNational.value) return
  118. try {
  119. const [wpRes, ruleRes] = await Promise.all([
  120. listWorkPlan(currentAdCode.value),
  121. listRule(currentAdCode.value, '')
  122. ])
  123. workPlans.value = wpRes.data || []
  124. ruleData.value = ruleRes.data || []
  125. } catch { /* ok */
  126. }
  127. }
  128. // 加载行政区划树
  129. const loadTree = async () => {
  130. // 根节点从数据库加载
  131. const res = await getChildren('000000000000')
  132. treeData.value = (res.data || []).map(item => ({
  133. ...item,
  134. adName: item.adName, adCode: item.adCode
  135. }))
  136. }
  137. // 懒加载子节点(逐级下钻)
  138. const loadTreeNode = async (node, resolve) => {
  139. if (node.level === 0) {
  140. // 根节点已在 loadTree 中加载
  141. return resolve(treeData.value)
  142. }
  143. try {
  144. const res = await getChildren(node.data.adCode)
  145. const children = (res.data || []).map(item => ({
  146. ...item,
  147. adName: item.adName,
  148. adCode: item.adCode,
  149. leaf: item.adGrad >= '5' // 村级为叶子节点
  150. }))
  151. resolve(children)
  152. } catch {
  153. resolve([])
  154. }
  155. }
  156. const onNodeClick = (node) => {
  157. currentAdCode.value = node.adCode
  158. currentAdName.value = node.adName
  159. if (!isNational.value) loadAreaData()
  160. }
  161. const sysTypeFilter = (item) => {
  162. switch (item.sysType) {
  163. case '1':
  164. return '河长会议制度'
  165. case '2':
  166. return '信息报送制度'
  167. case '3':
  168. return '工作督察制度'
  169. case '4':
  170. return '考核问责与激励制度'
  171. case '5':
  172. return '验收制度'
  173. case '6':
  174. return '信息共享制度'
  175. default:
  176. return '其他'
  177. }
  178. }
  179. const ruleProgressFilter = (item) => {
  180. if (item.ruleProgress === '1') {
  181. return '研讨'
  182. } else if (item.ruleProgress === '2') {
  183. return '起草'
  184. } else if (item.ruleProgress === '3') {
  185. return '征求意见'
  186. } else if (item.ruleProgress === '4') {
  187. return '报批'
  188. } else if (item.ruleProgress === '5') {
  189. return '印发'
  190. }
  191. }
  192. const showDetail = (item) => {
  193. detailVisible.value = true
  194. detailItem.value = item
  195. // if (item.url.substring(item.url.length - 3) === 'doc' || item.url.substring(item.url.length - 4) === 'docx') {
  196. // detailVisible.value = false;
  197. // window.open(item.url);
  198. // } else if (item.url.substring(item.url.length - 3) === 'pdf') {
  199. // detailVisible.value = true
  200. // detailItem.value = item
  201. // }
  202. }
  203. const filterNode = (value, data) => {
  204. if (!value) return true
  205. return data.adName?.includes(value)
  206. }
  207. watch(treeFilter, (val) => {
  208. treeRef.value?.filter(val)
  209. })
  210. onMounted(async () => {
  211. loading.value = true
  212. await Promise.all([loadFiles(), loadTree()])
  213. loading.value = false
  214. })
  215. </script>
  216. <style scoped>
  217. .gzfa-page {
  218. display: flex;
  219. height: calc(100vh - 60px);
  220. }
  221. .left-tree {
  222. width: 260px;
  223. flex-shrink: 0;
  224. border-right: 1px solid #e8e8e8;
  225. overflow-y: auto;
  226. background: #fafbfc;
  227. }
  228. .tree-title {
  229. padding: 12px 16px;
  230. font-weight: 600;
  231. font-size: 15px;
  232. color: #333;
  233. border-bottom: 1px solid #eee;
  234. }
  235. .right-content {
  236. flex: 1;
  237. overflow-y: auto;
  238. padding: 20px;
  239. }
  240. .file-section {
  241. margin-bottom: 16px;
  242. }
  243. .section-title {
  244. font-size: 16px;
  245. color: #0d4b80;
  246. padding: 8px 0;
  247. margin: 0 0 8px 0;
  248. border-bottom: 2px solid #6baed6;
  249. }
  250. .current-area {
  251. margin-bottom: 20px;
  252. padding: 10px;
  253. background: #f0f7ff;
  254. border-radius: 6px;
  255. font-size: 13px;
  256. color: #555;
  257. }
  258. .detail-info p {
  259. margin: 8px 0;
  260. font-size: 14px;
  261. }
  262. </style>