77681 1 天之前
父节点
当前提交
82dea9b7bb

+ 1 - 1
gw-admin/src/main/java/com/goldenwater/web/core/config/SwaggerConfig.java

@@ -53,7 +53,7 @@ public class SwaggerConfig
     {
         return new Info()
             // 设置标题
-            .title("标题:北京金水管理系统_接口文档")
+            .title("标题:水利工程建设项目信息管理系统_接口文档")
             // 描述
             .description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...")
             // 作者信息

+ 0 - 1
gw-slgc/src/main/java/com/goldenwater/web/controller/efinder/DocFileController.java

@@ -118,7 +118,6 @@ public class DocFileController extends BaseController {
      * 删除文件
      */
     @Operation(summary = "删除文件")
-    @Log(title = "文件管理", businessType = BusinessType.DELETE)
     @DeleteMapping("/{ids}")
     public AjaxResult remove(@Parameter(description = "文件ID数组") @PathVariable Long[] ids) {
         return toAjax(docFileService.deleteByIds(ids));

+ 0 - 2
gw-slgc/src/main/java/com/goldenwater/web/controller/efinder/DocFolderController.java

@@ -80,7 +80,6 @@ public class DocFolderController extends BaseController {
      * 删除文件夹
      */
     @Operation(summary = "删除文件夹")
-    @Log(title = "文件夹管理", businessType = BusinessType.DELETE)
     @DeleteMapping("/{ids}")
     public AjaxResult remove(@Parameter(description = "文件夹ID数组") @PathVariable Long[] ids) {
         return toAjax(docFolderService.deleteByIds(ids));
@@ -90,7 +89,6 @@ public class DocFolderController extends BaseController {
      * 递归删除文件夹及其所有子文件夹和文件
      */
     @Operation(summary = "递归删除文件夹及其所有子文件夹和文件")
-    @Log(title = "文件夹管理", businessType = BusinessType.DELETE)
     @DeleteMapping("/recursive/{id}")
     public AjaxResult removeRecursive(@Parameter(description = "文件夹ID") @PathVariable Long id) {
         return toAjax(docFolderService.deleteRecursive(id));

+ 6 - 3
gw-slgc/src/main/resources/mapper/efinder/DocFileMapper.xml

@@ -25,9 +25,9 @@
     <select id="selectById" resultMap="BaseResultMap">
         select <include refid="BaseColumns"/> from DOC_FILE where WK_ID = #{id}
     </select>
-    <insert id="insert">
-        insert into DOC_FILE (<include refid="BaseColumns"/>)
-        values (#{id}, #{pid}, #{name}, #{type}, #{size}, #{date}, #{path})
+    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
+        insert into DOC_FILE (FD_PID, WK_TITLE, FILE_TYPE, FILE_SIZE, FILE_DATE, FILE_PATH)
+        values (#{pid}, #{name}, #{type}, #{size}, #{date}, #{path})
     </insert>
     <update id="update">
         update DOC_FILE
@@ -50,6 +50,9 @@
             #{id}
         </foreach>
     </delete>
+    <delete id="deleteByPid">
+        delete from DOC_FILE where FD_PID = #{pid}
+    </delete>
     <select id="selectByParentId" resultMap="BaseResultMap">
         select <include refid="BaseColumns"/> from DOC_FILE where FD_PID = #{pid}
     </select>

+ 2 - 2
gw-slgc/src/main/resources/mapper/efinder/DocFolderMapper.xml

@@ -25,8 +25,8 @@
         select <include refid="BaseColumns"/>, PLAN_VALUE from DOC_FOLDER where FD_ID = #{id}
     </select>
     <insert id="insert" useGeneratedKeys="true" keyProperty="id">
-        insert into DOC_FOLDER (FD_PID, FD_NAME, FD_DATE, FD_CATE)
-        values (#{pid}, #{name}, #{date}, #{fdCate})
+        insert into DOC_FOLDER (FD_PID, FD_NAME, FD_DATE, FD_CATE, PLAN_VALUE)
+        values (#{pid}, #{name}, #{date}, #{fdCate}, #{planValue})
     </insert>
     <update id="update">
         update DOC_FOLDER

+ 4 - 4
gw-ui/.env.development

@@ -1,14 +1,14 @@
 # 页面标题
-VITE_APP_TITLE = 北京金水管理系统
+VITE_APP_TITLE = 水利工程建设项目信息管理系统
 
 # 开发环境配置
 VITE_APP_ENV = 'development'
 #vue启动端口
 VITE_APP_PORT = 25022
 # 前端基础路径
-VITE_APP_BASE_TITLE = '/gw'
-# 北京金水管理系统/开发环境
-VITE_SERVICE_BASE_TITLE = '/gw-api'
+VITE_APP_BASE_TITLE = '/slgc'
+# 水利工程建设项目信息管理系统/开发环境
+VITE_SERVICE_BASE_TITLE = '/slgc-api'
 
 # 后端接口基础路径
 VITE_SERVER_URL = 'http://127.0.0.1:8093/slgc'

+ 5 - 5
gw-ui/.env.production

@@ -1,17 +1,17 @@
 # 页面标题
-VITE_APP_TITLE = 北京金水管理系统
+VITE_APP_TITLE = 水利工程建设项目信息管理系统
 
 # 生产环境配置
 VITE_APP_ENV = 'production'
 #vue启动端口
 VITE_APP_PORT = 25022
 # 前端基础路径
-VITE_APP_BASE_TITLE = '/gw'
-# 北京金水管理系统/生产环境
-VITE_SERVICE_BASE_TITLE = '/gw-api'
+VITE_APP_BASE_TITLE = '/slgc'
+# 水利工程建设项目信息管理系统/生产环境
+VITE_SERVICE_BASE_TITLE = '/slgc-api'
 
 # 后端接口基础路径
-VITE_SERVER_URL = 'http://39.98.38.2:18055/gw-api'
+VITE_SERVER_URL = 'http://39.98.38.2:18055/slgc-api'
 
 # 是否在打包时开启压缩,支持 gzip 和 brotli
 VITE_BUILD_COMPRESS = gzip

+ 5 - 5
gw-ui/.env.staging

@@ -1,5 +1,5 @@
 # 页面标题
-VITE_APP_TITLE = 北京金水管理系统
+VITE_APP_TITLE = 水利工程建设项目信息管理系统
 
 # 生产环境配置
 VITE_APP_ENV = 'staging'
@@ -7,13 +7,13 @@ VITE_APP_ENV = 'staging'
 #vue启动端口
 VITE_APP_PORT = 25022
 # 前端基础路径
-VITE_APP_BASE_TITLE = '/gw'
+VITE_APP_BASE_TITLE = '/slgc'
 
-# 北京金水管理系统/生产环境
-VITE_SERVICE_BASE_TITLE = '/gw-api'
+# 水利工程建设项目信息管理系统/生产环境
+VITE_SERVICE_BASE_TITLE = '/slgc-api'
 
 # 后端接口基础路径
-VITE_SERVER_URL = 'http://39.98.38.2:18055/gw-api'
+VITE_SERVER_URL = 'http://39.98.38.2:18055/slgc-api'
 
 # 是否在打包时开启压缩,支持 gzip 和 brotli
 VITE_BUILD_COMPRESS = gzip

+ 1 - 1
gw-ui/package.json

@@ -1,7 +1,7 @@
 {
   "name": "goldenwater",
   "version": "3.9.2",
-  "description": "北京金水管理系统",
+  "description": "水利工程建设项目信息管理系统",
   "author": "北京金水",
   "license": "MIT",
   "type": "module",

+ 1 - 1
gw-ui/src/layout/components/TopNavbar/index.vue

@@ -144,7 +144,7 @@ export default {
       })
         .then(() => {
           userStore.logOut().then(() => {
-            location.href = "/gw/index";
+            location.href = "/slgc/index";
           });
         })
         .catch(() => {});

+ 19 - 123
gw-ui/src/router/index.js

@@ -156,116 +156,15 @@ export const businessRoutes = [
       }
     ]
   },
-  {
-    path: '/pms',
-    component: Layout,
-    redirect: '/pms/project',
-    name: 'Pms',
-    meta: { title: '项目管理', icon: 'documentation' },
-    children: [
-      {
-        path: 'project',
-        component: () => import('@/views/pms/project'),
-        name: 'PmsProject',
-        meta: { title: '项目列表', permissions: ['pms:project:list'] }
-      },
-      {
-        path: 'project/view/:prjId',
-        component: () => import('@/views/pms/projectView'),
-        name: 'PmsProjectView',
-        hidden: true,
-        meta: { title: '项目详情', activeMenu: '/pms/project' }
-      },
-      {
-        path: 'contract',
-        component: () => import('@/views/pms/contract'),
-        name: 'PmsContract',
-        meta: { title: '合同管理', permissions: ['pms:contract:list'] }
-      },
-      {
-        path: 'bidding',
-        component: () => import('@/views/pms/bidding'),
-        name: 'PmsBidding',
-        meta: { title: '招投标管理', permissions: ['pms:bidding:list'] }
-      },
-      {
-        path: 'meeting',
-        component: () => import('@/views/pms/meeting'),
-        name: 'PmsMeeting',
-        meta: { title: '会议纪要', permissions: ['pms:meeting:list'] }
-      },
-      {
-        path: 'change',
-        component: () => import('@/views/pms/change'),
-        name: 'PmsChange',
-        meta: { title: '变更管理', permissions: ['pms:change:list'] }
-      },
-      {
-        path: 'phase',
-        component: () => import('@/views/pms/phase'),
-        name: 'PmsPhase',
-        meta: { title: '阶段任务', permissions: ['pms:phase:list'] }
-      },
-      {
-        path: 'document',
-        component: () => import('@/views/pms/document'),
-        name: 'PmsDocument',
-        meta: { title: '文档管理', permissions: ['pms:document:list'] }
-      },
-      {
-        path: 'company',
-        component: () => import('@/views/pms/company'),
-        name: 'PmsCompany',
-        meta: { title: '单位联系人', permissions: ['pms:company:list'] }
-      },
-      {
-        path: 'accessory',
-        component: () => import('@/views/pms/accessory'),
-        name: 'PmsAccessory',
-        meta: { title: '附件管理', permissions: ['pms:accessory:list'] }
-      },
-      {
-        path: 'report',
-        component: () => import('@/views/pms/report'),
-        name: 'PmsReport',
-        meta: { title: '报告管理', permissions: ['pms:report:list'] }
-      },
-      {
-        path: 'addProject',
-        component: () => import('@/views/pms/addProject'),
-        name: 'PmsAddProject',
-        meta: { title: '新建项目' }
-      }
-    ]
-  },
-  {
-    path: '/efinder',
-    component: Layout,
-    redirect: '/efinder/index',
-    name: 'Efinder',
-    meta: { title: '文件管理', icon: 'folder' },
-    children: [
-      {
-        path: 'index',
-        component: () => import('@/views/efinder/index'),
-        name: 'EfinderIndex',
-        meta: { title: '文件浏览', permissions: ['efinder:file:list'] }
-      },
-      {
-        path: 'viewer',
-        component: () => import('@/views/efinder/viewer'),
-        name: 'EfinderViewer',
-        hidden: true,
-        meta: { title: '文件预览', activeMenu: '/efinder/index' }
-      }
-    ]
-  },
+
+  // 隐藏路由(通过代码导航访问,不在导航栏显示)
   {
     path: '/more',
     component: Layout,
+    hidden: true,
     redirect: '/more/tpz',
-    name: 'More',
-    meta: { title: '更多', icon: 'list' },
+    name: 'MoreHidden',
+    meta: { title: '更多' },
     children: [
       {
         path: 'tpz',
@@ -278,24 +177,21 @@ export const businessRoutes = [
         component: () => import('@/views/portal/wyh'),
         name: 'MoreWyh',
         meta: { title: '望亭闸' }
-      },
-      {
-        path: 'djxx',
-        component: () => import('@/views/portal/djxx'),
-        name: 'MoreDjxx',
-        meta: { title: '党建信息', permissions: ['portal:djxx:list'] }
-      },
-      {
-        path: 'jkth',
-        component: () => import('@/views/portal/jkth'),
-        name: 'MoreJkth',
-        meta: { title: '健康太湖' }
-      },
+      }
+    ]
+  },
+  {
+    path: '/efinder',
+    component: Layout,
+    hidden: true,
+    name: 'EfinderHidden',
+    meta: { title: '文件管理' },
+    children: [
       {
-        path: 'docCate',
-        component: () => import('@/views/slgc/docCate'),
-        name: 'MoreDocCate',
-        meta: { title: '文档分类', permissions: ['slgc:docCate:list'] }
+        path: 'viewer',
+        component: () => import('@/views/efinder/viewer'),
+        name: 'EfinderViewer',
+        meta: { title: '文件预览' }
       }
     ]
   }

+ 1 - 1
gw-ui/src/utils/request.js

@@ -89,7 +89,7 @@ service.interceptors.response.use(res => {
         ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
           isRelogin.show = false
           useUserStore().logOut().then(() => {
-            location.href = "/gw/index";
+            location.href = "/slgc/index";
           })
       }).catch(() => {
         isRelogin.show = false

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

@@ -67,6 +67,18 @@
       <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getFileList" />
     </div>
 
+    <el-dialog title="添加分类" v-model="addDialogVisible" width="400px" append-to-body>
+      <el-form ref="addCategoryFormRef" :model="addForm" :rules="addRules" label-width="80px">
+        <el-form-item label="分类名称" prop="name">
+          <el-input v-model="addForm.name" placeholder="请输入分类名称" maxlength="50" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button type="primary" @click="submitAddCategory">立即提交</el-button>
+        <el-button @click="addDialogVisible = false">取消</el-button>
+      </template>
+    </el-dialog>
+
     <el-dialog title="上传文件" v-model="uploadOpen" width="500px" append-to-body>
       <el-upload
         ref="uploadRef"
@@ -94,7 +106,7 @@
 </template>
 
 <script setup name="Ddjc">
-import { folderList, getFileList as apiGetFileList, downloadFile, deleteFile } from "@/api/efinder/finder"
+import { folderList, mkdir, getFileList as apiGetFileList, downloadFile, deleteFile, deleteFolderRecursive } from "@/api/efinder/finder"
 import { getToken } from "@/utils/auth"
 import { parseTime } from "@/utils/goldenwater"
 
@@ -111,6 +123,10 @@ const showSearch = ref(true)
 const ids = ref([])
 const single = ref(true)
 const multiple = ref(true)
+const addDialogVisible = ref(false)
+const addForm = reactive({ name: '' })
+const addRules = { name: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }] }
+
 const uploadOpen = ref(false)
 const uploadRef = ref(null)
 const uploadUrl = ref(import.meta.env.VITE_SERVICE_BASE_TITLE + "/efinder/file/upload")
@@ -173,7 +189,18 @@ function handleSelectionChange(selection) {
 }
 
 function handleAddRecord() {
-  router.push({ path: '/pms/addProject', query: { folderId: 0, zggcPrarms: '0' } })
+  addDialogVisible.value = true
+  addForm.name = ''
+}
+
+async function submitAddCategory() {
+  try {
+    await proxy.$refs.addCategoryFormRef.validate()
+    await mkdir({ name: addForm.name, fdCate: 'ddjc', pid: 0 })
+    proxy.$modal.msgSuccess('添加成功')
+    addDialogVisible.value = false
+    getCategoryTree()
+  } catch (_) {}
 }
 
 function handleUpload() {

+ 29 - 2
gw-ui/src/views/portal/gzzd.vue

@@ -69,6 +69,18 @@
       <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getDocList" />
     </div>
 
+    <el-dialog title="添加分类" v-model="addDialogVisible" width="400px" append-to-body>
+      <el-form ref="addCategoryFormRef" :model="addForm" :rules="addRules" label-width="80px">
+        <el-form-item label="分类名称" prop="name">
+          <el-input v-model="addForm.name" placeholder="请输入分类名称" maxlength="50" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button type="primary" @click="submitAddCategory">立即提交</el-button>
+        <el-button @click="addDialogVisible = false">取消</el-button>
+      </template>
+    </el-dialog>
+
     <el-dialog title="上传文件" v-model="uploadVisible" width="500px" append-to-body>
       <el-upload
         ref="uploadRef"
@@ -99,7 +111,7 @@
 </template>
 
 <script setup name="Gzzd">
-import { getFileList, folderList, downloadFile, deleteFile } from "@/api/efinder/finder"
+import { getFileList, folderList, mkdir, downloadFile, deleteFile } from "@/api/efinder/finder"
 import { getToken } from "@/utils/auth"
 import { parseTime } from "@/utils/goldenwater"
 
@@ -116,6 +128,10 @@ const ids = ref([])
 const single = ref(true)
 const multiple = ref(true)
 
+const addDialogVisible = ref(false)
+const addForm = reactive({ name: '' })
+const addRules = { name: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }] }
+
 const uploadVisible = ref(false)
 const uploadRef = ref(null)
 const uploadUrl = ref(import.meta.env.VITE_SERVICE_BASE_TITLE + "/efinder/file/upload")
@@ -173,7 +189,18 @@ function handleSelectionChange(selection) {
 }
 
 function handleAddCategory() {
-  router.push({ path: '/pms/addProject', query: { folderId: 0, zggcPrarms: '3' } })
+  addDialogVisible.value = true
+  addForm.name = ''
+}
+
+async function submitAddCategory() {
+  try {
+    await proxy.$refs.addCategoryFormRef.validate()
+    await mkdir({ name: addForm.name, fdCate: 'gzzd', pid: 0 })
+    proxy.$modal.msgSuccess('添加成功')
+    addDialogVisible.value = false
+    getCategoryTree()
+  } catch (_) {}
 }
 
 function handleUploadDoc() {

+ 171 - 40
gw-ui/src/views/portal/zdgc.vue

@@ -3,84 +3,215 @@
     <div class="portal-header">
       <h2 class="portal-title">重点工程</h2>
       <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>
       </div>
     </div>
 
-    <el-row :gutter="16">
-      <el-col v-for="item in folderItems" :key="item.id" :xs="24" :sm="12" :md="8" :lg="6" :xl="6" class="mb16">
-        <el-card shadow="hover" class="project-card" @click="handleProjectClick(item)">
-          <div class="card-body">
-            <div class="card-icon">
-              <svg-icon icon-class="folder" />
+    <el-tabs v-model="activeTab" @tab-click="handleTabClick" class="category-tabs">
+      <el-tab-pane v-for="tab in categoryTabs" :key="tab.id" :label="tab.name" :name="String(tab.id)" />
+    </el-tabs>
+
+    <div v-if="activeTab && activeTab !== '-1'" class="tab-content">
+      <el-form :model="queryParams" ref="queryRef" :inline="true" class="search-form">
+        <el-form-item label="工程名称" prop="name">
+          <el-input v-model="queryParams.name" placeholder="请输入工程名称" clearable style="width: 220px" @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+          <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+
+      <el-row :gutter="16">
+        <el-col v-for="item in projectList" :key="item.id" :xs="24" :sm="12" :md="8" :lg="6" :xl="6" class="mb16">
+          <el-card shadow="hover" class="project-card" :style="{ borderTop: '3px solid ' + getPlanColor(item.planValue) }">
+            <div class="card-header-row">
+              <el-checkbox v-model="item._checked" @change="handleCheckChange" @click.stop />
+              <el-button link type="danger" icon="Delete" class="card-delete-btn" @click.stop="handleDelete(item)" />
             </div>
-            <div class="card-info">
-              <div class="card-title">{{ item.name }}</div>
-              <div class="card-meta">
-                <span class="meta-time">{{ parseTime(item.date, '{y}-{m}-{d}') }}</span>
+            <div class="card-body" @click="handleProjectClick(item)">
+              <div class="card-info">
+                <div class="card-title">{{ item.name }}</div>
+                <div class="card-progress">
+                  <el-progress
+                    :percentage="item.planValue || 0"
+                    :color="getPlanColor(item.planValue)"
+                    :stroke-width="10"
+                    :format="(pct) => pct + '%'"
+                  />
+                </div>
               </div>
             </div>
-          </div>
-        </el-card>
-      </el-col>
-    </el-row>
+          </el-card>
+        </el-col>
+      </el-row>
 
-    <el-empty v-if="!loading && folderItems.length === 0" description="暂无工程数据" />
+      <el-empty v-if="!loading && projectList.length === 0" description="暂无工程数据" />
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="fetchProjects" />
+    </div>
 
-    <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getFolderList" />
+    <el-dialog title="添加工程" v-model="addDialogVisible" width="450px" append-to-body>
+      <el-form ref="addFormRef" :model="addForm" :rules="addRules" label-width="100px">
+        <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>
+      </el-form>
+      <template #footer>
+        <el-button type="primary" @click="submitAdd">立即提交</el-button>
+        <el-button @click="resetAddForm">重置</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup name="Zdgc">
-import { folderList } from "@/api/efinder/finder"
-import { parseTime } from "@/utils/goldenwater"
+import { folderList, mkdir, deleteFolderRecursive } from "@/api/efinder/finder"
 
+const { proxy } = getCurrentInstance()
 const router = useRouter()
 
-const folderItems = ref([])
+const categoryTabs = ref([])
+const activeTab = ref('-1')
+const projectList = ref([])
 const total = ref(0)
-const loading = ref(true)
+const loading = ref(false)
+const ids = ref([])
+const multiple = ref(true)
+
+const queryParams = reactive({ pageNum: 1, pageSize: 20, fdCate: 'zdgc', name: undefined, pid: undefined })
+
+const addDialogVisible = ref(false)
+const addFormRef = ref(null)
+const addForm = reactive({ name: '', description: '', planValue: 0 })
+const addRules = { name: [{ required: true, message: '工程名称不能为空', trigger: 'blur' }] }
+
+function getPlanColor(val) {
+  if (!val && val !== 0) return '#909399'
+  if (val >= 100) return '#409eff'
+  if (val >= 90) return '#67c23a'
+  if (val >= 50) return '#e6a23c'
+  return '#f56c6c'
+}
+
+function loadTabs() {
+  folderList({ fdCate: 'zdgc', pageSize: 999 }).then(res => {
+    const list = res.rows || res.data || []
+    const tree = proxy.handleTree(list, 'id', 'pid')
+    categoryTabs.value = tree
+    if (tree.length > 0) {
+      activeTab.value = String(tree[0].id)
+      queryParams.pid = tree[0].id
+      fetchProjects()
+    }
+  })
+}
 
-const data = reactive({
-  queryParams: { pageNum: 1, pageSize: 20, fdCate: "zdgc" }
-})
-const { queryParams } = toRefs(data)
+function handleTabClick() {
+  queryParams.pid = Number(activeTab.value)
+  queryParams.pageNum = 1
+  fetchProjects()
+}
 
-function getFolderList() {
+function fetchProjects() {
+  if (!queryParams.pid) return
   loading.value = true
-  folderList(queryParams.value).then(response => {
-    folderItems.value = response.data || response.rows || []
-    total.value = response.total || folderItems.value.length
+  folderList({ ...queryParams, fdCate: 'zdgc' }).then(res => {
+    projectList.value = (res.rows || res.data || []).map(item => ({ ...item, _checked: false }))
+    total.value = res.total || projectList.value.length
     loading.value = false
   }).catch(() => { loading.value = false })
 }
 
+function handleQuery() {
+  queryParams.pageNum = 1
+  fetchProjects()
+}
+
+function resetQuery() {
+  proxy.resetForm('queryRef')
+  queryParams.pid = Number(activeTab.value)
+  handleQuery()
+}
+
+function handleCheckChange() {
+  ids.value = projectList.value.filter(i => i._checked).map(i => i.id)
+  multiple.value = ids.value.length === 0
+}
+
 function handleProjectClick(item) {
   router.push({ path: '/zdgc/detail/' + item.id })
 }
 
 function handleAddProject() {
-  router.push({ path: '/pms/addProject', query: { folderId: 0, zggcPrarms: '2' } })
+  resetAddForm()
+  addDialogVisible.value = true
 }
 
-function handleQuery() {
-  queryParams.value.pageNum = 1
-  getFolderList()
+function resetAddForm() {
+  addForm.name = ''
+  addForm.description = ''
+  addForm.planValue = 0
+}
+
+function submitAdd() {
+  addFormRef.value.validate(valid => {
+    if (!valid) return
+    mkdir({ name: addForm.name, fdCate: 'zdgc', pid: Number(activeTab.value), planValue: addForm.planValue }).then(res => {
+      if (res.code === 200) {
+        proxy.$modal.msgSuccess('添加成功')
+        addDialogVisible.value = false
+        fetchProjects()
+      }
+    })
+  })
+}
+
+function handleDelete(item) {
+  proxy.$modal.confirm('是否确认删除"' + item.name + '"?').then(() => {
+    return deleteFolderRecursive(item.id)
+  }).then(() => {
+    proxy.$modal.msgSuccess('删除成功')
+    fetchProjects()
+  }).catch(() => {})
+}
+
+async function handleBatchDelete() {
+  if (ids.value.length === 0) {
+    proxy.$modal.msgWarning('请先选择要删除的项目')
+    return
+  }
+  try {
+    await proxy.$modal.confirm('是否确认删除选中的' + ids.value.length + '个项目?')
+    for (const id of ids.value) {
+      await deleteFolderRecursive(id)
+    }
+    proxy.$modal.msgSuccess('删除成功')
+    fetchProjects()
+  } catch (_) {}
 }
 
-onMounted(() => { getFolderList() })
+onMounted(() => { loadTabs() })
 </script>
 
 <style lang="scss" scoped>
-.portal-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
+.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: 12px; }
+.search-form { margin-bottom: 12px; }
 .project-card { cursor: pointer; transition: transform 0.2s; &:hover { transform: translateY(-2px); } }
-.card-body { display: flex; align-items: center; gap: 12px; }
-.card-icon { font-size: 32px; color: var(--el-color-primary); }
-.card-info { flex: 1; min-width: 0; }
-.card-title { font-size: 15px; font-weight: 500; margin-bottom: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.card-meta { display: flex; align-items: center; gap: 8px; font-size: 12px; }
-.meta-time { color: #999; }
+.card-header-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
+.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: 8px; }
+.card-progress { margin-top: 4px; }
 .mb16 { margin-bottom: 16px; }
 </style>

+ 143 - 62
gw-ui/src/views/portal/zggc.vue

@@ -2,20 +2,39 @@
   <div class="app-container">
     <div class="portal-header">
       <h2 class="portal-title">直管工程</h2>
-      <el-button type="primary" icon="Plus" @click="handleAddProject">添加项目</el-button>
+      <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>
+      </div>
     </div>
 
+    <el-form :model="queryParams" ref="queryRef" :inline="true" class="search-form">
+      <el-form-item label="项目名称" prop="name">
+        <el-input v-model="queryParams.name" placeholder="请输入项目名称" clearable style="width: 220px" @keyup.enter="handleQuery" />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
     <el-row :gutter="16">
       <el-col v-for="item in folderListData" :key="item.id" :xs="24" :sm="12" :md="8" :lg="6" :xl="6" class="mb16">
-        <el-card shadow="hover" class="project-card" @click="handleProjectClick(item)">
-          <div class="card-body">
-            <div class="card-icon">
-              <svg-icon icon-class="folder" />
-            </div>
+        <el-card shadow="hover" class="project-card" :style="{ borderTop: '3px solid ' + getPlanColor(item.planValue) }">
+          <div class="card-header-row">
+            <el-checkbox v-model="item._checked" @change="handleCheckChange" @click.stop />
+            <el-button link type="danger" icon="Delete" class="card-delete-btn" @click.stop="handleDelete(item)" />
+          </div>
+          <div class="card-body" @click="handleProjectClick(item)">
             <div class="card-info">
               <div class="card-title">{{ item.name }}</div>
-              <div class="card-meta">
-                <span>创建时间:{{ parseTime(item.date, '{y}-{m}-{d}') }}</span>
+              <div class="card-progress">
+                <el-progress
+                  :percentage="item.planValue || 0"
+                  :color="getPlanColor(item.planValue)"
+                  :stroke-width="10"
+                  :format="(pct) => pct + '%'"
+                />
               </div>
             </div>
           </div>
@@ -26,38 +45,81 @@
     <el-empty v-if="!loading && folderListData.length === 0" description="暂无工程数据" />
 
     <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getFolderList" />
+
+    <el-dialog title="添加项目" v-model="addDialogVisible" width="450px" append-to-body>
+      <el-form ref="addFormRef" :model="addForm" :rules="addRules" label-width="100px">
+        <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>
+      </el-form>
+      <template #footer>
+        <el-button type="primary" @click="submitAdd">立即提交</el-button>
+        <el-button @click="resetAddForm">重置</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup name="Zggc">
-import { folderList } from "@/api/efinder/finder"
-import { parseTime } from "@/utils/goldenwater"
+import { folderList, mkdir, deleteFolderRecursive } from "@/api/efinder/finder"
 
+const { proxy } = getCurrentInstance()
 const router = useRouter()
 
 const folderListData = ref([])
 const total = ref(0)
 const loading = ref(true)
+const ids = ref([])
+const multiple = ref(true)
 
 const data = reactive({
-  queryParams: {
-    pageNum: 1,
-    pageSize: 20,
-    fdCate: "zggc"
-  }
+  queryParams: { pageNum: 1, pageSize: 20, fdCate: "zggc", name: undefined }
 })
-
 const { queryParams } = toRefs(data)
 
+const addDialogVisible = ref(false)
+const addFormRef = ref(null)
+const addForm = reactive({ name: '', description: '', planValue: 0 })
+const addRules = {
+  name: [{ required: true, message: '项目名称不能为空', trigger: 'blur' }]
+}
+
+function getPlanColor(val) {
+  if (!val && val !== 0) return '#909399'
+  if (val >= 100) return '#409eff'
+  if (val >= 90) return '#67c23a'
+  if (val >= 50) return '#e6a23c'
+  return '#f56c6c'
+}
+
 function getFolderList() {
   loading.value = true
   folderList(queryParams.value).then(response => {
-    folderListData.value = response.data || response.rows || []
+    folderListData.value = (response.data || response.rows || []).map(item => ({ ...item, _checked: false }))
     total.value = response.total || folderListData.value.length
     loading.value = false
-  }).catch(() => {
-    loading.value = false
-  })
+  }).catch(() => { loading.value = false })
+}
+
+function handleQuery() {
+  queryParams.value.pageNum = 1
+  getFolderList()
+}
+
+function resetQuery() {
+  proxy.resetForm('queryRef')
+  handleQuery()
+}
+
+function handleCheckChange() {
+  ids.value = folderListData.value.filter(i => i._checked).map(i => i.id)
+  multiple.value = ids.value.length === 0
 }
 
 function handleProjectClick(item) {
@@ -65,12 +127,54 @@ function handleProjectClick(item) {
 }
 
 function handleAddProject() {
-  router.push({ path: '/pms/addProject', query: { folderId: 0, zggcPrarms: '1' } })
+  resetAddForm()
+  addDialogVisible.value = true
 }
 
-onMounted(() => {
-  getFolderList()
-})
+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 => {
+      if (res.code === 200) {
+        proxy.$modal.msgSuccess('添加成功')
+        addDialogVisible.value = false
+        getFolderList()
+      }
+    })
+  })
+}
+
+function handleDelete(item) {
+  proxy.$modal.confirm('是否确认删除"' + item.name + '"?(将同时删除该工程下的所有文件)').then(() => {
+    return deleteFolderRecursive(item.id)
+  }).then(() => {
+    proxy.$modal.msgSuccess('删除成功')
+    getFolderList()
+  }).catch(() => {})
+}
+
+async function handleBatchDelete() {
+  if (ids.value.length === 0) {
+    proxy.$modal.msgWarning('请先选择要删除的项目')
+    return
+  }
+  try {
+    await proxy.$modal.confirm('是否确认删除选中的' + ids.value.length + '个项目?')
+    for (const id of ids.value) {
+      await deleteFolderRecursive(id)
+    }
+    proxy.$modal.msgSuccess('删除成功')
+    getFolderList()
+  } catch (_) {}
+}
+
+onMounted(() => { getFolderList() })
 </script>
 
 <style lang="scss" scoped>
@@ -78,55 +182,32 @@ onMounted(() => {
   display: flex;
   align-items: center;
   justify-content: space-between;
-  margin-bottom: 16px;
-}
-
-.portal-title {
-  font-size: 20px;
-  font-weight: 600;
-  margin: 0;
+  margin-bottom: 12px;
 }
+.portal-title { font-size: 20px; font-weight: 600; margin: 0; }
+.header-actions { display: flex; gap: 8px; }
+.search-form { margin-bottom: 12px; }
 
 .project-card {
   cursor: pointer;
   transition: transform 0.2s;
-
-  &:hover {
-    transform: translateY(-2px);
-  }
+  &:hover { transform: translateY(-2px); }
 }
 
-.card-body {
+.card-header-row {
   display: flex;
   align-items: center;
-  gap: 12px;
-}
-
-.card-icon {
-  font-size: 32px;
-  color: var(--el-color-primary);
-}
-
-.card-info {
-  flex: 1;
-  min-width: 0;
-}
-
-.card-title {
-  font-size: 15px;
-  font-weight: 500;
-  margin-bottom: 4px;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
+  justify-content: space-between;
+  margin-bottom: 8px;
 }
 
-.card-meta {
-  font-size: 12px;
-  color: #999;
+.card-delete-btn {
+  font-size: 14px;
 }
 
-.mb16 {
-  margin-bottom: 16px;
-}
+.card-body { cursor: pointer; }
+.card-info { min-width: 0; }
+.card-title { font-size: 15px; font-weight: 500; margin-bottom: 8px; }
+.card-progress { margin-top: 4px; }
+.mb16 { margin-bottom: 16px; }
 </style>

+ 1 - 1
pom.xml

@@ -10,7 +10,7 @@
 
     <name>goldenwater</name>
     <url>http://www.goldenwater.vip</url>
-    <description>北京金水管理系统</description>
+    <description>水利工程建设项目信息管理系统</description>
 
     <properties>
         <goldenwater.version>3.9.2</goldenwater.version>