77681 1 dienu atpakaļ
vecāks
revīzija
29d75f8e5f

+ 2 - 0
gw-slgc/src/main/java/com/goldenwater/web/controller/pms/PmsAddProjectController.java

@@ -81,6 +81,8 @@ public class PmsAddProjectController extends BaseController {
                     subFolder.setPid(rootFolder.getId());
                     subFolder.setName(subName);
                     subFolder.setDate(new Date());
+                    // 子目录继承根目录分类,保证详情页文件夹树能按 fdCate 查询到
+                    subFolder.setFdCate(rootFolder.getFdCate());
                     docFolderService.insert(subFolder);
                 }
             }

+ 405 - 157
gw-ui/src/views/index.vue

@@ -1,139 +1,164 @@
 <template>
   <div class="app-container home">
     <!-- 太浦闸 -->
-    <el-divider content-position="left">
+    <div class="section-header">
+      <svg-icon icon-class="monitor" class="section-icon" />
       <span class="section-title">太浦闸</span>
-    </el-divider>
-    <el-row :gutter="20">
-      <el-col :span="24">
-        <el-card class="home-card">
-          <el-row :gutter="10">
-            <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-for="(item, index) in tpzItems" :key="item.id">
-              <div class="card-item" @click="goToPage('/more/tpz?folderId=' + item.id)">
-                {{ index + 1 }}){{ item.name }}
-              </div>
-            </el-col>
-            <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-if="loadingTpz && tpzItems.length === 0">
-              <div class="empty-text">暂无数据</div>
-            </el-col>
-          </el-row>
-        </el-card>
-      </el-col>
-    </el-row>
+      <span class="section-line"></span>
+      <el-button link type="primary" icon="Plus" @click="openAdd('tpz')">添加模块</el-button>
+    </div>
+    <el-card class="home-card" shadow="never">
+      <el-row :gutter="12">
+        <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-for="(item, index) in tpzItems" :key="item.id">
+          <div class="card-item" @click="goToPage('/more/tpz?folderId=' + item.id)">
+            <span class="card-index">{{ index + 1 }}</span>
+            <span class="card-text" :title="item.name">{{ item.name }}</span>
+          </div>
+        </el-col>
+        <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-if="!loadingTpz && tpzItems.length === 0">
+          <div class="empty-text">暂无数据</div>
+        </el-col>
+      </el-row>
+    </el-card>
 
     <!-- 望亭闸 -->
-    <el-divider content-position="left">
+    <div class="section-header">
+      <svg-icon icon-class="example" class="section-icon" />
       <span class="section-title">望亭闸</span>
-    </el-divider>
-    <el-row :gutter="20">
-      <el-col :span="24">
-        <el-card class="home-card">
-          <el-row :gutter="10">
-            <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-for="(item, index) in wyhItems" :key="item.id">
-              <div class="card-item" @click="goToPage('/more/wyh?folderId=' + item.id)">
-                {{ index + 1 }}){{ item.name }}
-              </div>
-            </el-col>
-            <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-if="loadingWyh && wyhItems.length === 0">
-              <div class="empty-text">暂无数据</div>
-            </el-col>
-          </el-row>
-        </el-card>
-      </el-col>
-    </el-row>
+      <span class="section-line"></span>
+      <el-button link type="primary" icon="Plus" @click="openAdd('wyh')">添加模块</el-button>
+    </div>
+    <el-card class="home-card" shadow="never">
+      <el-row :gutter="12">
+        <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-for="(item, index) in wyhItems" :key="item.id">
+          <div class="card-item" @click="goToPage('/more/wyh?folderId=' + item.id)">
+            <span class="card-index">{{ index + 1 }}</span>
+            <span class="card-text" :title="item.name">{{ item.name }}</span>
+          </div>
+        </el-col>
+        <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-if="!loadingWyh && wyhItems.length === 0">
+          <div class="empty-text">暂无数据</div>
+        </el-col>
+      </el-row>
+    </el-card>
 
     <!-- 直管工程 -->
-    <el-divider content-position="left">
+    <div class="section-header">
+      <svg-icon icon-class="build" class="section-icon" />
       <span class="section-title">直管工程</span>
-    </el-divider>
-    <el-row :gutter="20">
-      <el-col :span="24">
-        <el-card class="home-card">
-          <el-row :gutter="10">
-            <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-for="(item, index) in zggcList" :key="item.id">
-              <div class="project-item" @click="goToPage('/zggc/detail/' + item.id)">
-                <div class="project-name">{{ item.name }}</div>
-              </div>
-            </el-col>
-            <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-if="loadingZggc && zggcList.length === 0">
-              <div class="empty-text">暂无数据</div>
-            </el-col>
-          </el-row>
-        </el-card>
-      </el-col>
-    </el-row>
+      <span class="section-line"></span>
+      <el-button link type="primary" icon="Plus" @click="openAdd('zggc')">添加项目</el-button>
+    </div>
+    <el-card class="home-card" shadow="never">
+      <el-row :gutter="12">
+        <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-for="(item, index) in zggcList" :key="item.id">
+          <div class="project-item zggc-card" @click="goToPage('/zggc/detail/' + item.id)">
+            <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>
+            <div class="zggc-text" :title="item.name">
+              <svg-icon icon-class="build" class="project-icon" />
+              <span class="zggc-name">{{ item.name }}</span>
+              <span class="zggc-pct">{{ item.planValue || 0 }}%</span>
+            </div>
+            <span class="status-tag" :class="getZggcStatusClass(item.planValue)">{{ getZggcStatus(item.planValue) }}</span>
+          </div>
+        </el-col>
+        <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-if="!loadingZggc && zggcList.length === 0">
+          <div class="empty-text">暂无数据</div>
+        </el-col>
+      </el-row>
+    </el-card>
 
     <!-- 重点工程 -->
-    <el-divider content-position="left">
+    <div class="section-header">
+      <svg-icon icon-class="star" class="section-icon" />
       <span class="section-title">重点工程</span>
-    </el-divider>
-    <el-row :gutter="20">
-      <el-col :span="24">
-        <el-card class="home-card">
-          <el-row :gutter="10">
-            <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-for="(item, index) in zdgcList" :key="item.id">
-              <div class="project-item" @click="goToPage('/zdgc/detail/' + item.id)">
-                <div class="project-name">{{ item.name }}</div>
-              </div>
-            </el-col>
-            <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-if="loadingZdgc && zdgcList.length === 0">
-              <div class="empty-text">暂无数据</div>
-            </el-col>
-          </el-row>
-        </el-card>
-      </el-col>
-    </el-row>
+      <span class="section-line"></span>
+      <el-button link type="primary" icon="Plus" @click="openAdd('zdgc')">添加分类</el-button>
+    </div>
+    <el-card class="home-card" shadow="never">
+      <el-row :gutter="12">
+        <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-for="(item, index) in zdgcList" :key="item.id">
+          <div class="project-item zdgc-card" @click="goToPage('/zdgc/index?folderId=' + item.id)">
+            <span class="zdgc-name" :title="item.name">{{ item.name }}</span>
+          </div>
+        </el-col>
+        <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-if="!loadingZdgc && zdgcList.length === 0">
+          <div class="empty-text">暂无数据</div>
+        </el-col>
+      </el-row>
+    </el-card>
 
     <!-- 督导检查 -->
-    <el-divider content-position="left">
+    <div class="section-header">
+      <svg-icon icon-class="search" class="section-icon" />
       <span class="section-title">督导检查</span>
-    </el-divider>
-    <el-row :gutter="20">
-      <el-col :span="24">
-        <el-card class="home-card">
-          <el-row :gutter="10">
-            <el-col :span="2" v-for="(item, index) in ddjcList" :key="item.id">
-              <div class="ddjc-item" @click="goToPage('/ddjc/index?folderId=' + item.id)">
-                {{ item.name }}
-              </div>
-            </el-col>
-            <el-col :span="2" v-if="loadingDdjc && ddjcList.length === 0">
-              <div class="empty-text">暂无数据</div>
-            </el-col>
-          </el-row>
-        </el-card>
-      </el-col>
-    </el-row>
+      <span class="section-line"></span>
+      <el-button link type="primary" icon="Plus" @click="openAdd('ddjc')">添加检查</el-button>
+    </div>
+    <el-card class="home-card" shadow="never">
+      <el-row :gutter="12">
+        <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-for="(item, index) in ddjcList" :key="item.id">
+          <div class="card-item" @click="goToPage('/ddjc/index?folderId=' + item.id)">
+            <span class="card-index">{{ index + 1 }}</span>
+            <span class="card-text" :title="item.name">{{ item.name }}</span>
+          </div>
+        </el-col>
+        <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-if="!loadingDdjc && ddjcList.length === 0">
+          <div class="empty-text">暂无数据</div>
+        </el-col>
+      </el-row>
+    </el-card>
 
     <!-- 规章制度 -->
-    <el-divider content-position="left">
+    <div class="section-header">
+      <svg-icon icon-class="documentation" class="section-icon" />
       <span class="section-title">规章制度</span>
-    </el-divider>
-    <el-row :gutter="20">
-      <el-col :span="24">
-        <el-card class="home-card">
-          <el-row :gutter="10">
-            <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-for="(item, index) in gzzdList" :key="item.id">
-              <div class="card-item" @click="goToPage('/gzzd/detail/' + item.id)">
-                {{ item.name }}
-              </div>
-            </el-col>
-            <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-if="loadingGzzd && gzzdList.length === 0">
-              <div class="empty-text">暂无数据</div>
-            </el-col>
-          </el-row>
-        </el-card>
-      </el-col>
-    </el-row>
+      <span class="section-line"></span>
+      <el-button link type="primary" icon="Plus" @click="openAdd('gzzd')">添加记录</el-button>
+    </div>
+    <el-card class="home-card" shadow="never">
+      <el-row :gutter="12">
+        <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-for="(item, index) in gzzdList" :key="item.id">
+          <div class="card-item" @click="goToPage('/gzzd/index?folderId=' + item.id)">
+            <svg-icon icon-class="documentation" class="card-item-icon" />
+            <span class="card-text" :title="item.name">{{ item.name }}</span>
+          </div>
+        </el-col>
+        <el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="3" v-if="!loadingGzzd && gzzdList.length === 0">
+          <div class="empty-text">暂无数据</div>
+        </el-col>
+      </el-row>
+    </el-card>
+
+    <!-- 通用添加对话框 -->
+    <el-dialog :title="addTitle" v-model="addVisible" width="480px" append-to-body>
+      <el-form ref="addFormRef" :model="addForm" :rules="addRules" label-width="90px">
+        <el-form-item :label="nameLabel" prop="name">
+          <el-input v-model="addForm.name" placeholder="必填项,请输入名称" maxlength="100" />
+        </el-form-item>
+        <el-form-item v-if="showPlanInput" label="设置进度">
+          <el-input-number v-model="addForm.planValue" :min="0" :max="100" style="width: 150px" />
+          <span style="margin-left: 8px;">%</span>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" :loading="addSubmitting" @click="submitAdd">立即提交</el-button>
+          <el-button @click="resetAddForm">重置</el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
 import { ref, onMounted } from 'vue'
 import { useRouter } from 'vue-router'
-import { folderList } from '@/api/efinder/finder'
+import { folderList, mkdir } from '@/api/efinder/finder'
+import { addProject } from '@/api/pms/addProject'
 
 const router = useRouter()
+const { proxy } = getCurrentInstance()
 
 const tpzItems = ref([])
 const wyhItems = ref([])
@@ -152,10 +177,114 @@ const goToPage = (path) => {
   router.push(path)
 }
 
-const loadByCate = (cate, listRef, loadingRef) => {
+// 添加对话框
+const addVisible = ref(false)
+const addCate = ref('')
+const addFormRef = ref(null)
+const addForm = reactive({ name: '', planValue: 0 })
+const addRules = { name: [{ required: true, message: '名称不能为空', trigger: 'blur' }] }
+const addTitleMap = { tpz: '添加模块', wyh: '添加模块', zggc: '添加项目', zdgc: '添加分类', ddjc: '添加检查', gzzd: '添加记录' }
+const addTitle = computed(() => addTitleMap[addCate.value] || '添加')
+// 标签文字对齐旧版:项目(直管)/ 分类(重点/督导/规章)/ 文件夹(太浦/望亭)
+const nameLabelMap = { tpz: '文件夹名称', wyh: '文件夹名称', zggc: '项目名称', zdgc: '分类名称', ddjc: '分类名称', gzzd: '分类名称' }
+const nameLabel = computed(() => nameLabelMap[addCate.value] || '项目名称')
+// 进度仅直管工程显示(旧版 zggcPrarms=1 才显示)
+const showPlanInput = computed(() => addCate.value === 'zggc')
+
+function openAdd(cate) {
+  addCate.value = cate
+  addSubmitting.value = false
+  resetAddForm()
+  addVisible.value = true
+  nextTick(() => proxy.resetForm('addFormRef'))
+}
+
+function resetAddForm() {
+  addForm.name = ''
+  addForm.planValue = 0
+}
+
+const addSubmitting = ref(false)
+
+function submitAdd() {
+  if (addSubmitting.value) return
+  addFormRef.value.validate(valid => {
+    if (!valid) return
+    addSubmitting.value = true
+    // 直管工程:走 addProject 接口,自动创建子目录(工程概况/程序管理/... 对齐旧版)
+    if (addCate.value === 'zggc') {
+      addProject({
+        folderId: 0,
+        name: addForm.name,
+        zggcPrarms: '1',
+        planValues: String(addForm.planValue)
+      }).then(res => {
+        addSubmitting.value = false
+        if (res.code === 200) {
+          proxy.$modal.msgSuccess('添加成功')
+          addVisible.value = false
+          refreshByCate('zggc')
+        } else {
+          proxy.$modal.msgError(res.msg || '添加失败')
+        }
+      }).catch(() => {
+        addSubmitting.value = false
+        proxy.$modal.msgError('添加失败')
+      })
+      return
+    }
+    // 其余模块(重点工程分类/督导检查/规章制度/太浦闸/望亭闸):直接创建顶层文件夹
+    const data = { name: addForm.name, fdCate: addCate.value, pid: 0 }
+    mkdir(data).then(res => {
+      addSubmitting.value = false
+      if (res.code === 200) {
+        proxy.$modal.msgSuccess('添加成功')
+        addVisible.value = false
+        refreshByCate(addCate.value)
+      } else {
+        proxy.$modal.msgError(res.msg || '添加失败')
+      }
+    }).catch(() => {
+      addSubmitting.value = false
+      proxy.$modal.msgError('添加失败')
+    })
+  })
+}
+
+function refreshByCate(cate) {
+  const map = {
+    tpz: [tpzItems, loadingTpz],
+    wyh: [wyhItems, loadingWyh],
+    zggc: [zggcList, loadingZggc],
+    zdgc: [zdgcList, loadingZdgc],
+    ddjc: [ddjcList, loadingDdjc],
+    gzzd: [gzzdList, loadingGzzd]
+  }
+  const [listRef, loadingRef] = map[cate] || []
+  // 首页统一只展示顶层(pid=0),子级目录(如阶段目录)不作为一级模块展示
+  if (listRef) loadByCate(cate, listRef, loadingRef, isTopLevel)
+}
+
+// 直管工程:状态判断(对齐原版 tba-slgc)
+function getZggcStatus(val) {
+  if (!val || val === 0) return '未开工'
+  if (val >= 100) return '已完工'
+  return '在建'
+}
+function getZggcStatusClass(val) {
+  if (!val || val === 0) return 'status-gray'
+  if (val >= 100) return 'status-blue'
+  return 'status-green'
+}
+
+// 重点工程:首页展示顶层分类(一轮治太/二轮治太/太湖流域片重大水利工程)
+const isTopLevel = (item) => !item.pid || item.pid === 0
+
+const loadByCate = (cate, listRef, loadingRef, filterFn) => {
   loadingRef.value = true
   folderList({ fdCate: cate, pageSize: 999 }).then(res => {
-    listRef.value = res.rows || res.data || []
+    const list = res.rows || res.data || []
+    listRef.value = filterFn ? list.filter(item => filterFn(item, list)) : list
     loadingRef.value = false
   }).catch(() => {
     listRef.value = []
@@ -164,37 +293,71 @@ const loadByCate = (cate, listRef, loadingRef) => {
 }
 
 onMounted(() => {
-  loadByCate('tpz', tpzItems, loadingTpz)
-  loadByCate('wyh', wyhItems, loadingWyh)
-  loadByCate('zggc', zggcList, loadingZggc)
-  loadByCate('zdgc', zdgcList, loadingZdgc)
-  loadByCate('ddjc', ddjcList, loadingDdjc)
-  loadByCate('gzzd', gzzdList, loadingGzzd)
+  // 首页所有模块统一只展示顶层(pid=0),子级目录不作为一级模块展示
+  loadByCate('tpz', tpzItems, loadingTpz, isTopLevel)
+  loadByCate('wyh', wyhItems, loadingWyh, isTopLevel)
+  loadByCate('zggc', zggcList, loadingZggc, isTopLevel)
+  loadByCate('zdgc', zdgcList, loadingZdgc, isTopLevel)
+  loadByCate('ddjc', ddjcList, loadingDdjc, isTopLevel)
+  loadByCate('gzzd', gzzdList, loadingGzzd, isTopLevel)
 })
 </script>
 
 <style scoped>
 .home {
-  padding: 16px;
+  padding: 20px;
+}
+
+/* 分区标题:图标 + 标题 + 渐变分隔线 */
+.section-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 14px;
+}
+
+.section-icon {
+  width: 20px;
+  height: 20px;
+  color: #409eff;
+  flex-shrink: 0;
 }
 
 .section-title {
   font-size: 18px;
   font-weight: 600;
   color: #303133;
+  white-space: nowrap;
+}
+
+.section-line {
+  flex: 1;
+  height: 1px;
+  background: linear-gradient(to right, #e4e7ed, transparent);
 }
 
 .home-card {
-  margin-bottom: 20px;
-  border-radius: 8px;
+  margin-bottom: 24px;
+  border-radius: 10px;
+  border: 1px solid #ebeef5;
 }
 
+.home-card :deep(.el-card__body) {
+  padding: 16px 16px 12px;
+}
+
+/* 通用卡片:白底 + 左色条 + hover 上浮 */
 .card-item {
-  height: 40px;
-  line-height: 40px;
-  text-align: center;
+  height: 44px;
+  line-height: 44px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 6px;
+  background: #fff;
   border: 1px solid #ebeef5;
-  border-radius: 6px;
+  border-left: 3px solid #409eff;
+  border-radius: 8px;
   cursor: pointer;
   transition: all 0.25s;
   font-size: 13px;
@@ -203,68 +366,153 @@ onMounted(() => {
   overflow: hidden;
   text-overflow: ellipsis;
   margin: 4px 0;
+  padding: 0 8px;
 }
 
 .card-item:hover {
-  background-color: #ecf5ff;
+  transform: translateY(-2px);
+  box-shadow: 0 4px 12px rgba(64, 158, 255, 0.15);
   border-color: #409eff;
   color: #409eff;
 }
 
+.card-index {
+  font-size: 12px;
+  color: #a8abb2;
+  flex-shrink: 0;
+}
+
+.card-text {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.card-item-icon {
+  width: 14px;
+  height: 14px;
+  flex-shrink: 0;
+  color: #409eff;
+}
+
+/* 项目卡片容器(直管工程与重点工程高度统一为 60px) */
 .project-item {
   height: 60px;
-  background: linear-gradient(135deg, #67c23a, #85ce61);
-  border-radius: 6px;
+  background: #fff;
+  border: 1px solid #e4e7ed;
+  border-radius: 10px;
   cursor: pointer;
   transition: all 0.25s;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  padding: 5px;
+  position: relative;
   margin: 4px 0;
+  overflow: hidden;
 }
 
 .project-item:hover {
-  background: linear-gradient(135deg, #5daf34, #74bd52);
-  transform: translateY(-2px);
-  box-shadow: 0 4px 12px rgba(103, 194, 58, 0.3);
+  transform: translateY(-3px);
+  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
 }
 
-.project-name {
-  color: #fff;
-  font-size: 12px;
-  text-align: center;
+/* 直管工程进度条:蓝灰双色条铺满整个卡片 */
+.zggc-bar {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+}
+
+.zggc-text {
+  position: absolute;
+  left: 8%;
+  top: 50%;
+  transform: translateY(-50%);
+  color: #434342;
+  font-size: 13px;
+  font-weight: 600;
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
-  width: 100%;
+  max-width: 68%;
+  display: flex;
+  align-items: center;
+  gap: 3px;
+  z-index: 1;
+  background: rgba(255, 255, 255, 0.75);
+  border-radius: 12px;
+  padding: 3px 10px;
 }
 
-.ddjc-item {
-  height: 40px;
-  line-height: 40px;
-  text-align: center;
-  border: 1px solid #ebeef5;
-  border-radius: 6px;
-  cursor: pointer;
-  transition: all 0.25s;
-  font-size: 12px;
-  color: #606266;
-  white-space: nowrap;
+.zggc-name {
   overflow: hidden;
   text-overflow: ellipsis;
-  margin: 4px 0;
+  white-space: nowrap;
 }
 
-.ddjc-item:hover {
-  background-color: #ecf5ff;
-  border-color: #409eff;
-  color: #409eff;
+.zggc-pct {
+  flex-shrink: 0;
+  color: #fff;
+  font-size: 12px;
+  background: rgba(0, 0, 0, 0.25);
+  border-radius: 8px;
+  padding: 1px 6px;
+}
+
+.project-icon {
+  width: 14px;
+  height: 14px;
+  flex-shrink: 0;
+  color: #0f69dc;
+}
+
+/* 状态标签(右上角) */
+.status-tag {
+  position: absolute;
+  top: 8px;
+  right: 8px;
+  font-size: 11px;
+  padding: 1px 8px;
+  border-radius: 10px;
+  color: #fff;
+  z-index: 2;
+}
+
+.status-blue {
+  background: rgba(15, 105, 220, 0.8);
+}
+
+.status-green {
+  background: #6cdcf0;
+}
+
+.status-gray {
+  background: #c0c4cc;
+}
+
+/* 重点工程:分类卡片(旧版逻辑:分类无 type 走 else 分支 → 灰色) */
+.zdgc-card {
+  height: 60px;
+  display: flex;
+  align-items: center;
+  padding: 0 10px;
+  border: none;
+  background: rgb(212, 212, 212);
+}
+
+.zdgc-name {
+  color: #434342;
+  font-size: 14px;
+  font-weight: 600;
+  margin-left: 8%;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  width: 88%;
 }
 
 .empty-text {
-  height: 40px;
-  line-height: 40px;
+  height: 44px;
+  line-height: 44px;
   text-align: center;
   color: #909399;
   font-size: 14px;

+ 22 - 2
gw-ui/src/views/portal/ddjc.vue

@@ -2,7 +2,10 @@
   <div class="app-container ddjc-wrap">
     <div class="category-sidebar">
       <div class="sidebar-header">
-        <span>检查分类</span>
+        <span class="sidebar-title">
+          <svg-icon icon-class="search" class="sidebar-icon" />
+          检查分类
+        </span>
         <el-button link type="primary" icon="Plus" @click="handleAddRecord" />
       </div>
       <el-tree
@@ -297,7 +300,7 @@ onMounted(() => {
   flex-shrink: 0;
   background: #fff;
   padding: 12px;
-  border-radius: 6px;
+  border-radius: 10px;
   border: 1px solid #ebeef5;
   display: flex;
   flex-direction: column;
@@ -315,6 +318,18 @@ onMounted(() => {
   font-weight: 600;
   margin-bottom: 8px;
   flex-shrink: 0;
+  padding-bottom: 8px;
+  border-bottom: 1px solid #f0f2f6;
+}
+.sidebar-title {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+}
+.sidebar-icon {
+  width: 16px;
+  height: 16px;
+  color: #409eff;
 }
 .file-content {
   flex: 1;
@@ -322,6 +337,10 @@ onMounted(() => {
   display: flex;
   flex-direction: column;
   overflow: hidden;
+  background: #fff;
+  border: 1px solid #ebeef5;
+  border-radius: 10px;
+  padding: 16px;
   .el-table {
     flex: 1;
     overflow: auto;
@@ -330,6 +349,7 @@ onMounted(() => {
 .content-header h3 {
   margin: 0 0 12px;
   font-size: 18px;
+  font-weight: 600;
   flex-shrink: 0;
 }
 .mb8 {

+ 26 - 3
gw-ui/src/views/portal/gzzd.vue

@@ -2,7 +2,10 @@
   <div class="app-container gzzd-wrap">
     <div class="category-sidebar">
       <div class="sidebar-header">
-        <span>制度分类</span>
+        <span class="sidebar-title">
+          <svg-icon icon-class="documentation" class="sidebar-icon" />
+          制度分类
+        </span>
         <el-button link type="primary" icon="Plus" @click="handleAddCategory" />
       </div>
       <el-tree
@@ -117,6 +120,7 @@ import { parseTime } from "@/utils/goldenwater"
 
 const { proxy } = getCurrentInstance()
 const router = useRouter()
+const route = useRoute()
 
 const categoryTree = ref([])
 const currentCategory = ref(null)
@@ -146,7 +150,9 @@ function getCategoryTree() {
     const list = response.rows || response.data || []
     categoryTree.value = proxy.handleTree(list, "id", "pid")
     if (list.length > 0 && !currentCategory.value) {
-      currentCategory.value = list[0]
+      // 支持首页传入 folderId 定位分类
+      const fid = route.query.folderId ? parseInt(route.query.folderId) : null
+      currentCategory.value = fid ? (list.find(item => item.id === fid) || list[0]) : list[0]
       getDocList()
     }
   })
@@ -306,7 +312,7 @@ onMounted(() => {
   flex-shrink: 0;
   background: #fff;
   padding: 12px;
-  border-radius: 6px;
+  border-radius: 10px;
   border: 1px solid #ebeef5;
   display: flex;
   flex-direction: column;
@@ -324,6 +330,18 @@ onMounted(() => {
   font-weight: 600;
   margin-bottom: 8px;
   flex-shrink: 0;
+  padding-bottom: 8px;
+  border-bottom: 1px solid #f0f2f6;
+}
+.sidebar-title {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+}
+.sidebar-icon {
+  width: 16px;
+  height: 16px;
+  color: #409eff;
 }
 .file-content {
   flex: 1;
@@ -331,6 +349,10 @@ onMounted(() => {
   display: flex;
   flex-direction: column;
   overflow: hidden;
+  background: #fff;
+  border: 1px solid #ebeef5;
+  border-radius: 10px;
+  padding: 16px;
   .el-table {
     flex: 1;
     overflow: auto;
@@ -339,6 +361,7 @@ onMounted(() => {
 .content-header h3 {
   margin: 0 0 12px;
   font-size: 18px;
+  font-weight: 600;
   flex-shrink: 0;
 }
 .file-icon {

+ 57 - 8
gw-ui/src/views/portal/portalDetail.vue

@@ -19,8 +19,8 @@
           :props="{ children: 'children', label: 'name' }"
           node-key="id"
           :filter-node-method="filterNode"
+          :default-expanded-keys="expandedKeys"
           highlight-current
-          default-expand-all
           @node-click="handleNodeClick"
         />
       </div>
@@ -173,10 +173,13 @@ const fdCate = computed(() => {
 const folderId = ref(Number(route.params.folderId))
 
 const titleMap = { zggc: '直管工程', zdgc: '重点工程', gzzd: '规章制度', ddjc: '监督检查', wyh: '望亭闸', tpz: '图片展' }
-const pageTitle = computed(() => titleMap[fdCate.value] || '详情')
+// 页面标题优先显示当前项目(文件夹)名称,实现点击项目跳转到对应模块的感知
+const currentFolderName = ref('')
+const pageTitle = computed(() => currentFolderName.value || titleMap[fdCate.value] || '详情')
 const showPlanValue = computed(() => fdCate.value === 'zggc' || fdCate.value === 'zdgc')
 
 const folderTree = ref([])
+const expandedKeys = ref([])
 const folderSearch = ref('')
 const fileList = ref([])
 const loading = ref(false)
@@ -227,11 +230,36 @@ function goBack() {
 function getFolderTree() {
   return folderList({ fdCate: fdCate.value, pageSize: 999 }).then(response => {
     const list = response.rows || response.data || []
+    // 根据当前选中文件夹更新标题,体现二级模块跳转
+    const current = list.find(f => f.id === currentPid.value)
+    if (current) {
+      currentFolderName.value = current.name
+    }
     const treeData = proxy.handleTree(list, 'id', 'pid')
     folderTree.value = treeData
+    // 计算当前项目的祖先链,树只展开该链,聚焦到当前项目及其二级模块
+    expandedKeys.value = getAncestorKeys(list, currentPid.value)
+    nextTick(() => {
+      if (folderTreeRef.value && currentPid.value) {
+        folderTreeRef.value.setCurrentKey(currentPid.value)
+      }
+    })
   })
 }
 
+// 计算某节点所有祖先 id,用于树展开
+function getAncestorKeys(list, pid) {
+  const keys = []
+  let current = list.find(f => f.id === pid)
+  while (current && current.pid) {
+    const parent = list.find(f => f.id === current.pid)
+    if (!parent) break
+    keys.push(parent.id)
+    current = parent
+  }
+  return keys
+}
+
 function fetchFileList() {
   if (!currentPid.value && !folderId.value) return
   loading.value = true
@@ -257,11 +285,24 @@ function loadPlanValue(id) {
 }
 
 function handleNodeClick(node) {
-  currentPid.value = node.id
-  queryParams.value.pageNum = 1
-  fetchFileList()
+  currentFolderName.value = node.name
+  // 更新 URL,实现二级模块的真实跳转(可刷新/可回退),由 watcher 统一加载
+  if (Number(route.params.folderId) !== node.id) {
+    router.replace({ path: '/' + fdCate.value + '/detail/' + node.id })
+  } else {
+    fetchFileList()
+  }
 }
 
+// 监听 URL 参数变化(浏览器前进/后退或外部跳转)
+watch(() => route.params.folderId, (val) => {
+  if (val) {
+    currentPid.value = Number(val)
+    queryParams.value.pageNum = 1
+    Promise.all([getFolderTree(), fetchFileList()])
+  }
+})
+
 function handleQuery() {
   queryParams.value.pageNum = 1
   fetchFileList()
@@ -420,8 +461,7 @@ function formatFileSize(size) {
 onMounted(() => {
   currentPid.value = folderId.value
   Promise.all([getFolderTree(), fetchFileList()])
-})
-</script>
+})</script>
 
 <style lang="scss" scoped>
 .tree-sidebar-manage-wrap {
@@ -432,6 +472,7 @@ onMounted(() => {
 .page-header-title {
   font-size: 18px;
   font-weight: 600;
+  color: #303133;
 }
 .portal-detail-wrap {
   display: flex;
@@ -444,7 +485,7 @@ onMounted(() => {
   flex-shrink: 0;
   background: #fff;
   padding: 12px;
-  border-radius: 6px;
+  border-radius: 10px;
   border: 1px solid #ebeef5;
   display: flex;
   flex-direction: column;
@@ -463,6 +504,8 @@ onMounted(() => {
   font-weight: 600;
   margin-bottom: 8px;
   flex-shrink: 0;
+  padding-bottom: 8px;
+  border-bottom: 1px solid #f0f2f6;
 }
 .file-content {
   flex: 1;
@@ -471,11 +514,17 @@ onMounted(() => {
   flex-direction: column;
   height: 100%;
   overflow: hidden;
+  background: #fff;
+  border: 1px solid #ebeef5;
+  border-radius: 10px;
+  padding: 16px;
 }
 .content-header h3 {
   margin: 0 0 12px;
   font-size: 18px;
+  font-weight: 600;
   flex-shrink: 0;
+  color: #303133;
 }
 .search-form {
   flex-shrink: 0;

+ 16 - 1
gw-ui/src/views/portal/tpz.vue

@@ -1,7 +1,10 @@
 <template>
   <div class="app-container">
     <div class="portal-header">
-      <h2 class="portal-title">图片库</h2>
+      <div class="header-title">
+        <svg-icon icon-class="monitor" class="header-icon" />
+        <h2 class="portal-title">图片库</h2>
+      </div>
       <el-button type="primary" icon="Upload" @click="handleUpload" v-hasPermi="['efinder:file:upload']">上传图片</el-button>
     </div>
 
@@ -189,6 +192,18 @@ onMounted(async () => {
   align-items: center;
   justify-content: space-between;
   margin-bottom: 16px;
+  padding-bottom: 12px;
+  border-bottom: 1px solid #f0f2f6;
+}
+.header-title {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+.header-icon {
+  width: 20px;
+  height: 20px;
+  color: #409eff;
 }
 
 .portal-title {

+ 16 - 1
gw-ui/src/views/portal/wyh.vue

@@ -1,7 +1,10 @@
 <template>
   <div class="app-container">
     <div class="portal-header">
-      <h2 class="portal-title">望亭枢纽</h2>
+      <div class="header-title">
+        <svg-icon icon-class="example" class="header-icon" />
+        <h2 class="portal-title">望亭枢纽</h2>
+      </div>
       <el-button type="primary" icon="Plus" @click="handleAddFolder" v-hasPermi="['efinder:folder:add']">新建文件夹</el-button>
     </div>
 
@@ -222,6 +225,18 @@ onMounted(async () => {
   align-items: center;
   justify-content: space-between;
   margin-bottom: 16px;
+  padding-bottom: 12px;
+  border-bottom: 1px solid #f0f2f6;
+}
+.header-title {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+.header-icon {
+  width: 20px;
+  height: 20px;
+  color: #409eff;
 }
 
 .portal-title {

+ 52 - 12
gw-ui/src/views/portal/zdgc.vue

@@ -2,7 +2,10 @@
   <div class="app-container">
     <div class="content-box">
       <div class="portal-header">
-        <h2 class="portal-title">重点工程</h2>
+        <div class="header-title">
+          <svg-icon icon-class="star" class="header-icon" />
+          <h2 class="portal-title">重点工程</h2>
+        </div>
         <div class="header-actions">
           <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleBatchDelete">删除</el-button>
           <el-button type="primary" icon="Plus" @click="handleAddProject">添加工程</el-button>
@@ -59,9 +62,6 @@
         <el-form-item label="工程名称" prop="name">
           <el-input v-model="addForm.name" placeholder="请输入工程名称" maxlength="100" />
         </el-form-item>
-        <el-form-item label="工程描述" prop="description">
-          <el-input v-model="addForm.description" type="textarea" :rows="3" placeholder="请输入工程描述(200字以内)" maxlength="200" show-word-limit />
-        </el-form-item>
         <el-form-item label="设置进度" prop="planValue">
           <el-slider v-model="addForm.planValue" :min="0" :max="100" show-input style="width: 320px" />
         </el-form-item>
@@ -76,9 +76,11 @@
 
 <script setup name="Zdgc">
 import { folderList, mkdir, deleteFolderRecursive } from "@/api/efinder/finder"
+import { addProject } from "@/api/pms/addProject"
 
 const { proxy } = getCurrentInstance()
 const router = useRouter()
+const route = useRoute()
 
 const categoryTabs = ref([])
 const activeTab = ref('-1')
@@ -92,7 +94,7 @@ const queryParams = reactive({ pageNum: 1, pageSize: 20, fdCate: 'zdgc', name: u
 
 const addDialogVisible = ref(false)
 const addFormRef = ref(null)
-const addForm = reactive({ name: '', description: '', planValue: 0 })
+const addForm = reactive({ name: '', planValue: 0 })
 const addRules = { name: [{ required: true, message: '工程名称不能为空', trigger: 'blur' }] }
 
 function getPlanColor(val) {
@@ -111,7 +113,10 @@ function loadTabs() {
     tree.forEach(t => { t.projects = t.children || []; delete t.children })
     categoryTabs.value = tree
     if (tree.length > 0) {
-      activeTab.value = String(tree[0].id)
+      // 支持首页传入 folderId 定位分类
+      const targetId = Number(route.query.folderId)
+      const target = tree.find(t => t.id === targetId)
+      activeTab.value = target ? String(target.id) : String(tree[0].id)
       showTabProjects()
     }
   })
@@ -119,6 +124,8 @@ function loadTabs() {
 
 function handleTabClick(pane) {
   activeTab.value = String(pane.paneName ?? pane.props?.name ?? pane.name)
+  // 切换分类时重置分页,避免停留在旧页码
+  queryParams.pageNum = 1
 }
 
 watch(activeTab, (val) => {
@@ -159,13 +166,16 @@ function handleProjectClick(item) {
 }
 
 function handleAddProject() {
+  if (categoryTabs.value.length === 0 || activeTab.value === '-1') {
+    proxy.$modal.msgWarning('请先创建分类')
+    return
+  }
   resetAddForm()
   addDialogVisible.value = true
 }
 
 function resetAddForm() {
   addForm.name = ''
-  addForm.description = ''
   addForm.planValue = 0
 }
 
@@ -175,6 +185,8 @@ function reloadAll() {
     const tree = proxy.handleTree(JSON.parse(JSON.stringify(list)), 'id', 'pid')
     tree.forEach(t => { t.projects = t.children || []; delete t.children })
     categoryTabs.value = tree
+    // 操作后回到第一页,避免停留在超出范围的页码
+    queryParams.pageNum = 1
     showTabProjects()
   })
 }
@@ -182,12 +194,22 @@ function reloadAll() {
 function submitAdd() {
   addFormRef.value.validate(valid => {
     if (!valid) return
-    mkdir({ name: addForm.name, fdCate: 'zdgc', pid: Number(activeTab.value), planValue: addForm.planValue }).then(res => {
+    // 走 addProject 接口,自动创建子目录(工程概况/监督检查/工程验收)
+    addProject({
+      folderId: String(activeTab.value),
+      name: addForm.name,
+      zggcPrarms: '2',
+      planValues: String(addForm.planValue)
+    }).then(res => {
       if (res.code === 200) {
         proxy.$modal.msgSuccess('添加成功')
         addDialogVisible.value = false
         reloadAll()
+      } else {
+        proxy.$modal.msgError(res.msg || '添加失败')
       }
+    }).catch(() => {
+      proxy.$modal.msgError('添加失败')
     })
   })
 }
@@ -222,7 +244,7 @@ onMounted(() => { loadTabs() })
 <style lang="scss" scoped>
 .content-box {
   background: #fff;
-  border-radius: 8px;
+  border-radius: 10px;
   padding: 20px;
   border: 1px solid #ebeef5;
   height: 100%;
@@ -239,18 +261,36 @@ onMounted(() => { loadTabs() })
   flex: 1;
   overflow: auto;
   min-height: 0;
+  padding: 4px 2px;
+}
+.portal-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 8px;
+  padding-bottom: 12px;
+  border-bottom: 1px solid #f0f2f6;
+}
+.header-title {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+.header-icon {
+  width: 20px;
+  height: 20px;
+  color: #409eff;
 }
-.portal-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
 .portal-title { font-size: 20px; font-weight: 600; margin: 0; }
 .header-actions { display: flex; gap: 8px; }
 .category-tabs { margin-bottom: 16px; }
 .search-form { margin-bottom: 16px; }
-.project-card { cursor: pointer; transition: all 0.25s; border-radius: 8px; &:hover { transform: translateY(-3px); box-shadow: 0 6px 20px rgba(0,0,0,0.08); } }
+.project-card { cursor: pointer; transition: all 0.25s; border-radius: 10px; &:hover { transform: translateY(-3px); box-shadow: 0 6px 20px rgba(0,0,0,0.1); } }
 .card-header-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
 .card-delete-btn { font-size: 14px; }
 .card-body { cursor: pointer; }
 .card-info { min-width: 0; }
-.card-title { font-size: 15px; font-weight: 500; margin-bottom: 6px; }
+.card-title { font-size: 15px; font-weight: 600; margin-bottom: 8px; color: #303133; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
 .card-progress { margin-top: 4px; }
 .mb16 { margin-bottom: 16px; }
 </style>

+ 37 - 12
gw-ui/src/views/portal/zggc.vue

@@ -2,7 +2,10 @@
   <div class="app-container">
     <div class="content-box">
       <div class="portal-header">
-        <h2 class="portal-title">直管工程</h2>
+        <div class="header-title">
+          <svg-icon icon-class="build" class="header-icon" />
+          <h2 class="portal-title">直管工程</h2>
+        </div>
         <div class="header-actions">
           <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleBatchDelete">删除</el-button>
           <el-button type="primary" icon="Plus" @click="handleAddProject">添加项目</el-button>
@@ -54,9 +57,6 @@
         <el-form-item label="项目名称" prop="name">
           <el-input v-model="addForm.name" placeholder="请输入项目名称" maxlength="100" />
         </el-form-item>
-        <el-form-item label="项目描述" prop="description">
-          <el-input v-model="addForm.description" type="textarea" :rows="3" placeholder="请输入项目描述(200字以内)" maxlength="200" show-word-limit />
-        </el-form-item>
         <el-form-item label="设置进度" prop="planValue">
           <el-slider v-model="addForm.planValue" :min="0" :max="100" show-input style="width: 320px" />
         </el-form-item>
@@ -71,6 +71,7 @@
 
 <script setup name="Zggc">
 import { folderList, mkdir, deleteFolderRecursive } from "@/api/efinder/finder"
+import { addProject } from "@/api/pms/addProject"
 
 const { proxy } = getCurrentInstance()
 const router = useRouter()
@@ -82,13 +83,13 @@ const ids = ref([])
 const multiple = ref(true)
 
 const data = reactive({
-  queryParams: { pageNum: 1, pageSize: 20, fdCate: "zggc", name: undefined }
+  queryParams: { pageNum: 1, pageSize: 20, fdCate: "zggc", pid: 0, name: undefined }
 })
 const { queryParams } = toRefs(data)
 
 const addDialogVisible = ref(false)
 const addFormRef = ref(null)
-const addForm = reactive({ name: '', description: '', planValue: 0 })
+const addForm = reactive({ name: '', planValue: 0 })
 const addRules = {
   name: [{ required: true, message: '项目名称不能为空', trigger: 'blur' }]
 }
@@ -136,19 +137,28 @@ function handleAddProject() {
 
 function resetAddForm() {
   addForm.name = ''
-  addForm.description = ''
   addForm.planValue = 0
 }
 
 function submitAdd() {
   addFormRef.value.validate(valid => {
     if (!valid) return
-    mkdir({ name: addForm.name, fdCate: 'zggc', pid: 0, planValue: addForm.planValue }).then(res => {
+    // 走 addProject 接口,自动创建子目录(工程概况/程序管理/... 与首页行为一致)
+    addProject({
+      folderId: 0,
+      name: addForm.name,
+      zggcPrarms: '1',
+      planValues: String(addForm.planValue)
+    }).then(res => {
       if (res.code === 200) {
         proxy.$modal.msgSuccess('添加成功')
         addDialogVisible.value = false
         getFolderList()
+      } else {
+        proxy.$modal.msgError(res.msg || '添加失败')
       }
+    }).catch(() => {
+      proxy.$modal.msgError('添加失败')
     })
   })
 }
@@ -158,6 +168,7 @@ function handleDelete(item) {
     return deleteFolderRecursive(item.id)
   }).then(() => {
     proxy.$modal.msgSuccess('删除成功')
+    queryParams.value.pageNum = 1
     getFolderList()
   }).catch(() => {})
 }
@@ -173,6 +184,7 @@ async function handleBatchDelete() {
       await deleteFolderRecursive(id)
     }
     proxy.$modal.msgSuccess('删除成功')
+    queryParams.value.pageNum = 1
     getFolderList()
   } catch (_) {}
 }
@@ -183,7 +195,7 @@ onMounted(() => { getFolderList() })
 <style lang="scss" scoped>
 .content-box {
   background: #fff;
-  border-radius: 8px;
+  border-radius: 10px;
   padding: 20px;
   border: 1px solid #ebeef5;
   height: 100%;
@@ -194,6 +206,7 @@ onMounted(() => { getFolderList() })
   flex: 1;
   overflow: auto;
   min-height: 0;
+  padding: 4px 2px;
 }
 
 .portal-header {
@@ -201,6 +214,18 @@ onMounted(() => { getFolderList() })
   align-items: center;
   justify-content: space-between;
   margin-bottom: 16px;
+  padding-bottom: 12px;
+  border-bottom: 1px solid #f0f2f6;
+}
+.header-title {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+.header-icon {
+  width: 20px;
+  height: 20px;
+  color: #409eff;
 }
 .portal-title { font-size: 20px; font-weight: 600; margin: 0; }
 .header-actions { display: flex; gap: 8px; }
@@ -209,10 +234,10 @@ onMounted(() => { getFolderList() })
 .project-card {
   cursor: pointer;
   transition: all 0.25s;
-  border-radius: 8px;
+  border-radius: 10px;
   &:hover {
     transform: translateY(-3px);
-    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08);
+    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);
   }
 }
 .card-header-row {
@@ -223,7 +248,7 @@ onMounted(() => { getFolderList() })
 }
 .card-delete-btn { font-size: 14px; }
 .card-info { min-width: 0; }
-.card-title { font-size: 15px; font-weight: 500; margin-bottom: 6px; }
+.card-title { font-size: 15px; font-weight: 600; margin-bottom: 8px; color: #303133; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
 .card-progress { margin-top: 4px; }
 .mb16 { margin-bottom: 16px; }
 </style>