Jelajahi Sumber

增加mapbox引擎,地图绘制功能

mork 1 hari lalu
induk
melakukan
4fea4041e3

File diff ditekan karena terlalu besar
+ 2879 - 48
package-lock.json


+ 8 - 0
package.json

@@ -8,20 +8,28 @@
     "preview": "vite preview"
   },
   "dependencies": {
+    "@mapbox/mapbox-gl-draw": "^1.5.1",
+    "@turf/turf": "^7.3.5",
     "@types/markdown-it": "^14.1.2",
     "@types/moment": "^2.11.29",
     "@types/qs": "^6.15.1",
     "axios": "^1.6.8",
     "echarts": "^5.5.0",
     "element-plus": "^2.6.1",
+    "jsoneditor": "^10.4.3",
+    "mapbox-gl": "^3.27.0",
     "markdown-it": "^14.2.0",
     "moment": "^2.30.1",
     "pinia": "^2.1.7",
     "qs": "^6.15.2",
     "vue": "^3.4.21",
+    "vue-clipboard3": "^2.0.0",
     "vue-router": "^4.3.0"
   },
   "devDependencies": {
+    "@types/jsoneditor": "^9.9.6",
+    "@types/mapbox__mapbox-gl-draw": "^1.4.9",
+    "@types/mapbox-gl": "^3.4.1",
     "@types/node": "^20.11.0",
     "@vitejs/plugin-vue": "^5.0.4",
     "typescript": "^5.3.3",

+ 5 - 1
public/static/config/config.js

@@ -19,5 +19,9 @@ window.CONFIG = {
     { id: 6, seq:5, type: 1, text: '登记投标信息', done: false,create_time:'',username:'',userid:''},
     { id: 7, seq:6, type: 1, text: '开标标书监控', done: false,create_time:'',username:'',userid:''},
     { id: 8, seq:7, type: 1, text: '中标确认与部门推送', done: false,create_time:'',username:'',userid:''},
-  ]
+  ],
+  MAPBOX_ACCESS_TOKEN: "pk.eyJ1Ijoid2FuZ3lvbmdjYW4iLCJhIjoiY2phN2l2YW50MHp0NjJxbGx5eTBjOXpmNCJ9.jPxXPXN_CSnBlYG1jp9XJg",
+  MAPBOX_TILES_NORMAL: "https://service-api.onemap.sh.gov.cn/data-service-manage-service/MapProxyApi/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBsaWNhdGlvbl9pZCI6NjEsImFwcGxpY2F0aW9uX25hbWUiOiLpnZLmtabkuozkuInnu7TmnI3liqHns7vnu58iLCJleHAiOjIwNDY2Nzg0MDN9.IKUMdjUX4U1jncIUNren-iotL7duXI90aLECMjpvUX8/shmap_normal_web/MapServer/tile/{z}/{y}/{x}",
+  MAPBOX_TILES_BLUE: "https://service-api.onemap.sh.gov.cn/data-service-manage-service/MapProxyApi/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBsaWNhdGlvbl9pZCI6NjEsImFwcGxpY2F0aW9uX25hbWUiOiLpnZLmtabkuozkuInnu7TmnI3liqHns7vnu58iLCJleHAiOjIwNDY2Nzg0MDN9.IKUMdjUX4U1jncIUNren-iotL7duXI90aLECMjpvUX8/shmap_blue_web/MapServer/tile/{z}/{y}/{x}",
+  MAPBOX_TILES_YXT:"https://t6.tianditu.gov.cn/img_w/wmts?tilematrix={z}&layer=img&style=default&tilerow={y}&tilecol={x}&tilematrixset=w&format=tiles&service=WMTS&version=1.0.0&request=GetTile&tk=f74e6c0cc247c42af05f7053e0b5fb9b",
 }

+ 25 - 85
src/components/bidResult/addContent.vue

@@ -20,7 +20,7 @@
             </el-select>
           </el-form-item>
            <el-form-item label="类似项目名称" prop="title"  v-else>
-              <el-cascader v-model="adminCascader" :options="adminArea" :show-all-levels="true"  @change="handleAdminCascaderChange" style="width: 100%"></el-cascader>
+              <el-cascader v-model="adminCascader" :options="props.areaProject" :show-all-levels="true"  @change="handleAdminCascaderChange" style="width: 100%"></el-cascader>
             </el-form-item>
           <!-- <el-form-item label="项目名称" v-else>
             <el-input v-model="addForm.title" autocomplete="off" placeholder="请输入项目名称"/>
@@ -47,7 +47,7 @@
           <!-- </div> -->
           <div  v-if="addForm.type != '监控中'">
               <el-form-item label="管理区域" prop="c_glqy">
-                <el-cascader ref="areaCascader" v-model="addFormArea" :options="allArea" :show-all-levels="true"  @change="handleNodeAreaClick" style="width: 100%"></el-cascader>
+                <el-cascader ref="areaCascader" v-model="addFormArea" :options="props.glArea" :show-all-levels="true"  @change="handleNodeAreaClick" style="width: 100%"></el-cascader>
               </el-form-item>
               <el-form-item label="投标日期" prop="tb_date">
                 <el-date-picker
@@ -171,7 +171,16 @@ const props = defineProps({
   close: {
     type: Function,
     default: () => {},
+  },
+  glArea: {
+    type: Array,
+    default: () => [],
+  },
+  areaProject: {
+    type: Array,
+    default: () => [],
   }
+
 })
 
 
@@ -186,7 +195,7 @@ const txbList = ref<any[]>([])
 const jkList = ref<any[]>([])
 
 const ytList = ref<resultForm[]>([])
-const glArea = ref([{} as TreeItem])
+
 const addFormZgfw = ref('')
 const addFormFwlx = ref([])
 const addFormYT = ref('')
@@ -202,9 +211,7 @@ interface TreeItem {
 const areaCascader = ref(null)
 const areaObject = ref({})
 const adminCascader = ref([])
-const adminArea = ref([{} as TreeItem])
-const allArea = ref([{} as TreeItem])
-const finishBidOption = ref([{} as resultForm])
+// const finishBidOption = ref([{} as resultForm])
 const jkBidObject = ref<any>(null)
 const txbBidObject = ref<any>(null)
 const finishBidObject = ref<any>(null)
@@ -220,15 +227,12 @@ const initData = async () => {
     await getFinishProjects()
     const itemTxbList = await getTypeProjects(CONFIG.COLUMNIDARR[2]) // 投续标项目
     const itemJkList = await getTypeProjects(CONFIG.COLUMNIDARR[6]) // 中标监控项目
-    const itemFinishList = await getTypeProjects(CONFIG.COLUMNIDARR[7]) // 已中标项目
+    // const itemFinishList = await getTypeProjects(CONFIG.COLUMNIDARR[7]) // 已中标项目
     const itemYtList = await getTypeProjects(CONFIG.COLUMNIDARR[8]) // 业态
-    const itemGLList = await getTypeProjects(CONFIG.COLUMNIDARR[9]) // 管理区域
-    console.log(itemFinishList)
-    finishBidOption.value = itemFinishList.data || []
+    // console.log(itemFinishList)
+    // finishBidOption.value = itemFinishList.data || []
     txbList.value = []
     jkList.value = []
-    glArea.value = []
-    allArea.value = []
     ytList.value = []
     itemTxbList.data.filter((item: any) => {
         const json = JSON.parse(item.c_xmjd_json)
@@ -248,30 +252,7 @@ const initData = async () => {
         value: item.c_yt_name,
         children: item.children || []
     }))
-    // glArea.value = itemGLList.data
-    //     .filter((item: any) => item?.c_part_name && item?.c_district_code?.startsWith('3101')) // 过滤空、null、undefined、''
-    //     .map((item: any) => ({
-    //     label: item.c_part_name,
-    //     value: item.c_part_name,
-    //     children: []
-    // }))
-    // const uniqueGlTree = Array.from(
-    //     new Map(glArea.value.map(item => [item.value, item])).values()
-    // )
-    // glArea.value = uniqueGlTree
-
-    adminArea.value = itemGLList.data.filter((item: any) => item?.c_district_name && item?.c_district_code?.startsWith('310')) // 过滤空、null、undefined、''
-      .map((item: any) => ({
-        label: item.c_district_name,
-        value: item.c_district_name,
-        children: finishTreeProjects.value.filter((item2: any) => item.c_district_name.includes(item2.c_sq))
-      }))
-      const uniqueTree = Array.from(
-        new Map(adminArea.value.map(item => [item.value, item])).values()
-      )
-    adminArea.value = uniqueTree
-    allArea.value = buildAreaTree(itemGLList.data)
-    console.log(itemFinishList)
+
 }
 const dialogFormVisible = props.flag
 const close = () => {
@@ -304,7 +285,7 @@ const buildTree = (data: any[]): any[] => {
     map.set(item.c_guid, {
       ...item,
       value: item.id,
-      label: item.title,
+      label: item.c_xmmc,
       children: []
     })
   })
@@ -356,9 +337,6 @@ const addForm = ref({
 const rules = ref({
   
 })
-// interface ProjectItem {
-//   [key: string]: any
-// }
 const submitForm = (formEl: FormInstance | undefined) => {
   if (!formEl) return
   formEl.validate(async (valid) => {
@@ -480,7 +458,7 @@ const submitForm = (formEl: FormInstance | undefined) => {
           c_type: '中标登记',
           c_enable: false
         }
-        console.log(kbFormData)
+        // console.log(kbFormData)
         const res = await addContentParams(kbFormData,CONFIG.COLUMNIDARR[6],CONFIG.MODELIDARR[3])
         let desc = {
           zb_project_code: selectedObject.value.c_xgzb ? selectedObject.value.c_xgzb : addForm.value.zb_code,
@@ -516,7 +494,7 @@ const submitForm = (formEl: FormInstance | undefined) => {
         updateProject(CONFIG.COLUMNIDARR[2],CONFIG.MODELIDARR[0],JSON.stringify(tbFormData))
         // console.log("其他")
       }else{
-        console.log("其他")
+        // console.log("其他")
          //流程
         const rwlcqd = CONFIG.TASKLIST
         rwlcqd.forEach((p: any) => {
@@ -569,7 +547,7 @@ const submitForm = (formEl: FormInstance | undefined) => {
           c_type: '中标登记',
           c_enable: false
         }
-        console.log(kbFormData)
+        // console.log(kbFormData)
         const res = await addContentParams(kbFormData,CONFIG.COLUMNIDARR[6],CONFIG.MODELIDARR[3])
         let desc = {
           zb_project_code: addForm.value.zb_code,
@@ -645,7 +623,7 @@ const handleProjectChange = async (val: any) => {
   }else{
     selectedObject.value = val
   }
-  console.log("项目名称:", val)
+  // console.log("项目名称:", val)
 }
 const getTypeProjects = async (columnId:number) => {
   const params: any = {
@@ -691,7 +669,7 @@ const updateProject = async (columnId:number,modelId:number,content:string) => {
 
 const handleXBSwitchChange = (val: any) => {
   addForm.value.c_is_xb = val
-  console.log(val)
+  // console.log(val)
 }
 // 获取投续标内容
 const getTxbBidContent = async (columnId:number,search:string,type:string) => {
@@ -715,45 +693,7 @@ const getTxbBidContent = async (columnId:number,search:string,type:string) => {
   const res = await getDmsDataList(params)
   return (res as any).content.data[0]
 }
-const buildAreaTree = (flatList: any[]) => {
-  const districtMap = new Map<string, TreeItem>()
-  const filteredList = flatList.filter(item => item?.c_district_code?.startsWith('3101'))
-  for (const item of filteredList) {
-    const districtCode = item.c_district_code
-    const districtName = item.c_district_name
-    if (!districtCode || !districtName) continue
-    let districtNode = districtMap.get(districtCode)
-    if (!districtNode) {
-      districtNode = {
-        id: districtCode,
-        label: districtName,
-        value: districtCode,
-        children: [],
-        ...item
-      }
-      districtMap.set(districtCode, districtNode as TreeItem)
-    }
-   const partCode = item.c_part_code
-    const partName = item.c_part_name
-    if (!partCode || !partName) continue
-    const partMap = new Map<string, TreeItem>()
-    for (const child of (districtNode?.children || []) as TreeItem[]) {
-      partMap.set(child.value, child)
-    }
-    let partNode = partMap.get(partCode)
-    if (!partNode) {
-      partNode = {
-        id: partCode,
-        label: partName,
-        value: partCode,
-        children: [],
-        ...item
-      }
-      districtNode?.children?.push(partNode as TreeItem)
-    }
-  }
-  return Array.from(districtMap.values())
-}
+
 
 const handleNodeAreaClick = (value: any[])  => {
   console.log('选中的值数组:', value)
@@ -768,7 +708,7 @@ const handleNodeAreaClick = (value: any[])  => {
 const handleAdminCascaderChange = (val: any) => {
   const tempObj = finishTreeProjects.value.filter((item: any) => item.id.includes(val[1]))[0]
   finishObject.value = tempObj
-  console.log(tempObj)
+  // console.log(tempObj)
 }
 </script>
 

+ 148 - 162
src/components/bidResult/addProject.vue

@@ -12,7 +12,7 @@
             </el-form-item>
             <el-form-item label="管理区域" prop="c_glqy">
                 <!-- <el-input v-model="addForm.c_glqy" placeholder="请输入管理区域" /> -->
-                <el-cascader v-model="addFormArea" :options="allArea" :show-all-levels="true"  @change="handleNodeAreaClick" style="width: 100%"></el-cascader>
+                 <el-cascader v-model="addFormArea" :options="props.glArea" :show-all-levels="true"  @change="handleNodeAreaClick" style="width: 100%"></el-cascader>
             </el-form-item>
             <!-- <el-form-item label="市场部名称" >
                 <el-input v-model="addForm.marketName" placeholder="请输入市场部名称" />
@@ -207,12 +207,12 @@
                 placeholder="请输入招标费用(其他)"
                 />
             </el-form-item> -->
-            <el-form-item label="合同面积(平米)" >
+            <el-form-item label="合同面积(平米)" >
                 <el-input-number
                 style="width: 100%"
                 v-model="addForm.htmj"
                 controls-position="right"
-                placeholder="请输入合同面积(平米)"
+                placeholder="请输入合同面积(平米)"
                 />
             </el-form-item>
              <el-form-item label="合同签约甲方" >
@@ -352,8 +352,9 @@
                 <el-checkbox label="保洁"></el-checkbox>
             </el-checkbox-group>
         </el-form-item>
-
-
+        <el-form-item label="geometry" prop="geometry">
+            <el-button class="btn" style="width: 100%" @click="openMapForm" >绘制geometry</el-button>
+        </el-form-item>
 
         </el-form>
         <template #footer>
@@ -366,6 +367,47 @@
         </template>
       </el-dialog>
 
+      <div v-if="mapFormVisible" >
+        <el-dialog
+          v-model="mapFormVisible"
+          title="地图"
+          width="900"
+          draggable
+        >
+          <div style="display: flex;">
+            <div style="flex: 1;">
+              <div style="margin-bottom: 8px;">
+                <el-button @click="drawGeoJson()">开始绘制面</el-button>
+                <el-button @click="clearGeoData()">清空</el-button>
+                <!-- <el-button @click="mapRef?.editMode()">编辑模式</el-button> -->
+              </div>
+              <MapDraw ref="mapRef" :geoJson="geoData" @geoJson-change="onGeoJsonChange"/>
+              <!-- <div v-if="area > 0" style="margin-top: 8px;">
+                当前面积:{{ area }} 平方米
+              </div> -->
+            </div>
+            <div style="width: 300px;">
+              <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
+                <span style="font-weight: bold;">地理数据预览</span>
+                <span>
+                    <!-- <el-button @click="handleSave" :disabled="!geoData">确认修改</el-button> -->
+                    <el-button @click="copyGeoJson" :disabled="!geoData">复制</el-button>
+                </span>
+              </div>
+              <GeoJsonEditor ref="editRef"  :geo-data="geoData" @geoJson-edit="onGeoJsonEdit"/>
+            
+            </div>
+          </div>
+           <template #footer>
+            <div class="dialog-footer">
+                <el-button type="primary" @click="handleMapConfirm">
+                确认
+                </el-button>
+            </div>
+            </template>
+        </el-dialog>
+      </div>
+
       <el-dialog
         v-model="dialogVisible"
         title="提示信息"
@@ -390,14 +432,27 @@
 
 <script setup lang="ts">
 import { ref,onMounted } from 'vue'
-// import moment from 'moment'
+import moment from 'moment'
 import api from '@/api/common'
 import CONFIG from '@/utils/config'
 import type { FormInstance } from 'element-plus'
 // import { ElMessage } from 'element-plus'
+import UTIL from '@/utils/util'
+import MapDraw from '@/components/map/MapDraw.vue'
+import GeoJsonEditor from '@/components/view/GeoJsonEditor.vue'
+
+const mapRef = ref<InstanceType<typeof MapDraw>>()
+// const editRef = ref<InstanceType<typeof GeoJsonEditor>>()
+const geoData = ref<GeoJSON.Feature<GeoJSON.Polygon> | null>(null)
+const area = ref(0)
+
+
 const formRef = ref<FormInstance>()
 const dialogVisible = ref(false)
 
+const soureProjects = ref<any>({})
+const txbProjects = ref<any>({})
+
 // const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}')
 // const userName = userInfo?.username =='user_xtw' ? '徐天炜' : userInfo?.username || ''
 
@@ -413,6 +468,10 @@ const props = defineProps({
   projectData:{
     type : Object,
     default : () => {}
+  },
+  glArea: {
+    type: Array,
+    default: () => [],
   }
 })
 
@@ -420,177 +479,66 @@ const props = defineProps({
 interface resultForm {
   [key: string]: any
 }
+const mapFormVisible = ref(false)
 
+const onGeoJsonChange = (geo: any, num: number) => {
+  geoData.value = geo
+  area.value = num
+//   console.log('多边形geojson', geo)
+}
 
-const txbList = ref<any[]>([])
-const jkList = ref<any[]>([])
-
-const ytList = ref<resultForm[]>([])
-const allArea = ref([{} as TreeItem])
-const addFormZgfw = ref('')
-const addFormFwlx = ref([])
-const addFormYT = ref('')
-const addFormArea = ref([])
 
-interface TreeItem {
-  id: string | number
-  value: string
-  label: string
-  children: TreeItem[],
-  [key: string]: any
+const drawGeoJson = () => {
+    mapRef.value?.startDrawPolygon()
+}
+const clearGeoData = () => {
+    geoData.value = null
+    mapRef.value?.clearDraw()
+}
+const onGeoJsonEdit = (geo: any) => {
+  console.log('geoJson-edit', geo)
+  geoData.value = geo
+  mapRef.value?.getBounds(geo)
 }
 
-const finishBidOption = ref([{} as resultForm])
-// const finishBidObject = ref<any>(null)
-// const finishBidObjectTemp = ref({
-//   flag:false,
-//   url:''
-// })
-const buildAreaTree = (flatList: any[]) => {
-  const districtMap = new Map<string, TreeItem>()
-  const filteredList = flatList.filter(item => item?.c_district_code?.startsWith('3101'))
-  for (const item of filteredList) {
-    const districtCode = item.c_district_code
-    const districtName = item.c_district_name
-    if (!districtCode || !districtName) continue
-    let districtNode = districtMap.get(districtCode)
-    if (!districtNode) {
-      districtNode = {
-        id: districtCode,
-        label: districtName,
-        value: districtCode,
-        children: []
-      }
-      districtMap.set(districtCode, districtNode)
-    }
-   const partCode = item.c_part_code
-    const partName = item.c_part_name
-    if (!partCode || !partName) continue
-    const partMap = new Map<string, TreeItem>()
-    for (const child of districtNode.children) {
-      partMap.set(child.value, child)
-    }
-    let partNode = partMap.get(partCode)
-    if (!partNode) {
-      partNode = {
-        id: partCode,
-        label: partName,
-        value: partCode,
-        children: []
-      }
-      districtNode.children.push(partNode)
-    }
+const copyGeoJson = async () => {
+  if (!geoData.value) return
+  try {
+    const text = JSON.stringify(geoData.value, null, 2);
+    UTIL.copyText(text)
+  } catch (e) {
+    console.error('复制失败', e)
   }
-  return Array.from(districtMap.values())
 }
-// 省市区县管理区域树
-// const buildAreaTree2 = (flatList: any[]) => {
-//   const provinceMap = new Map<string, TreeItem>()
-//   const filteredList = flatList.filter(item => item?.c_province_code?.startsWith('310'))
-//   for (const item of filteredList) {
-//     const provinceCode = item.c_province_code
-//     const provinceName = item.c_province_name
-//     if (!provinceCode || !provinceName) continue
-//     let provinceNode = provinceMap.get(provinceCode)
-//     if (!provinceNode) {
-//       provinceNode = {
-//         id: provinceCode,
-//         label: provinceName,
-//         value: provinceCode,
-//         children: []
-//       }
-//       provinceMap.set(provinceCode, provinceNode)
-//     }
-//     const cityCode = item.c_city_code
-//     const cityName = item.c_city_name
-//     if (!cityCode || !cityName) continue
-//     const cityMap = new Map<string, TreeItem>()
-//     for (const child of provinceNode.children) {
-//       cityMap.set(child.value, child)
-//     }
-//     let cityNode = cityMap.get(cityCode)
-//     if (!cityNode) {
-//       cityNode = {
-//         id: cityCode,
-//         label: cityName,
-//         value: cityCode,
-//         children: []
-//       }
-//       provinceNode.children.push(cityNode)
-//     }
-
-//     const districtCode = item.c_district_code
-//     const districtName = item.c_district_name
-//     if (!districtCode || !districtName) continue
-//     const districtMap = new Map<string, TreeItem>()
-//     for (const child of cityNode.children) {
-//       districtMap.set(child.value, child)
-//     }
-//     let districtNode = districtMap.get(districtCode)
-//     if (!districtNode) {
-//       districtNode = {
-//         id: districtCode,
-//         label: districtName,
-//         value: districtCode,
-//         children: []
-//       }
-//       cityNode.children.push(districtNode)
-//     }
-//     const partCode = item.c_part_code
-//     const partName = item.c_part_name
-//     if (!partCode || !partName) continue
-//     const partMap = new Map<string, TreeItem>()
-//     for (const child of districtNode.children) {
-//       partMap.set(child.value, child)
-//     }
-//     if (!partMap.get(partCode)) {
-//       districtNode.children.push({
-//         id: partCode,
-//         label: partName,
-//         value: partCode,
-//         children: []
-//       })
-//     }
-//   }
-//   return Array.from(provinceMap.values())
-// }
+
+const ytList = ref<resultForm[]>([])
+const addFormZgfw = ref('')
+const addFormFwlx = ref([])
+const addFormYT = ref('')
+const addFormArea = ref(["310118","QDPQ"])
 
 onMounted(() => {
   initData()
 })
 const initData = async () => {
-    const itemTxbList = await getTypeProjects(CONFIG.COLUMNIDARR[2]) // 投续标项目
-    const itemJkList = await getTypeProjects(CONFIG.COLUMNIDARR[6]) // 中标监控项目
-    const itemFinishList = await getTypeProjects(CONFIG.COLUMNIDARR[7]) // 已中标项目
     const itemYtList = await getTypeProjects(CONFIG.COLUMNIDARR[8]) // 业态
-    const itemGLList = await getTypeProjects(CONFIG.COLUMNIDARR[9]) // 管理区域
-    console.log(itemFinishList)
-    finishBidOption.value = itemFinishList.data || []
-    txbList.value = []
-    jkList.value = []
-    allArea.value = []
     ytList.value = []
-    itemTxbList.data.filter((item: any) => {
-        const json = JSON.parse(item.c_xmjd_json)
-        if(json.length > 0 &&  json[json.length - 1].xmjd < 6){
-        txbList.value.push(item)
-        }
-    })
-    itemJkList.data.filter((item: any) => {
-        const flag = item.c_sfzb
-        if(flag == false){// 已中标
-            jkList.value.push(item)
-        }
-    })
     ytList.value = itemYtList.data.map((item: any) => ({
         ...item,
         label: item.c_yt_name,
         value: item.c_yt_name,
         children: item.children || []
     }))
-    allArea.value = buildAreaTree(itemGLList.data)
+    //源项目数据
+    txbProjects.value = await getSoureProjects(CONFIG.COLUMNIDARR[0],props.projectData.c_glxm,"guid")  // 源项目
+    console.log(txbProjects.value)
+
+    //投续标项目数据
+    txbProjects.value = await getSoureProjects(CONFIG.COLUMNIDARR[2],props.projectData.id,"txb")  // 投续标项目
+    console.log(txbProjects.value)
+ 
 }
-const dialogFormVisible = props.flag
+const dialogFormVisible = ref(props.flag)
 const close = () => {
   props.close()
 }
@@ -598,8 +546,8 @@ const close = () => {
 const addForm = ref({
   id: '',
   title: props.projectData.c_xmmc,
-  address: '上海市杨浦区长海路 366 号',
-  c_glqy: '杨浦区/中心区域',
+  address: soureProjects.value.c_xmdz || '',
+  c_glqy: '',
   marketName: '',
   wtnr_zh: '',
   zbrs_zh: null,
@@ -632,8 +580,8 @@ const addForm = ref({
   zbrs_qt: null,
   zbfy_qt: null,
   htmj: null,
-  htqyjf: '上海市杨浦区图书馆',
-  scfwrq: '',
+  htqyjf: soureProjects.value.c_htqyjf || '',
+  scfwrq: soureProjects.value.c_scfwrq || '',  //moment('2019-04-17').format('YYYY-MM-DD'),
   xmyxgsrq: '',
   xmzbggrq: '',
   xmtbrq: '',
@@ -647,6 +595,7 @@ const addForm = ref({
   yshtje: null,
   htzj: null,
   zbdf: null,
+  geometry: '',
 })
 
 const rules = ref({
@@ -665,10 +614,31 @@ const handleConfirm = () => {
   close()
 }
 const handleNodeAreaClick = (item: any) => {
-  console.log(item)
+//   console.log(item)
+  addFormArea.value = item
+  console.log(addFormArea)
 }
 
-
+const getSoureProjects = async (columnId:number,ids: string,type: string) =>{
+  const params: any = {
+    columnId: columnId,
+    states: '0,1,2,3',
+    pageSize: 1,
+    page: 0
+  }
+  if (ids !== "") {
+    params.search = JSON.stringify([
+        ...(params.search ? JSON.parse(params.search) : []),
+        {
+          field: type === "guid" ? "c_guid" : "c_xgjk",
+          searchType: 2,
+          content: { value: `%${ids}%` },
+        },
+    ]);
+  }
+  const res = await getDmsDataList(params)
+  return (res as any).content.data[0]
+}
 
 const getTypeProjects = async (columnId:number) => {
   const params: any = {
@@ -687,6 +657,22 @@ const getDmsDataList = async (params: any) => {
   return await api.getDmsDataList(params)
 }
 
+const openMapForm = () => {
+  mapFormVisible.value = true
+  if(geoData){
+    setTimeout(() =>{
+      mapRef.value?.getBounds(geoData.value)
+    },1000)
+  }
+}
+const handleMapConfirm = () => {
+    console.log('多边形geojson', geoData.value)
+    mapFormVisible.value = false
+}
+
+
+
+
 // const updateProject = async (columnId:number,modelId:number,content:string) => {
 //   const params: any = {
 //     columnId: columnId,

+ 282 - 0
src/components/map/MapDraw.vue

@@ -0,0 +1,282 @@
+<template>
+  <div style="width:100%;height:500px; position: relative;">
+    <div v-if="!isLoaded" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #999; z-index: 10;">
+      地图加载中...
+    </div>
+    <div class="map-container" id="mapContainer" ref="mapContainer" style="width:100%;height:100%;"></div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
+import mapboxgl from 'mapbox-gl'
+import MapboxDraw from '@mapbox/mapbox-gl-draw'
+import CONFIG from '@/utils/config'
+import * as turf from '@turf/turf'
+import 'mapbox-gl/dist/mapbox-gl.css'
+import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css'
+
+const mapContainer = ref("mapContainer")
+let map: mapboxgl.Map | null = null
+let draw: MapboxDraw | null = null
+const isLoaded = ref(false)
+
+// 父组件传入回显面 geojson
+const props = defineProps<{
+  geoJson?: GeoJSON.Feature<GeoJSON.Polygon> | null
+}>()
+const emit = defineEmits<{
+  'geoJson-change': [geo: GeoJSON.Feature<GeoJSON.Polygon> | null, area: number]
+}>()
+
+// 初始化地图
+const initMap = () => {
+  mapboxgl.accessToken = CONFIG.MAPBOX_ACCESS_TOKEN
+  map = new mapboxgl.Map({
+    container: mapContainer.value!,
+    style: {
+      version: 8,
+      sources: {},
+      layers: [],
+      glyphs: "mapbox://fonts/mapbox/{fontstack}/{range}.pbf"
+    },
+    projection: 'equirectangular', // globe 球体投影   mercator 平面墨卡托投影  equirectangular WGS84平面投影
+    center: [121.082334, 31.147052],
+    zoom: 10,
+    minZoom: 9,
+    maxZoom: 18,
+    doubleClickZoom: false,
+  })
+
+  map.on('load', () => {
+    initBaseMap("zwb")
+    initDraw()
+    // 回显多边形
+    if (props.geoJson) {
+        draw!.set({ type: 'FeatureCollection', features: [props.geoJson] })
+    }
+    isLoaded.value = true
+  })
+}
+const initBaseMap = (type:string) => {
+  let titleStyle = CONFIG.MAPBOX_TILES_NORMAL
+  if (type == "zwb") {
+    titleStyle = CONFIG.MAPBOX_TILES_NORMAL
+  } else if (type == "asb") {
+    titleStyle = CONFIG.MAPBOX_TILES_BLUE
+  } else if (type == "yxt") {
+    titleStyle = CONFIG.MAPBOX_TILES_YXT
+  }
+  if (map!.getSource('custom-tiles')) {
+    (map!.getSource('custom-tiles') as mapboxgl.RasterTileSource)?.setTiles([titleStyle])
+  } else {
+    map!.addSource('custom-tiles', {
+      'type': 'raster',
+      'tiles': [titleStyle],
+      'tileSize': 512
+    })
+    map!.addLayer({
+      'id': 'custom-tiles-layer',
+      'type': 'raster',
+      'source': 'custom-tiles',
+      'source-layer': 'custom-tiles',
+      'minzoom': 0,
+      'maxzoom': 20
+    })
+  }
+}
+
+// 初始化绘制控件
+const initDraw = () => {
+  draw = new MapboxDraw({
+    displayControlsDefault: false,
+    controls: {
+      polygon: false,
+      trash: false
+    },
+    styles: [
+      {
+        id: 'draw-polygon',
+        type: 'fill',
+        filter: ['all', ['==', '$type', 'Polygon']],
+        paint: {
+          'fill-color': '#1677ff',
+          'fill-opacity': 0.3
+        }
+      },
+      {
+        id: 'draw-polygon-outline',
+        type: 'line',
+        filter: ['all', ['==', '$type', 'Polygon']],
+        paint: {
+          'line-color': '#1677ff',
+          'line-width': 2
+        }
+      },
+      {
+        id: 'draw-active-points',
+        type: 'circle',
+        filter: ['all', ['==', '$type', 'Point'], ['==', 'meta', 'vertex']],
+        paint: {
+          'circle-radius': 5,
+          'circle-color': '#fff',
+          'circle-stroke-color': '#1677ff',
+          'circle-stroke-width': 2
+        }
+      }
+    ]
+  })
+  map!.addControl(draw)
+
+  // 创建图形
+  map!.on('draw.create', handleGeoChange)
+  // 修改图形
+  map!.on('draw.update', handleGeoChange)
+  // 删除图形
+  map!.on('draw.delete', () => {
+    emit('geoJson-change', null, 0)
+  })
+}
+
+// 处理面数据变更 + 计算面积(平方米)
+const handleGeoChange = () => {
+  const all = draw!.getAll()
+  if (all.features.length === 0) {
+    emit('geoJson-change', null, 0)
+    return
+  }
+  const polygonFeature = all.features[0] as GeoJSON.Feature<GeoJSON.Polygon>
+  // turf计算面积 单位:平方米
+  const area = turf.area(polygonFeature)
+  const result = cleanPolygonGeoJSON(polygonFeature);
+  emit('geoJson-change', result, Math.round(area))
+}
+
+// ========== 对外暴露方法 ==========
+// 手动开启绘制多边形
+const startDrawPolygon = () => {
+  if (!draw) {
+    console.warn('MapDraw: 地图尚未加载完成,请稍后再试')
+    return
+  }
+  draw.changeMode('draw_polygon')
+}
+// 清空绘制
+const clearDraw = () => {
+  if (!draw) {
+    console.warn('MapDraw: 地图尚未加载完成,请稍后再试')
+    return
+  }
+  draw.deleteAll()
+  emit('geoJson-change', null, 0)
+}
+// 切换选择编辑模式
+const editMode = () => {
+  if (!draw) {
+    console.warn('MapDraw: 地图尚未加载完成,请稍后再试')
+    return
+  }
+  draw.changeMode('simple_select')
+}
+const getBounds = (geometry:any) => {
+  if (map && geometry) {
+    const bbox = turf.bbox(geometry)
+    map.fitBounds([[bbox[0], bbox[1]], [bbox[2], bbox[3]]], {
+        padding: {top: 100, bottom:100, left: 100, right: 100}
+    });
+  }
+}
+
+/**
+ * 清理面geojson:移除id + 坐标保留6位小数
+ * @param geojson Feature | FeatureCollection
+ */
+function cleanPolygonGeoJSON(geojson: any) {
+  // 递归处理坐标
+  const formatCoord = (coord: number[]) => {
+    return [
+      Number(coord[0].toFixed(6)),
+      Number(coord[1].toFixed(6))
+    ];
+  };
+
+  // 处理坐标环
+  const processRing = (ring: number[][]) => {
+    return ring.map(p => formatCoord(p));
+  };
+
+  // 处理单个feature
+  const processFeature = (feat: any) => {
+    const newFeat = { ...feat };
+    // 删除id
+    delete newFeat.id;
+
+    const geom = newFeat.geometry;
+    if (!geom) return newFeat;
+
+    if (geom.type === 'Polygon') {
+      geom.coordinates = geom.coordinates.map((ring:any) => processRing(ring));
+    } else if (geom.type === 'MultiPolygon') {
+      geom.coordinates = geom.coordinates.map((polygon:any) =>
+        polygon.map((ring:any) => processRing(ring))
+      );
+    }
+    return newFeat;
+  };
+
+  if (geojson.type === 'FeatureCollection') {
+    return {
+      ...geojson,
+      features: geojson.features.map((f:any) => processFeature(f))
+    };
+  } else if (geojson.type === 'Feature') {
+    return processFeature(geojson);
+  }
+  return geojson;
+}
+
+defineExpose({
+  startDrawPolygon,
+  clearDraw,
+  editMode,
+  isLoaded,
+  getBounds
+})
+
+// 监听外部传入GeoJSON,自动回显
+watch(() => props.geoJson, (val) => {
+  if (!map || !draw) return
+  draw.deleteAll()
+  if (val) draw.set({ type: 'FeatureCollection', features: [val] })
+}, { immediate: true })
+
+
+onMounted(() => {
+  //弹窗打开后,延迟执行resize
+  nextTick(()=>{
+    initMap()
+    // 关键!通知map重新计算大小
+    setTimeout(()=>{
+      map!.resize()
+    },50)
+  })
+})
+onUnmounted(() => {
+  map!.remove()
+})
+</script>
+
+<style scoped>
+:deep(.mapboxgl-ctrl-bottom-left) div{
+  display:none !important;
+}
+.map-container {
+  width: 100%;
+  height: 600px;
+  border: 1px solid #e5e7eb;
+}
+#mapContainer {
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 14 - 12
src/components/renewal/BidRenewal.vue

@@ -67,13 +67,13 @@
               <div>
                 <span>{{ item[columnId+'_c_xmbtqc'] }} </span>
                 <span>  ·  合同时间 {{ moment(item[columnId+'_c_kssj']).format('YYYY-MM-DD') }} 至 {{ moment(item[columnId+'_c_dqsj']).format('YYYY-MM-DD') }}</span>
-                <span>  ·  年化 {{ UTIL.formatCurrency(item[columnId+'_c_nh']) }}</span>
+                <span>  ·  年化 {{ UTIL.formatCurrency(item[columnId+'_c_nh']) }} 万元</span>
                 <span>  ·  {{ item[columnId+'_c_zgfw'] }}  {{ item[columnId+'_c_ytfw'] }}</span>
               </div>
               <div v-if="item.zb_source_name">
                 <span>{{ item.zb_source_name }}  ·  {{ item.zb_project_name }}</span>
                 <span>  ·  招标截止时间 {{ moment(item[columnId+'_zbbjzsj']).format('YYYY-MM-DD') }}</span>
-                <span>  ·  招标金额 {{ UTIL.formatCurrency(item.zb_budget_amount) }}</span>
+                <span>  ·  招标金额 {{ UTIL.formatCurrency(item.zb_budget_amount/10000) }}万元</span>
               </div>
             </div>
           </div>
@@ -631,7 +631,7 @@ const getCountProjects = async (entTime: string) => {
 const calculateRemaining = (st: string, et: string, xmjd: number): { days: string; daysClass: string; year: string; type: string; jdClass: string;} => {
   if (!et) return { days: '', daysClass: '', year: '', type: '', jdClass: jdArr.value[xmjd-1].color}
   const startDate = moment(moment(st).format('YYYY-MM-DD'), 'YYYY-MM-DD')
-  const expireDate = moment(moment(et).format('YYYY-MM-DD'), 'YYYY-MM-DD')
+  const expireDate = moment(moment(et).add(45, 'days').format('YYYY-MM-DD'), 'YYYY-MM-DD')
   
   if (!startDate.isValid() || !expireDate.isValid()) {
     return { days: '', daysClass: '', year: '', type: '', jdClass: jdArr.value[xmjd-1].color}
@@ -651,15 +651,17 @@ const calculateRemaining = (st: string, et: string, xmjd: number): { days: strin
     if (yearNum < 0) yearNum = 0
   }
   if (diffDays < 0) {
-    return { days: `已过期${Math.abs(diffDays)}天`, daysClass: 'danger', year: yearNum.toString(), type: jdArr.value[xmjd-1].label, jdClass: jdArr.value[xmjd-1].color}
+    return { days: `已过期${Math.abs(diffDays)}天`, daysClass: 'grey', year: yearNum.toString(), type: jdArr.value[xmjd-1].label, jdClass: jdArr.value[xmjd-1].color}
   } else if (diffDays === 0) {
-    return { days: '今天到期', daysClass: 'danger', year: yearNum.toString(), type: jdArr.value[xmjd-1].label, jdClass: jdArr.value[xmjd-1].color}
-  } else if (diffDays <= 30) {
-    return { days: `剩余${diffDays}天`, daysClass: 'danger', year: yearNum.toString(), type: jdArr.value[xmjd-1].label, jdClass: jdArr.value[xmjd-1].color}
+    return { days: '今天到期', daysClass: 'grey', year: yearNum.toString(), type: jdArr.value[xmjd-1].label, jdClass: jdArr.value[xmjd-1].color}
+  } else if (diffDays <= 60) {
+    return { days: `剩余${diffDays}天`, daysClass: 'red', year: yearNum.toString(), type: jdArr.value[xmjd-1].label, jdClass: jdArr.value[xmjd-1].color}
   } else if (diffDays <= 90) {
-    return { days: `剩余${diffDays}天`, daysClass: 'warn', year: yearNum.toString(), type: jdArr.value[xmjd-1].label, jdClass: jdArr.value[xmjd-1].color}
-  } else {
-    return { days: `剩余${diffDays}天`, daysClass: 'ok', year: yearNum.toString(), type: jdArr.value[xmjd-1].label, jdClass: jdArr.value[xmjd-1].color}
+    return { days: `剩余${diffDays}天`, daysClass: 'orange', year: yearNum.toString(), type: jdArr.value[xmjd-1].label, jdClass: jdArr.value[xmjd-1].color}
+  } else if (diffDays <= 180) {
+    return { days: `剩余${diffDays}天`, daysClass: 'yellow', year: yearNum.toString(), type: jdArr.value[xmjd-1].label, jdClass: jdArr.value[xmjd-1].color}
+  }else {
+    return { days: `剩余${diffDays}天`, daysClass: 'green', year: yearNum.toString(), type: jdArr.value[xmjd-1].label, jdClass: jdArr.value[xmjd-1].color}
   }
 }
 
@@ -680,7 +682,7 @@ const handleSwitchChange = (val: boolean) => {
   addForm.value.c_glqy = ''
   addForm.value.c_is_xb = val
   addForm.value.rwlcqd = JSON.stringify(CONFIG.TASKLIST)
-  console.log(addForm.value)
+  // console.log(addForm.value)
 }
 
 // const handleNodeClick = (item: any) => {
@@ -840,7 +842,7 @@ const getAdminArea = async () => {
     const uniqueGlTree = Array.from(
       new Map(glArea.value.map(item => [item.value, item])).values()
     )
-    console.log(uniqueGlTree)
+    // console.log(uniqueGlTree)
     adminArea.value = uniqueTree
     glArea.value = uniqueGlTree
     

+ 8 - 6
src/components/renewal/Renew.vue

@@ -186,15 +186,17 @@ const calculateRemaining = async (st: string, et: string, xmjd_json: any): Promi
     if (yearNum < 0) yearNum = 0
   }
   if (diffDays < 0) {
-    return { days: `已过期${Math.abs(diffDays)}天`, daysClass: 'danger', year: yearNum.toString(), type: jdArr.value[xmjd-1].label, jdClass: jdArr.value[xmjd-1].color}
+    return { days: `已过期${Math.abs(diffDays)}天`, daysClass: 'grey', year: yearNum.toString(), type: jdArr.value[xmjd-1].label, jdClass: jdArr.value[xmjd-1].color}
   } else if (diffDays === 0) {
-    return { days: '今天到期', daysClass: 'danger', year: yearNum.toString(), type: jdArr.value[xmjd-1].label, jdClass: jdArr.value[xmjd-1].color}
-  } else if (diffDays <= 30) {
-    return { days: `剩余${diffDays}天`, daysClass: 'danger', year: yearNum.toString(), type: jdArr.value[xmjd-1].label, jdClass: jdArr.value[xmjd-1].color}
+    return { days: '今天到期', daysClass: 'grey', year: yearNum.toString(), type: jdArr.value[xmjd-1].label, jdClass: jdArr.value[xmjd-1].color}
+  } else if (diffDays <= 60) {
+    return { days: `剩余${diffDays}天`, daysClass: 'red', year: yearNum.toString(), type: jdArr.value[xmjd-1].label, jdClass: jdArr.value[xmjd-1].color}
   } else if (diffDays <= 90) {
-    return { days: `剩余${diffDays}天`, daysClass: 'warn', year: yearNum.toString(), type: jdArr.value[xmjd-1].label, jdClass: jdArr.value[xmjd-1].color}
+    return { days: `剩余${diffDays}天`, daysClass: 'orange', year: yearNum.toString(), type: jdArr.value[xmjd-1].label, jdClass: jdArr.value[xmjd-1].color}
+  } else if (diffDays <= 180) {
+    return { days: `剩余${diffDays}天`, daysClass: 'yellow', year: yearNum.toString(), type: jdArr.value[xmjd-1].label, jdClass: jdArr.value[xmjd-1].color}
   } else {
-    return { days: `剩余${diffDays}天`, daysClass: 'ok', year: yearNum.toString(), type: jdArr.value[xmjd-1].label, jdClass: jdArr.value[xmjd-1].color}
+    return { days: `剩余${diffDays}天`, daysClass: 'green', year: yearNum.toString(), type: jdArr.value[xmjd-1].label, jdClass: jdArr.value[xmjd-1].color}
   }
 }
 

+ 10 - 8
src/components/tenderpool/Intention.vue

@@ -178,7 +178,7 @@ const getProjects = async () => {
       ...calculateRemaining(JSON.parse(item.c_content_document).sections[0].blocks[0].rows[0]['预计采购时间(填写到月)'])
     }))
     total.value = (res as any).content.count
-    console.log(dataList.value)
+    // console.log(dataList.value)
   }else{
     dataList.value = []
     total.value = 0
@@ -193,7 +193,7 @@ const calculateRemaining = (et: string): { days: string; daysClass: string; } =>
   if (!et) return { days: '', daysClass: '' }
   // 1. 正则剔除末尾 (北京时间) 多余字符
   const expireDate = moment(moment(et).add(1, 'months').format('YYYY-MM-DD HH:mm'), 'YYYY-MM-DD HH:mm')
-  console.log(expireDate)
+  // console.log(expireDate)
   if (!expireDate.isValid()) {
     return { days: '', daysClass: '' }
   }
@@ -202,15 +202,17 @@ const calculateRemaining = (et: string): { days: string; daysClass: string; } =>
   const diffDays = expireDate.diff(today, 'days')
   
   if (diffDays < 0) {
-    return { days: `${diffDays}`, daysClass: 'danger' }
+    return { days: `${diffDays}`, daysClass: 'grey' }
   } else if (diffDays === 0) {
-    return { days: '0', daysClass: 'danger' }
-  } else if (diffDays <= 30) {
-    return { days: `${diffDays}`, daysClass: 'danger' }
+    return { days: '0', daysClass: 'grey' }
+  } else if (diffDays <= 60) {
+    return { days: `${diffDays}`, daysClass: 'red' }
   } else if (diffDays <= 90) {
-    return { days: `${diffDays}`, daysClass: 'warn' }
+    return { days: `${diffDays}`, daysClass: 'orange' }
+  } else if (diffDays <= 180) {
+    return { days: `${diffDays}`, daysClass: 'yellow' }
   } else {
-    return { days: `${diffDays}`, daysClass: 'ok' }
+    return { days: `${diffDays}`, daysClass: 'green' }
   }
 }
 

+ 13 - 11
src/components/tenderpool/Monitor.vue

@@ -555,14 +555,14 @@ const getXMProjects = async (id: string,type: string) => {
 
 // 标书类型对应 (招标:0;投标:1;续标:2;储备:3;忽略:改成草稿0)
 const handleConfirmClick =(formEl: FormInstance | undefined) => {
-  console.log(dialogName.value)
+  // console.log(dialogName.value)
   if(!formEl){
     if(dialogName.value == "储备"){
       updateProject(project.value,3)
     }else if(dialogName.value == "忽略"){
       updateAudit(project.value,0)
     }else if(dialogName.value == "移出"){
-      console.log(dialogName.value)
+      // console.log(dialogName.value)
       updateProject(project.value,0)
     }
     dialogFormVisible.value = false
@@ -573,7 +573,7 @@ const handleConfirmClick =(formEl: FormInstance | undefined) => {
     if (valid) {
       if(dialogName.value == "投标"){
         getXMProjects(finishObject.value.id,'zbxmbh').then(res => {
-          console.log(res)
+          // console.log(res)
           if(res>0){
             ElMessage.error('此标已投标,不能重复投标')
           }else{
@@ -760,7 +760,7 @@ const getProjects = async () => {
       ...calculateRemainingDays(item.c_publish_time,item.c_bid_deadline)
     }))
     total.value = (res as any).content.count
-    console.log(poolList.value)
+    // console.log(poolList.value)
   }else{
     poolList.value = []
     total.value = 0
@@ -858,15 +858,17 @@ const calculateRemainingDays = (st: string, et: string): { days: string; daysCla
     if (yearNum < 0) yearNum = 0
   }
   if (diffDays < 0) {
-    return { days: `${diffDays}`, daysClass: 'danger', year: yearNum.toString() }
+    return { days: `${diffDays}`, daysClass: 'grey', year: yearNum.toString() }
   } else if (diffDays === 0) {
-    return { days: '0', daysClass: 'danger', year: yearNum.toString() }
-  } else if (diffDays <= 30) {
-    return { days: `${diffDays}`, daysClass: 'danger', year: yearNum.toString() }
+    return { days: '0', daysClass: 'grey', year: yearNum.toString() }
+  } else if (diffDays <= 60) {
+    return { days: `${diffDays}`, daysClass: 'red', year: yearNum.toString() }
   } else if (diffDays <= 90) {
-    return { days: `${diffDays}`, daysClass: 'warn', year: yearNum.toString() }
+    return { days: `${diffDays}`, daysClass: 'orange', year: yearNum.toString() }
+  }  else if (diffDays <= 180) {
+    return { days: `${diffDays}`, daysClass: 'yellow', year: yearNum.toString() }
   } else {
-    return { days: `${diffDays}`, daysClass: 'ok', year: yearNum.toString() }
+    return { days: `${diffDays}`, daysClass: 'green', year: yearNum.toString() }
   }
 }
 
@@ -1044,7 +1046,7 @@ const getAdminArea = () => {
       const uniqueGlTree = Array.from(
         new Map(glArea.value.map(item => [item.value, item])).values()
       )
-      console.log(uniqueGlTree)
+      // console.log(uniqueGlTree)
       adminArea.value = uniqueTree
       glArea.value = uniqueGlTree
     }else{

+ 9 - 7
src/components/tenderpool/Notice.vue

@@ -235,15 +235,17 @@ const calculateRemainingDays = (st: string, et: string): { days: string; daysCla
     if (yearNum < 0) yearNum = 0
   }
   if (diffDays < 0) {
-    return { days: `${diffDays}`, daysClass: 'danger', year: yearNum.toString() }
+    return { days: `${diffDays}`, daysClass: 'grey', year: yearNum.toString() }
   } else if (diffDays === 0) {
-    return { days: '0', daysClass: 'danger', year: yearNum.toString() }
-  } else if (diffDays <= 30) {
-    return { days: `${diffDays}`, daysClass: 'danger', year: yearNum.toString() }
+    return { days: '0', daysClass: 'grey', year: yearNum.toString() }
+  } else if (diffDays <= 60) {
+    return { days: `${diffDays}`, daysClass: 'red', year: yearNum.toString() }
   } else if (diffDays <= 90) {
-    return { days: `${diffDays}`, daysClass: 'warn', year: yearNum.toString() }
-  } else {
-    return { days: `${diffDays}`, daysClass: 'ok', year: yearNum.toString() }
+    return { days: `${diffDays}`, daysClass: 'orange', year: yearNum.toString() }
+  }  else if (diffDays <= 180) {
+    return { days: `${diffDays}`, daysClass: 'yellow', year: yearNum.toString() }
+  }  else {
+    return { days: `${diffDays}`, daysClass: 'green', year: yearNum.toString() }
   }
 }
 

+ 127 - 0
src/components/view/GeoJsonEditor.vue

@@ -0,0 +1,127 @@
+<template>
+  <div>
+    <div ref="container" style="height: 500px; overflow: hidden;"></div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, watch, onMounted, onBeforeUnmount, nextTick } from 'vue'
+import JSONEditor from 'jsoneditor'
+import 'jsoneditor/dist/jsoneditor.min.css'
+import { ElMessage } from 'element-plus'
+
+const props = defineProps<{
+  geoData?: any
+}>()
+const emit = defineEmits(['geoJson-edit'])
+const container = ref<HTMLDivElement>()
+let editor: JSONEditor | null = null
+let pendingData: any = null
+
+const initEditor = () => {
+  if (!container.value) return
+  editor = new JSONEditor(container.value, {
+    mode: 'code',
+    modes: ['code', 'tree', 'view'],
+    mainMenuBar: false,
+    navigationBar: true,
+    statusBar: false,
+    onChange: function () {
+        // 内容发生变化触发(手动输入、拖拽、修改都触发)
+        // const jsonData = editor?.get();
+        // console.log('数据变更:', jsonData);
+        // emit('geoJson-edit', jsonData)
+        // const errors = editor.validate();
+        const errors = editor?.validate() || []
+        Promise.resolve(errors).then((errList) => {
+          if (errList.length === 0) {
+            const jsonData = safeGetEditorData(editor)
+            emit('geoJson-edit', jsonData)
+            console.log(jsonData)
+          } else {
+            // emit('geoJson-edit', null)
+            ElMessage.error("存在语法错误")
+            console.log("存在语法错误", errList)
+          }
+        })
+    }
+  })
+
+  if (pendingData) {
+    setData(pendingData)
+    pendingData = null
+  }
+}
+function safeGetEditorData(editor:any) {
+  if (!editor) return {};
+  try {
+    return editor.get();
+  } catch (err) {
+    // JSON为空、语法错误时,返回默认空对象
+    return null;
+  }
+}
+const setData = (data: any) => {
+  if (!editor) {
+    pendingData = data
+    return
+  }
+  editor.set(data)
+}
+
+// 获取数据
+const getGeoJson = () => {
+  return editor?.get()
+}
+
+defineExpose({
+  getGeoJson
+})
+
+watch(
+  () => props.geoData,
+  (val) => {
+    if(val == null){
+        setData({})
+    }else{
+        setData(val)
+    }
+  },
+  { immediate: true, deep: true }
+)
+
+onMounted(() => {
+  nextTick(() => {
+    initEditor()
+    if (props.geoData) setData(props.geoData)
+  })
+})
+onBeforeUnmount(() => {
+  editor?.destroy()
+})
+</script>
+
+<style scoped>
+/* 深度修改jsoneditor全局样式 */
+:deep(.jsoneditor) {
+  background-color: #f5f5f5 !important;
+}
+:deep(.jsoneditor-menu) {
+  background-color: #ebebeb !important;
+}
+:deep(.jsoneditor-code) {
+  background-color: #f5f5f5 !important;
+}
+:deep(.jsoneditor textarea) {
+  background-color: #f5f5f5 !important;
+  font-size: 12px !important;
+  font-family: Consolas, monospace !important;
+}
+:deep(.jsoneditor-tree) {
+  background-color: #f5f5f5 !important;
+  font-size: 12px !important;
+}
+:deep(.jsoneditor-statusbar) {
+  background-color: #ebebeb !important;
+}
+</style>

+ 28 - 2
src/styles/variables.css

@@ -23,6 +23,15 @@
   --match-soft: #dff0f1;
   --radius: 10px;
 
+  --green: #059669;
+  --green-soft: #e6f6f4;
+  --yellow: #e2a40d;
+  --yellow-soft: #fcf6e1;
+  --orange: #E8590C;
+  --orange-soft: #faece1;
+  --red: #dc2626;
+  --red-soft: #fef2f2;
+
   /* 滚动条整体部分,必须要设置 */
   ::-webkit-scrollbar {
     width: 6px;
@@ -80,13 +89,30 @@
     color: var(--danger);
   }
   .ok {
-    background-color: var(--info-soft);
-    color: var(--info);
+    background-color: var(--green-soft);
+    color: var(--green);
   }
   .grey‌ {
     background-color: var(--grey‌-soft);
     color: var(--grey‌);
   }
+  .green {
+    background-color: var(--green-soft);
+    color: var(--green);
+  }
+  .yellow {
+    background-color: var(--yellow-soft);
+    color: var(--yellow);
+  }
+  .orange {
+    background-color: var(--orange-soft);
+    color: var(--orange);
+  }
+  .red {
+    background-color: var(--red-soft);
+    color: var(--red);
+  }
+  
 
 
   /* 分页控件 */

+ 4 - 0
src/types/window.d.ts

@@ -8,6 +8,10 @@ declare global {
       COLUMNIDARR: number[]
       MODELIDARR: number[]
       TASKLIST:any[]
+      MAPBOX_ACCESS_TOKEN: string
+      MAPBOX_TILES_NORMAL: string
+      MAPBOX_TILES_BLUE: string
+      MAPBOX_TILES_YXT: string
     }
   }
 }

+ 9 - 1
src/utils/config.ts

@@ -8,6 +8,10 @@ declare global {
       COLUMNIDARR: number[]
       MODELIDARR: number[]
       TASKLIST:any[]
+      MAPBOX_ACCESS_TOKEN: string
+      MAPBOX_TILES_NORMAL: string
+      MAPBOX_TILES_BLUE: string
+      MAPBOX_TILES_YXT: string
     }
   }
 }
@@ -19,7 +23,11 @@ const CONFIG = window.CONFIG || {
   SQ_URL: '',
   COLUMNIDARR: [],
   MODELIDARR: [],
-  TASKLIST:[]
+  TASKLIST:[],
+  MAPBOX_ACCESS_TOKEN: '',
+  MAPBOX_TILES_NORMAL: '',
+  MAPBOX_TILES_BLUE: '',
+  MAPBOX_TILES_YXT: '',
 }
 
 export default CONFIG

+ 13 - 1
src/utils/util.ts

@@ -1,3 +1,4 @@
+import { ElMessage } from 'element-plus'
 const formatCurrency = (value: string | number): string => {
   if (!value) return '0.00'
   const num = typeof value === 'string' ? parseFloat(value) : value
@@ -26,8 +27,19 @@ const generateUUID = () =>{
   // 释放内存,防止内存泄漏
   document.body.removeChild(aDom)
 }
+
+// 剪切板
+import useClipboard from 'vue-clipboard3'
+const copyText = function (text:any) {
+  try {
+    useClipboard().toClipboard(text);
+    ElMessage.success({ message: '复制成功', type: 'success' })
+  } catch (e) { }
+}
+
 export default {
   formatCurrency,
   generateUUID,
-  downloadFile
+  downloadFile,
+  copyText
 }

+ 2 - 2
src/views/bidMonitor/TenderPool.vue

@@ -12,7 +12,7 @@
     </div>
     <div class="pool-content">
       <Monitor v-if="activeTab === 'monitor'" :historyProjectData="(historyProjects as any)"></Monitor>
-      <Notice v-if="activeTab === 'notice'"></Notice>
+      <!-- <Notice v-if="activeTab === 'notice'"></Notice> -->
       <Intention v-if="activeTab === 'intention'" :historyProjectData="(historyProjects as any)"></Intention>
     </div>
   </div>
@@ -21,7 +21,7 @@
 <script setup lang="ts">
 import { ref,onMounted } from 'vue'
 import Monitor from '@/components/tenderpool/Monitor.vue'
-import Notice from '@/components/tenderpool/Notice.vue'
+// import Notice from '@/components/tenderpool/Notice.vue'
 import Intention from '@/components/tenderpool/Intention.vue'
 import api from '@/api/common'
 

+ 8 - 6
src/views/bidPreparation/RenewalDetail.vue

@@ -132,15 +132,17 @@ const calculateRemainingDays = (st: string, et: string): { days: string; daysCla
     if (yearNum < 0) yearNum = 0
   }
   if (diffDays < 0) {
-    return { days: `${Math.abs(diffDays)}`, daysClass: 'danger', year: yearNum.toString(), type: '已完成' }
+    return { days: `${Math.abs(diffDays)}`, daysClass: 'grey', year: yearNum.toString(), type: '已完成' }
   } else if (diffDays === 0) {
-    return { days: '0', daysClass: 'danger', year: yearNum.toString(), type: '进行中' }
-  } else if (diffDays <= 30) {
-    return { days: `${diffDays}`, daysClass: 'danger', year: yearNum.toString(), type: '进行中' }
+    return { days: '0', daysClass: 'grey', year: yearNum.toString(), type: '进行中' }
+  } else if (diffDays <= 60) {
+    return { days: `${diffDays}`, daysClass: 'red', year: yearNum.toString(), type: '进行中' }
   } else if (diffDays <= 90) {
-    return { days: `${diffDays}`, daysClass: 'warn', year: yearNum.toString(), type: '监控中' }
+    return { days: `${diffDays}`, daysClass: 'orange', year: yearNum.toString(), type: '监控中' }
+  }  else if (diffDays <= 180) {
+    return { days: `${diffDays}`, daysClass: 'yellow', year: yearNum.toString(), type: '监控中' }
   } else {
-    return { days: `${diffDays}`, daysClass: 'ok', year: yearNum.toString(), type: '监控中' }
+    return { days: `${diffDays}`, daysClass: 'green', year: yearNum.toString(), type: '监控中' }
   }
 }
 </script>

+ 72 - 36
src/views/bidPreparation/RenewalExpireDetail.vue

@@ -6,8 +6,8 @@
         <div class="project-title">{{projects.title}}</div>
         <!-- <div class="project-subtitle">{{ projects.c_xmbtqc }}</div> -->
 
-        <div class="project-meta">{{projects.c_fbz || projects.zbmsxx ?.zb_source_name || ''}} · {{projects.c_zgfw || projects.zbmsxx ?.zb_project_name || ''}} · 金额{{UTIL.formatCurrency(projects.c_nh || '0')}}元</div>
-        <!-- <div class="project-meta">{{projects.c_fbz || projects.zbmsxx ?.zb_source_name || '上海市杨浦区政府采购中心'}} · 杨浦区图书馆总馆物业管理服务项目 · 金额{{UTIL.formatCurrency(projects.c_nh || '0')}}元</div> -->
+        <div class="project-meta">{{projects.c_fbz || projects.zbmsxx ?.zb_source_name || ''}} · {{projects.c_zgfw || projects.zbmsxx ?.zb_project_name || ''}} · 金额{{UTIL.formatCurrency(projects.c_nh || '0')}}元</div>
+        <!-- <div class="project-meta">{{projects.c_fbz || projects.zbmsxx ?.zb_source_name || '上海市杨浦区政府采购中心'}} · 杨浦区图书馆总馆物业管理服务项目 · 金额{{UTIL.formatCurrency(projects.c_nh || '0')}}元</div> -->
 
 
         <!-- <div class="project-subtitle">{{ projects.c_is_xb == true ? projects.c_xmbtqc : projects.zbmsxx ?.zb_project_name || ''}}</div>
@@ -177,10 +177,10 @@
                 </tr>
               </thead>
               <tbody>
-                <tr v-for="item in tablelist" :key="item.id">
+                <tr v-for="item in historyBidList" :key="item.id">
                   <td>{{ item.year }}</td>
-                  <td>{{ item.status }}</td>
-                  <td>{{ item.money }}</td>
+                  <td> 中标</td>
+                  <td>{{ UTIL.formatCurrency(item.money/10000) }} 万元</td>
                   <td class="link-text" >
                     查看
                   </td>
@@ -213,11 +213,11 @@
             </div>
             <div class="info-row">
               <span class="label">合同金额</span>
-              <span class="value">{{UTIL.formatCurrency(projects.c_nh)}}元</span>
+              <span class="value">{{UTIL.formatCurrency(projects.c_nh)}}元</span>
             </div>
             <div class="info-row">
               <span class="label">委托服务</span>
-              <span class="value">{{projects.c_zgfw}}</span>
+              <span class="value">{{wtArr[projects.c_zgfw]}}</span>
             </div>
             <!-- <div class="info-row">
               <span class="label">合同状况</span>
@@ -242,11 +242,11 @@
             <div class="info-row">
               <span class="label">招标起止</span>
               <!-- <span class="value">{{projects.zbProject?.c_publish_time ? moment(projects.zbProject?.c_publish_time).format('YYYY.MM.DD') : '' }}-{{projects.zbProject?.c_bid_deadline ? moment(projects.zbProject?.c_bid_deadline.replace(/\s*\(.*\)$/, '') , 'YYYY年MM月DD日 HH:mm').format('YYYY.MM.DD') : '' }}</span> -->
-               <span class="value">{{moment(projects.zbkssj).format('YYYY.MM.DD') }}-{{moment(projects.zbjzsj).format('YYYY.MM.DD') }}</span>
+               <span class="value">{{projects.zbkssj ? moment(projects.zbkssj).format('YYYY.MM.DD') : '' }}-{{projects.zbjzsj ? moment(projects.zbjzsj).format('YYYY.MM.DD') : '' }}</span>
             </div>
             <div class="info-row">
               <span class="label">招标金额</span>
-              <span class="value">{{UTIL.formatCurrency(projects.zbmsxx ?.zb_budget_amount || 0)}}</span>
+              <span class="value">{{projects.zbmsxx ?.zb_budget_amount ? UTIL.formatCurrency(projects.zbmsxx ?.zb_budget_amount / 10000 || 0) + '万元' : '-'}}</span>
             </div>
             <div class="info-row">
               <span class="label">数据源名称</span>
@@ -661,6 +661,42 @@ const ruleBidForm = reactive<RuleForm>({
     file:[],
   },
 })
+const wtArr = ref<any>([
+  '全委',
+  '单一单委',
+  '多项单委'
+])
+const historyBidList = ref<any>([
+  {
+    year: '2019',
+    money: '8585360'
+  },
+  {
+    year: '2020',
+    money: '8585360'
+  },
+  {
+    year: '2021',
+    money: '8585360'
+  },
+  {
+    year: '2022',
+    money: '8499576'
+  },
+  {
+    year: '2023',
+    money: '8499576'
+  },
+  {
+    year: '2024',
+    money: '8499576'
+  },
+  {
+    year: '2025',
+    money: '8395152'
+  },
+])
+
 const dialogBidFileVisible = ref(false)
 const bidRules = reactive<FormRules<RuleForm>>({
   file1: [
@@ -673,10 +709,7 @@ const bidRules = reactive<FormRules<RuleForm>>({
 
 const steps = ref<any[]>([]);
 const stage = ref(Number(sessionStorage.getItem('progres')) || 0);
-const tablelist = ref([
-  {id:1,year:"2025",status:"中标",money:"620.00万",url:""},
-  {id:2,year:"2024",status:"中标",money:"560.00万",url:""},
-]);
+
 const stepsType = ref('')
 // const swb_status = ref('1')
 // const jsb_status = ref('1')
@@ -808,7 +841,7 @@ const ruleKBJKForm = reactive<RuleForm>({
   c_sfzb:false,
   c_kbsj:'',
   c_xmmc:'',
-  c_zbtjr:moment('2026-06-22 10:00:00').format('YYYY-MM-DD HH:mm:ss'),
+  c_zbtjr:moment('2026-06-29 10:00:00').format('YYYY-MM-DD HH:mm:ss'),
   c_tbbj:'',
   c_glxm:'',
   c_enable: true
@@ -923,14 +956,14 @@ const handleViewClick = (item: any,type: string) => {
       let flag2 = projects.value.rwlcqd.find((p: any) => p.id == '2').done
       if(flag && flag2){
         updateProject()
-        console.log(item)
+        // console.log(item)
         ruleZBJKForm.title = projects.value.title,
         ruleZBJKForm.content = projects.value.content,
         ruleZBJKForm.c_province = projects.value.sourceProject.c_sfzxs,
         ruleZBJKForm.c_city = projects.value.sourceProject.c_sfzxs,
         ruleZBJKForm.c_district = projects.value.sourceProject.c_sq,
         // ruleZBJKForm.c_project_code = projects.value.xmbh
-        ruleZBJKForm.c_title_key_word = projects.value.title,
+        ruleZBJKForm.c_title_key_word = projects.value.c_xmbt,
         ruleZBJKForm.c_publisher_key_word = projects.value.c_xfyt,
         ruleZBJKForm.c_complete = false,
         ruleZBJKForm.c_gl_xm = projects.value.id,
@@ -954,9 +987,9 @@ const handleViewClick = (item: any,type: string) => {
       }
       
     }else if(item.n == 4){
-      console.log(item)
+      // console.log(item)
       if(type == 'cqgg'){
-          console.log("cqgg=====",item)
+          // console.log("cqgg=====",item)
           dialogFormFileVisible.value = true
       }else if(stepsType.value == '1'){
         let flag =  projects.value.rwlcqd.find((p: any) => p.id == '4').done
@@ -1122,7 +1155,7 @@ const getDmsDataList = async (params: any) => {
 const calculateRemaining = async  (st: string, et: string, zbxmbh: string,xmbh: string): Promise<{ days: string; daysClass: string; year: string; type: string, zbProject: any,sourceProject:any }> => {
   if (!et) return { days: '', daysClass: '', year: '', type: '', zbProject: {},sourceProject:{} }
   const startDate = moment(moment(st).format('YYYY-MM-DD'), 'YYYY-MM-DD')
-  const expireDate = moment(moment(et).format('YYYY-MM-DD'), 'YYYY-MM-DD')
+  const expireDate = moment(moment(et).add(45, 'days').format('YYYY-MM-DD'), 'YYYY-MM-DD')
   
   if (!startDate.isValid() || !expireDate.isValid()) {
     return { days: '', daysClass: '', year: '', type: '', zbProject: {},sourceProject:{} }
@@ -1143,15 +1176,17 @@ const calculateRemaining = async  (st: string, et: string, zbxmbh: string,xmbh:
     if (yearNum < 0) yearNum = 0
   }
   if (diffDays < 0) {
-    return { days: `${Math.abs(diffDays)}`, daysClass: 'danger', year: yearNum.toString(), type: '已完成', zbProject:zbProject,sourceProject:sourceProject }
+    return { days: `${Math.abs(diffDays)}`, daysClass: 'grey', year: yearNum.toString(), type: '已完成', zbProject:zbProject,sourceProject:sourceProject }
   } else if (diffDays === 0) {
-    return { days: '0', daysClass: 'danger', year: yearNum.toString(), type: '进行中', zbProject:zbProject,sourceProject:sourceProject }
-  } else if (diffDays <= 30) {
-    return { days: `${diffDays}`, daysClass: 'danger', year: yearNum.toString(), type: '进行中', zbProject:zbProject,sourceProject:sourceProject }
+    return { days: '0', daysClass: 'grey', year: yearNum.toString(), type: '进行中', zbProject:zbProject,sourceProject:sourceProject }
+  } else if (diffDays <= 60) {
+    return { days: `${diffDays}`, daysClass: 'red', year: yearNum.toString(), type: '进行中', zbProject:zbProject,sourceProject:sourceProject }
   } else if (diffDays <= 90) {
-    return { days: `${diffDays}`, daysClass: 'warn', year: yearNum.toString(), type: '监控中', zbProject:zbProject,sourceProject:sourceProject }
+    return { days: `${diffDays}`, daysClass: 'orange', year: yearNum.toString(), type: '监控中', zbProject:zbProject,sourceProject:sourceProject }
+  }  else if (diffDays <= 180) {
+    return { days: `${diffDays}`, daysClass: 'yellow', year: yearNum.toString(), type: '监控中', zbProject:zbProject,sourceProject:sourceProject }
   } else {
-    return { days: `${diffDays}`, daysClass: 'ok', year: yearNum.toString(), type: '监控中', zbProject:zbProject,sourceProject:sourceProject }
+    return { days: `${diffDays}`, daysClass: 'green', year: yearNum.toString(), type: '监控中', zbProject:zbProject,sourceProject:sourceProject }
   }
 }
 
@@ -1275,14 +1310,15 @@ const handleZBSubmit = async (formEl: FormInstance | undefined) => {
           c_detail_url:ruleZBForm.c_detail_url,
         }
         updateZBProject(tempTask.value,formData,'')
-        console.log(projects.value.id)
+        // console.log(projects.value.id)
         getProjectParams(projects.value.id,'c_gl_xm',CONFIG.COLUMNIDARR[5]).then(async res => {
           const reslust = (res as any)[0]
           const content = {  
             id: reslust.id,
+            c_complete: true,
             c_enable: false
           }
-          console.log(content)
+          // console.log(content)
           updateProjectParams(CONFIG.COLUMNIDARR[5],CONFIG.MODELIDARR[2], content)
         })
         
@@ -1320,12 +1356,12 @@ const handleKBJKSubmit = async (formEl: FormInstance | undefined) => {
   await formEl.validate(async (valid) => {
       if (valid) {
         // console.log('submit!')
-        console.log(ruleKBJKForm)
+        // console.log(ruleKBJKForm)
         ruleKBJKForm.c_glxm = projects.value.xmbh
         ruleKBJKForm.c_xmmc = projects.value.c_xmbt
         ruleKBJKForm.c_zbbh = projects.value.zbxmbh
         ruleKBJKForm.c_kbsj = moment(projects.value.zbkssj).format('YYYY-MM-DD HH:mm')
-        ruleKBJKForm.title = projects.value.title
+        ruleKBJKForm.title = projects.value.c_xmbt
         ruleKBJKForm.content = projects.value.content
         ruleKBJKForm.c_enable = true
         // console.log(ruleKBJKForm)
@@ -1353,7 +1389,7 @@ const handleKBJKSubmit = async (formEl: FormInstance | undefined) => {
           tbrq:ruleKBJKForm.c_zbtjr,
           tbbj:ruleKBJKForm.c_tbbj,
         }
-        console.log(constParams)
+        // console.log(constParams)
         updateTXBProject(CONFIG.COLUMNIDARR[2],CONFIG.MODELIDARR[0],constParams)
         dialogKBJKVisible.value = false
       } else {
@@ -1368,7 +1404,7 @@ const handleBidSubmit = async (formEl: FormInstance | undefined) => {
   await formEl.validate((valid) => {
       if (valid) {
         // console.log('submit!')
-        console.log(ruleBidForm)
+        // console.log(ruleBidForm)
         
         updateTypeProject(2)
         ruleBidForm.file3.file.forEach((item: any) => {
@@ -1405,7 +1441,7 @@ const dialogFormFileVisible = ref(false)
 const FileForm = ref<RuleForm>({})
 const fileList = ref([])
 const handleCQFileChange = (file: any) => {
-  console.log(file)
+  // console.log(file)
   FileForm.value.name = file.name
   FileForm.value.file = file
 }
@@ -1419,11 +1455,11 @@ const submitFileForm = (file:any) => {
     type: 3,
     file: file
   }
-  console.log(fileList.value)
+  // console.log(fileList.value)
   api.uploadFile(params).then((res: any) => {
     if((res as any).code === 200){
       ElMessage.success('上传成功')
-      console.log((res as any).content)
+      // console.log((res as any).content)
       const path = (res as any).content
       getProjectParams(projects.value.id,'c_gltxbid',columnId).then(async res2 => {
         let arr = []
@@ -1436,7 +1472,7 @@ const submitFileForm = (file:any) => {
             create_time: moment().format('YYYY-MM-DD HH:mm'),
             author: userName,
           })
-          console.log(arr)
+          // console.log(arr)
           const params: any = {
             columnId: columnId,
             modelId: modelId,
@@ -1589,7 +1625,7 @@ const updateJKLCProject = async () => {
         { n: 4, label: '标书制作'},
         { n: 5, label: '投标提交'},
         { n: 6, label: '开标监控'},
-        { n: 7, label: '确认中标'},
+        { n: 7, label: '开标监控完成,已中标'},
       ]
       let xmjd_json = projects.value.c_xmjd_json
       let json = xmjd_json.map((item:any) => {

+ 5 - 5
src/views/bidPreparation/RenewalXQDetail.vue

@@ -367,7 +367,7 @@ const handleSubmit = async (formEl: FormInstance | undefined) => {
        
       } else {
         dialogVisible.value = false
-        console.log('error submit!')
+        // console.log('error submit!')
       }
   })
 }
@@ -519,13 +519,13 @@ const calculateRemaining = async  (st: string, et: string, xmjd_json: any): Prom
     if (yearNum < 0) yearNum = 0
   }
   const xmjd = xmjd_json[xmjd_json.length-1].xmjd
-  let color = "warn"
+  let color = "orange"
   if(xmjd == "1"){
-    color = "warn"
+    color = "orange"
   }else if(xmjd == "2"){
-    color = "danger"
+    color = "red"
   }else{
-    color = "ok"
+    color = "green"
   }
 
   if (diffDays < 0) {

+ 147 - 31
src/views/bidResult/BidResult.vue

@@ -88,15 +88,15 @@
 
       <div v-else>
          <div style="margin-bottom: 16px;">
-            <el-select v-model="finishObject" placeholder="请选择项目名称" @change="handleFinishProjectChange" value-key="id">
+            <el-select v-model="jkObject" placeholder="请选择项目名称" @change="handleFinishProjectChange" value-key="id">
               <el-option v-for="item in finishProjects" :selected="item.id == selectedBid" :key="item.id" :label="item.c_xmmc" :value="item" />
             </el-select>
         </div>
         <div class="result-banner" :class="{ lost: !showWin }">
           <div>
-            <span style="font-size: large;font-weight: bold;">{{ showWin ? '🎉 中标' : '😔 未中标' }} </span> · <span>{{finishObject.c_xmmc}}</span>
-            <!-- {{finishObject.project?.c_winning_suppliers || '未中标'}} -->
-            <p>{{ showWin ? '恭喜您的标书在本次招标中脱颖而出!' : '请再接再厉,下次一定成功!' }} · 结果发布:{{ finishObject.project?.c_publish_time || '2025-07-11' }} · 中标单位:上海申勤物业管理服务有限公司</p> 
+            <span style="font-size: large;font-weight: bold;">{{ showWin ? '🎉 中标' : '😔 未中标' }} </span> · <span>{{jkObject.c_xmmc}}</span>
+            <!-- {{jkObject.project?.c_winning_suppliers || '未中标'}} -->
+            <p>{{ showWin ? '恭喜您的标书在本次招标中脱颖而出!' : '请再接再厉,下次一定成功!' }} · 结果发布:{{ jkObject.project?.c_publish_time || '2025-07-11' }} · 中标单位:上海申勤物业管理服务有限公司</p> 
           </div>
         </div>
 
@@ -121,7 +121,7 @@
           <div class="card-body markdown-container">
             
             <!-- <div class="markdown-content"  v-html="markdownContent"></div> -->
-           <div class="markdown-content" v-if="finishObject.c_gljg" v-html="markdownContent"></div>
+           <div class="markdown-content" v-if="jkObject.c_gljg" v-html="markdownContent"></div>
            <div class="markdown-content"v-else>人工处理结果:<span style="cursor: pointer;color: var(--primary);"  @click="handleOpenPageClick()">点击查看中标结果详情</span></div>
           </div>
           <div style="padding: 20px;">
@@ -157,18 +157,19 @@
           <div class="card-head">中标流程</div>
           <div class="card-body">
             <ul class="timeline">
-              <li class="done" v-for="item in finishObject.c_lcsjx">{{item.create_time}} · {{item.desc}} · {{item.username}}</li>
+              <li class="done" v-for="item in jkObject.c_lcsjx">{{moment(item.create_time).format('YYYY-MM-DD')}} · {{item.desc}} · {{item.username}}</li>
             </ul>
           </div>
         </div>
       </div>
 
       <div v-if="addFormVisible" >
-        <addContent :flag="addFormVisible" :close="closeAddForm"/>
+        <addContent :flag="addFormVisible" :areaProject="areaProject" :glArea="glArea" :close="closeAddForm"/>
       </div>
       <div v-if="addProjectFormVisible" >
-        <addProject :flag="addProjectFormVisible" :projectData="finishObject" :close="closeAddProjectForm"/>
+        <addProject :flag="addProjectFormVisible" :glArea="glArea" :projectData="jkObject" :close="closeAddProjectForm"/>
       </div>
+      
 
        <el-dialog v-model="dialogFormFileVisible" draggable title="提示信息" width="300">
         <el-input v-model="FileForm.name" autocomplete="off" placeholder="请输入文件名称"/>
@@ -208,8 +209,8 @@ import moment from 'moment'
 import api from '@/api/common'
 import CONFIG from '@/utils/config'
 // import type { FormInstance } from 'element-plus'
-import addContent from '@/components/bidResult/addContent.vue'
-import addProject from '@/components/bidResult/addProject.vue'
+import addContent from '@/components/bidResult/AddContent.vue'
+import addProject from '@/components/bidResult/AddProject.vue'
 import MarkdownIt from 'markdown-it'
 const activeTab = ref('ongoing')
 const selectedBid = ref('')
@@ -221,7 +222,7 @@ const markdownContent = ref('')
 interface jgForm {
   [key: string]: any
 }
-const finishObject = ref<jgForm>({})
+const jkObject = ref<jgForm>({})
 const dialogFormFileVisible = ref(false)
 const FileForm = ref<jgForm>({})
 const fileList = ref([])
@@ -231,8 +232,20 @@ const page = ref(1)
 const total = ref(0)
 const md = ref<MarkdownIt>()
 
+const finishTreeProjects = ref<any[]>([])
+const glArea = ref([{} as TreeItem])
+const areaProject = ref([{} as TreeItem])
+interface TreeItem {
+  id: string | number
+  value: string
+  label: string
+  children: TreeItem[],
+  [key: string]: any
+}
+
 onMounted(() => {
   initData()
+  getFinishProjects()
   md.value = new MarkdownIt({
     html: true,
     linkify: true,
@@ -336,8 +349,8 @@ const getTabName = (stat: { label: string; highlight: boolean }) => {
 const handleTabClick = (tab: string) => {
   activeTab.value = tab
   sessionStorage.setItem('activeTab',tab)
-  if(tab == 'won' && finishObject.value.project){
-    openMD(finishObject.value.project.c_content_html_path || '')
+  if(tab == 'won' && jkObject.value.project){
+    openMD(jkObject.value.project.c_content_html_path || '')
   }
 }
 
@@ -359,10 +372,10 @@ const handleBidRegistration = async () => {
 const handleFinishProjectChange = (val: any) => {
   console.log(val)
   if (val && typeof val === 'object' && val.project) {
-    finishObject.value = val
-    // finishObject.value.c_lcsjx = JSON.parse(val.c_lcsjx || '[]')
-    finishObject.value.c_lcsjx  = val.c_lcsjx || '[]'
-    openMD(finishObject.value.project.c_content_html_path || '')
+    jkObject.value = val
+    // jkObject.value.c_lcsjx = JSON.parse(val.c_lcsjx || '[]')
+    jkObject.value.c_lcsjx  = val.c_lcsjx || '[]'
+    openMD(jkObject.value.project.c_content_html_path || '')
   }
 }
 
@@ -370,13 +383,13 @@ const handleOpenClick = (item: ProjectItem) => {
   if(item.c_sfzb == true){
     sessionStorage.setItem('activeTab', 'won')
     activeTab.value = 'won'
-    finishObject.value = item
-    finishObject.value.c_lcsjx = JSON.parse(item.c_lcsjx || '[]')
+    jkObject.value = item
+    jkObject.value.c_lcsjx = JSON.parse(item.c_lcsjx || '[]')
     selectedBid.value = item.id.toString()
   }
 }
 const handleOpenPageClick = () => {
-  window.open(finishObject.value.c_jgjk_url)
+  window.open(jkObject.value.c_jgjk_url)
 }
 
 const initData = () => {
@@ -456,15 +469,15 @@ const getProjects = async (label:string,type:string) => {
       finishProjects.value = await Promise.all(promises)
       bidStats.value.filter(item => item.label == '已中标')[0].value = result.count
       if(selectedBid.value){
-        finishObject.value = finishProjects.value.find(item => item.id == selectedBid.value) || {}
-        // console.log(finishObject.value)
-        if(finishObject.value.project){
-          openMD(finishObject.value.project.c_content_html_path || '')
+        jkObject.value = finishProjects.value.find(item => item.id == selectedBid.value) || {}
+        // console.log(jkObject.value)
+        if(jkObject.value.project){
+          openMD(jkObject.value.project.c_content_html_path || '')
         }
       }else{
-        finishObject.value = finishProjects.value[0] || {}
+        jkObject.value = finishProjects.value[0] || {}
       }
-      // console.log(finishObject.value)
+      // console.log(jkObject.value)
     }
   }else{
     if(label == ''){
@@ -526,8 +539,8 @@ const calculateRemainingDays = (st: string, et: string): { days: string;} => {
  
 }
 const handleConfirmBidPush = async () => {
-  // console.log(finishObject.value)
-  // let arr = finishObject.value.c_lcsjx ? finishObject.value.c_lcsjx : []
+  // console.log(jkObject.value)
+  // let arr = jkObject.value.c_lcsjx ? jkObject.value.c_lcsjx : []
   // arr.push({
   //   create_time: moment().format('YYYY-MM-DD HH:mm'),
   //   desc:'市场部确认',
@@ -539,8 +552,8 @@ const handleConfirmBidPush = async () => {
   //   username:userName,
   //   userid:userInfo?.id || ''
   // })
-  // updateAudit(finishObject.value,4)
-  // updateKBJKProject(finishObject.value,arr)
+  // updateAudit(jkObject.value,4)
+  // updateKBJKProject(jkObject.value,arr)
   // console.log(arr)
   addProjectFormVisible.value = true
 }
@@ -581,7 +594,7 @@ const handleConfirmBidPush = async () => {
 // }
 
 const handleFileChange = (file: any) => {
-  console.log(file)
+  // console.log(file)
   FileForm.value = file
 }
 const submitFileForm = async () => {
@@ -591,6 +604,109 @@ const openFileForm = (type: string) => {
   FileForm.value.type = type
   dialogFormFileVisible.value = true
 }
+const getFinishProjects = async () => {
+  const params: any = {
+    columnId: CONFIG.COLUMNIDARR[0], //原项目数据
+    orderBy: JSON.stringify([{ field: "c_zgxmhtdqrq", orderByType: 1 }]),
+    states: '0',// 只看草稿箱
+    pageSize: 9999,
+    page: 0
+  }
+  await api.getDmsDataList(params).then(res => {
+    if ((res as any).code === 200) {
+      let finishProjects = (res as any).content.data
+      finishTreeProjects.value = buildTree(finishProjects)
+      getGLXQData()
+    }
+  })
+}
+const getGLXQData = async () => {
+  const itemList = await getTypeProjects(CONFIG.COLUMNIDARR[9]) // 管理区域
+  glArea.value = buildAreaTree(itemList.data || [])
+
+  areaProject.value = itemList.data.filter((item: any) => item?.c_district_name && item?.c_district_code?.startsWith('310')) // 过滤空、null、undefined、''
+      .map((item: any) => ({
+        label: item.c_district_name,
+        value: item.c_district_name,
+        children: finishTreeProjects.value.filter((item2: any) => item.c_district_name.includes(item2.c_sq))
+      }))
+      const uniqueTree = Array.from(
+        new Map(areaProject.value.map(item => [item.value, item])).values()
+      )
+    areaProject.value = uniqueTree
+}
+
+const getTypeProjects = async (columnId:number) => {
+  const params: any = {
+    columnId: columnId,
+    orderBy: JSON.stringify([{ field: "update_time", orderByType: 2 }]),
+    states: '0,1,2,3,4',
+    pageSize: 9999,
+    page: 0
+  }
+  const res = await getDmsDataList(params)
+  return (res as any).content
+}
+const buildTree = (data: any[]): any[] => {
+  const map = new Map()
+  const tree: any[] = []
+
+  data.forEach((item: any) => {
+    map.set(item.c_guid, {
+      ...item,
+      value: item.id,
+      label: item.c_xmmc,
+      children: []
+    })
+  })
+
+  data.forEach((item: any) => {
+    const node = map.get(item.c_guid)
+    if (item.c_father_code && map.has(item.c_father_code)) {
+      // map.get(item.c_father_code).children.push(node)
+    } else {
+      tree.push(node)
+    }
+  })
+  return tree
+}
+const buildAreaTree = (list: any[]) => {
+  const districtMap = new Map<string, TreeItem>()
+  const filteredList = list.filter(item => item?.c_district_code?.startsWith('3101'))
+  for (const item of filteredList) {
+    const districtCode = item.c_district_code
+    const districtName = item.c_district_name
+    if (!districtCode || !districtName) continue
+    let districtNode = districtMap.get(districtCode)
+    if (!districtNode) {
+      districtNode = {
+        id: districtCode,
+        label: districtName,
+        value: districtCode,
+        children: []
+      }
+      districtMap.set(districtCode, districtNode)
+    }
+   const partCode = item.c_part_code
+    const partName = item.c_part_name
+    if (!partCode || !partName) continue
+    const partMap = new Map<string, TreeItem>()
+    for (const child of districtNode.children) {
+      partMap.set(child.value, child)
+    }
+    let partNode = partMap.get(partCode)
+    if (!partNode) {
+      partNode = {
+        id: partCode,
+        label: partName,
+        value: partCode,
+        children: []
+      }
+      districtNode.children.push(partNode)
+    }
+  }
+  return Array.from(districtMap.values())
+}
 
 </script>
 

+ 41 - 18
src/views/home/Dashboard.vue

@@ -72,9 +72,9 @@
             </tr>
           </thead>
           <tbody>
-            <tr v-for="item in projects" :key="item.title">
+            <tr v-for="item in projects" :key="item.c_xmmc">
               <td>
-                <strong style="cursor: pointer;" @click="handleViewClick(item)">{{ item.title }}</strong>
+                <strong style="cursor: pointer;" @click="handleViewClick(item)">{{ item.c_xmmc }}</strong>
                 <div style="font-size:11px;color:var(--text3);cursor: pointer;" @click="handleViewClick(item)">{{ item.c_xmdz }}</div>
                 </td>
               <td>{{ item.c_hzyt }}</td>
@@ -111,11 +111,11 @@ import api from '@/api/common'
 import CONFIG from '@/utils/config'
 
 const stats = ref([
-  { label: '在管项目', desc: '本年新增0个',count: '0', warn: false },
-  { label: '在岗人员', desc: '',count: '0', warn: false },
-  { label: '年化收入', desc: '',count: '0', warn: false },
-  { label: '平均毛利率', desc: '',count: '0%', warn: false },
-  { label: '待续约', desc: '3个月内到期',count: '0', warn: true },
+  { label: '在管项目', desc: '本年新增77个',count: '213', warn: false },
+  { label: '在岗人员', desc: '',count: '2808', warn: false },
+  { label: '年化收入', desc: '',count: '2.86亿', warn: false },
+  { label: '平均毛利率', desc: '',count: '27.78%', warn: false },
+  { label: '待续约', desc: '3个月内到期',count: '72', warn: true },
   { label: '招标信息', desc: '',count: '0', warn: true }
 ])
 
@@ -130,11 +130,11 @@ interface TodoItem {
 }
 
 const todoList = ref<TodoItem[]>([
-  { title: 'XX项目续标方案确认', create_time: '2026-06-15 14:00:00', urgent: true },
-  { title: '招标文件分析报告', create_time: '2026-06-15 16:00:00', urgent: false },
-  { title: '投标文件审核', create_time: '2026-06-16 09:00:00', urgent: false },
-  { title: '项目进度汇报', create_time: '2026-06-16 14:00:00', urgent: false },
-  { title: '合同续签谈判', create_time: '2026-06-20 10:00:00', urgent: true }
+  { title: '合同续签谈判', create_time: '2026-06-07 10:00:00', urgent: false },
+  { title: '项目进度汇报', create_time: '2026-06-06 14:00:00', urgent: false },
+  { title: '投标文件审核', create_time: '2026-06-06 09:00:00', urgent: false },
+  { title: '招标文件分析报告', create_time: '2026-06-05 16:00:00', urgent: false },
+  { title: '项目续标方案确认', create_time: '2026-06-05 14:00:00', urgent: false }
 ])
 
 const quickLinks = ref([
@@ -147,6 +147,7 @@ const quickLinks = ref([
 
 interface ProjectItem {
   title: string
+  c_xmmc: string
   c_xmdz: string
   c_hzyt: string
   c_glqynbkj: string
@@ -174,6 +175,12 @@ const initData = () => {
   getProjects();
 }
 const getBasicCount = async () => {
+  //测试数据
+  // setTimeout(() => {
+  //     stats.value.forEach((item) => {
+  //       animateNumber(item.label, item.count)
+  //     })
+  //   }, 100)
   const res = await api.getBasicCount()
   if ((res as any).code === 200) {
     const content = (res as any).content
@@ -255,13 +262,27 @@ const getProjects = async () => {
       ...item,
       ...calculateRemaining(item.c_zgxmhtksrq, item.c_zgxmhtdqrq)
     }))
+
+    //测试用
+    // let testArr = [
+    //   { title: '群艺馆', c_xmmc: '群艺馆',c_xmdz: '徐汇区古宜路125号(中山西路1551号)',c_hzyt: '公众物业',c_glqynbkj: '中心区域',c_zgxmhtksrq: '2026-03-09',c_zgxmhtdqrq: '2029-03-08'},
+    //   { title: '区政府', c_xmmc: '区政府',c_xmdz: '松江区园中路1号',c_hzyt: '机关办公楼',c_glqynbkj: '松江区域',c_zgxmhtksrq: '2026-01-01',c_zgxmhtdqrq: '2027-12-31'},
+    //   { title: '体育局', c_xmmc: '体育局',c_xmdz: '松江区九峰路2号',c_hzyt: '机关办公楼',c_glqynbkj: '松江区域',c_zgxmhtksrq: '2025-07-01',c_zgxmhtdqrq: '2027-06-30'},
+    //   { title: '红房子医院', c_xmmc: '红房子医院',c_xmdz: '青浦区浦泰路1003号',c_hzyt: '医院物业',c_glqynbkj: '青西片区',c_zgxmhtksrq: '2026-06-01',c_zgxmhtdqrq: '2027-05-31'},
+    //   { title: '华师大国际软件院', c_xmmc: '华师大国际软件院',c_xmdz: '上海市临港新片区楠木路111号',c_hzyt: '字校物业',c_glqynbkj: '中心区域',c_zgxmhtksrq: '2026-06-01',c_zgxmhtdqrq: '2027-05-31'},
+    //  ]
+    // projects.value = testArr.map((item) => ({
+    //   ...item,
+    //   ...calculateRemaining(item.c_zgxmhtksrq, item.c_zgxmhtdqrq)
+    // }))
+
     // console.log(projects.value)
   }
 }
 const calculateRemaining = (st: string, et: string): { days: string; daysClass: string; statusClass: string; statusText: string } => {
   if (!et) return { days: '', daysClass: '', statusClass: '', statusText: ''}
   const startDate = moment(moment(st).format('YYYY-MM-DD'), 'YYYY-MM-DD')
-  const expireDate = moment(moment(et).format('YYYY-MM-DD'), 'YYYY-MM-DD')
+  const expireDate = moment(moment(et).add(45, 'days').format('YYYY-MM-DD'), 'YYYY-MM-DD')
   
   if (!startDate.isValid() || !expireDate.isValid()) {
     return { days: '', daysClass: '', statusClass: '', statusText: ''}
@@ -274,12 +295,14 @@ const calculateRemaining = (st: string, et: string): { days: string; daysClass:
     return { days: `${Math.abs(diffDays)}`, daysClass: 'grey', statusClass: 'grey', statusText: '已过期'}
   } else if (diffDays === 0) {
     return { days: `${diffDays}`, daysClass: 'grey', statusClass: 'grey', statusText: '到期'}
-  } else if (diffDays <= 30) {
-    return { days: `${diffDays}`, daysClass: 'danger', statusClass: 'danger', statusText: '续标预警'}
+  } else if (diffDays <= 60) {
+    return { days: `${diffDays}`, daysClass: 'red', statusClass: 'danger', statusText: '续标预警'}
   } else if (diffDays <= 90) {
-    return { days: `${diffDays}`, daysClass: 'warn', statusClass: 'warn', statusText: '续标预警'}
-  } else {
-    return { days: `${diffDays}`, daysClass: 'ok', statusClass: 'ok', statusText: '在管'}
+    return { days: `${diffDays}`, daysClass: 'orange', statusClass: 'orange', statusText: '续标预警'}
+  } else if (diffDays <= 180) {
+    return { days: `${diffDays}`, daysClass: 'yellow', statusClass: 'green', statusText: '在管'}
+  }else {
+    return { days: `${diffDays}`, daysClass: 'green', statusClass: 'green', statusText: '在管'}
   }
 }
 

+ 77 - 21
src/views/ledger/ProjectDetail.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="project-page">
     <!-- 顶部续约预警栏 -->
-    <div :class="projects.days >= 90 ? 'warning-green' : projects.days >= 30 ? 'warning-yellow' : projects.days >= 0 ? 'warning-red' : 'warning-gray'">
+    <div :class="projects.days >= 180 ? 'warning-green' : projects.days >= 90 ? 'warning-yellow' : projects.days >= 60 ? 'warning-orange' : projects.days >= 0 ? 'warning-red' : 'warning-gray'">
       <div class="warning-info">
         <div class="warning-text"> <strong v-if="projects.days >0" class="warning-danger">续标预警</strong><strong v-else>已过期</strong> · 合同起止 {{ moment(projects.c_zgxmhtksrq).format('YYYY-MM-DD') }} 至 {{ moment(projects.c_zgxmhtdqrq).format('YYYY-MM-DD') }},距本期<span v-if="projects.days >0">到期剩</span><span v-else>已过期</span> <strong class="warning-danger">{{ projects.days }}</strong> 天</div>
         <div class="warning-text-detail">服务期限 {{ moment(projects.c_zgxmhtksrq).format('YYYY-MM-DD') }} 至 {{ moment(projects.c_zgxmhtdqrq).format('YYYY-MM-DD') }},{{ projects.year }} 年服务 · 请进入项目续标跟进全流程。</div>
@@ -87,11 +87,11 @@
           </div>
           <div class="info-row">
             <span class="label">委托服务</span>
-            <span class="value">{{ projects.c_wtfs }}</span>
+            <span class="value">{{ wtArr[projects.c_wtfs] }}</span>
           </div>
           <div class="info-row">
-            <span class="label">服务方式</span>
-            <span class="value">{{ projects.c_jffs }}</span>
+            <span class="label">计费方式</span>
+            <span class="value">{{ jfArr[projects.c_jffs] }}</span>
           </div>
          
         </div>
@@ -105,27 +105,27 @@
         <div class="card-body">
           <div class="info-row">
             <span class="label">合同金额</span>
-            <span class="value">{{ formatCurrency(projects.c_htzj) }} 元</span>
+            <span class="value">{{ formatCurrency(projects.c_htzj) }} 元</span>
           </div>
           <div class="info-row">
             <span class="label">年化合同额</span>
-            <span class="value">{{ formatCurrency(projects.c_nhhtsr) }} 元</span>
+            <span class="value">{{ formatCurrency(projects.c_nhhtsr) }} 元</span>
           </div>
           <div class="info-row">
             <span class="label">当年饱和收入</span>
-            <span class="value">{{ formatCurrency(projects.c_dnbhsr) }} 元</span>
+            <span class="value">{{ formatCurrency(projects.c_dnbhsr) }} 元</span>
           </div>
           <div class="info-row">
             <span class="label">投标核算年度总成本</span>
-            <span class="value">{{ formatCurrency(projects.c_tbhsndzcb) }} 元</span>
+            <span class="value">{{ formatCurrency(projects.c_tbhsndzcb) }} 元</span>
           </div>
           <div class="info-row">
             <span class="label">自用人力总成本</span>
-            <span class="value">{{ formatCurrency(projects.c_zyrlzcbyss) }} 元</span>
+            <span class="value">{{ formatCurrency(projects.c_zyrlzcbjss) }} 万元</span>
           </div>
           <div class="info-row">
             <span class="label">综合成本</span>
-            <span class="value">{{ formatCurrency(projects.c_zhcbyss) }} 元</span>
+            <span class="value">{{ formatCurrency(projects.c_zhcbjss) }} 万元</span>
           </div>
         </div>
       </div>
@@ -136,7 +136,11 @@
           <span class="card-title">历史投标</span>
         </div>
         <div class="card-body">
-          <div class="info-row">
+          <div class="info-row"  v-for="item in historyBidList" :key="item.year">
+            <span class="label">{{ item.year }}年</span>
+            <span class="value">中标 {{ formatCurrency(item.money/10000) }} 万元</span>
+          </div>
+          <!-- <div class="info-row">
             <span class="label">2024年</span>
             <span class="value">中标 6,200,000 元</span>
           </div>
@@ -147,7 +151,7 @@
           <div class="info-row">
             <span class="label">2020年</span>
             <span class="value">中标 3,680,000 元</span>
-          </div>
+          </div> -->
         </div>
       </div>
     </div>
@@ -189,6 +193,47 @@ const formatCurrency = (value: string | number): string => {
 const projects = ref<any>({})
 const tempArr = ref<any>([])
 
+const wtArr = ref<any>([
+  '全委',
+  '单一单委',
+  '多项单委'
+])
+const jfArr = ref<any>([
+  '包干',
+  '酬金'
+])
+
+const historyBidList = ref<any>([
+  {
+    year: '2019',
+    money: '8585360'
+  },
+  {
+    year: '2020',
+    money: '8585360'
+  },
+  {
+    year: '2021',
+    money: '8585360'
+  },
+  {
+    year: '2022',
+    money: '8499576'
+  },
+  {
+    year: '2023',
+    money: '8499576'
+  },
+  {
+    year: '2024',
+    money: '8499576'
+  },
+  {
+    year: '2025',
+    money: '8395152'
+  },
+])
+
 onMounted(() => {
   initData()
 })
@@ -228,7 +273,7 @@ const calculateRemaining = (st: string, et: string): { days: string; daysClass:
   if (!et) return { days: '', daysClass: '', year: '' }
   
   const startDate = moment(moment(st).format('YYYY-MM-DD'), 'YYYY-MM-DD')
-  const expireDate = moment(moment(et).format('YYYY-MM-DD'), 'YYYY-MM-DD')
+  const expireDate = moment(moment(et).add(45, 'days').format('YYYY-MM-DD'), 'YYYY-MM-DD')
   
   if (!startDate.isValid() || !expireDate.isValid()) {
     return { days: '', daysClass: '', year: '' }
@@ -280,7 +325,7 @@ const handleRenewalClick = () => {
       // console.log(res)
       if(res.length>0){  // 直接进入投续标详情页面
         sessionStorage.setItem('projectName', projects.value.c_xmmc)
-        sessionStorage.setItem('name', projects.value.title)
+        sessionStorage.setItem('name', projects.value.c_xmmc)
         sessionStorage.setItem('dmsId', res[0].id)
         sessionStorage.setItem('progres', JSON.parse(res[0].c_xmjd_json)[JSON.parse(res[0].c_xmjd_json).length-1].xmjd)
         router.push(`/renewal-expire-detail/${res[0].id}`)
@@ -289,7 +334,7 @@ const handleRenewalClick = () => {
         // console.log("新增项目id:",ids)
         if(ids){
           sessionStorage.setItem('projectName', projects.value.c_xmmc)
-          sessionStorage.setItem('name', projects.value.title)
+          sessionStorage.setItem('name', projects.value.c_xmmc)
           sessionStorage.setItem('dmsId', `${ids}`)
           sessionStorage.setItem('progres', tempArr.value[tempArr.value.length-1].xmjd)
           router.push(`/renewal-expire-detail/${ids}`)
@@ -304,7 +349,7 @@ const handleRenewaXQlClick = () => {
       // console.log(res)
       if(res.length>0){  // 直接进入投续标详情页面
         sessionStorage.setItem('projectName', projects.value.c_xmmc)
-        sessionStorage.setItem('name', projects.value.title)
+        sessionStorage.setItem('name', projects.value.c_xmmc)
         sessionStorage.setItem('dmsId', res[0].id)
         sessionStorage.setItem('progres', JSON.parse(res[0].c_xmjd_json)[JSON.parse(res[0].c_xmjd_json).length-1].xmjd)
         router.push(`/renewal-xq-detail/${res[0].id}`)
@@ -312,7 +357,7 @@ const handleRenewaXQlClick = () => {
         const ids = await addXQProject()
         if(ids){
           sessionStorage.setItem('projectName', projects.value.c_xmmc)
-          sessionStorage.setItem('name', projects.value.title)
+          sessionStorage.setItem('name', projects.value.c_xmmc)
           sessionStorage.setItem('dmsId', `${ids}`)
           sessionStorage.setItem('progres', tempArr.value[tempArr.value.length-1].xmjd)
           router.push(`/renewal-xq-detail/${ids}`)
@@ -375,7 +420,7 @@ const addProject = async () => {
     }
     let formData = {
         //项目
-        title: projects.value.title,
+        title: projects.value.c_xmmc,
         content: projects.value.content,
         c_fbz: projects.value.c_htqyjf,
         // c_jdbm: projects.value.c_jdbm,
@@ -420,7 +465,7 @@ const addXQProject = async () => {
     }
     tempArr.value.push(str)
     let formData = {
-        title: projects.value.title,
+        title: projects.value.c_xmmc,
         content: projects.value.content,
         c_wtfs: projects.value.c_wtfs,
         c_htje: projects.value.c_nhhtsr,
@@ -498,12 +543,23 @@ const addXQProject = async () => {
     margin-bottom: 16px;
 }
 .warning-yellow {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    background: linear-gradient(90deg, #f8f5ec, #fff);
+    border: 1px solid #fae8ae;    
+    border-left: 4px solid var(--yellow);
+    padding: 14px 16px;
+    border-radius: 10px;
+    margin-bottom: 16px;
+}
+.warning-orange {
     display: flex;
     justify-content: space-between;
     align-items: center;
     background: linear-gradient(90deg, #f8f2ec, #fff);
-    border: 1px solid #fad8ae;    
-    border-left: 4px solid var(--warn);
+    border: 1px solid #f5cca5;    
+    border-left: 4px solid var(--orange);
     padding: 14px 16px;
     border-radius: 10px;
     margin-bottom: 16px;

+ 28 - 13
src/views/ledger/Projects.vue

@@ -33,8 +33,8 @@
               </tr>
             </thead>
             <tbody>
-              <tr v-for="item in projects" :key="(item as any)[columnId+'_title']">
-                <td><span class="link-text" @click="handleViewClick(item)">{{ (item as any)[columnId+'_title'] }}</span></td>
+              <tr v-for="item in projects" :key="(item as any)[columnId+'_c_xmmc']">
+                <td><span class="link-text" @click="handleViewClick(item)">{{ (item as any)[columnId+'_c_xmmc'] }}</span></td>
                 <td>{{ (item as any)[columnId+'_c_hzyt']}}</td>
                 <td>{{ (item as any)[columnId+'_c_glqynbkj'] }}</td>
                 <td>{{ moment((item as any)[columnId+'_c_zgxmhtksrq']).format('YYYY-MM-DD') }} - {{ moment((item as any)[columnId+'_c_zgxmhtdqrq']).format('YYYY-MM-DD') }}</td>
@@ -75,6 +75,8 @@ const columnId = CONFIG.COLUMNIDARR[0]
 
 interface ProjectItem {
   title: string
+  c_xmmc: string
+  c_xmdz: string
   c_hzyt: string
   c_glqynbkj: string
   c_zgxmhtksrq: string
@@ -89,7 +91,7 @@ interface ProjectItem {
 const calculateRemainingDays = (dateStr: string): { days: string; daysClass: string } => {
   if (!dateStr) return { days: '', daysClass: '' }
   // console.log(dateStr)`
-  const expireDate = moment(moment(dateStr).format('YYYY-MM-DD'), 'YYYY-MM-DD')
+  const expireDate = moment(moment(dateStr).add(45, 'days').format('YYYY-MM-DD'), 'YYYY-MM-DD')
   if (!expireDate.isValid()) return { days: '', daysClass: '' }
   
   const today = moment().startOf('day')
@@ -99,12 +101,14 @@ const calculateRemainingDays = (dateStr: string): { days: string; daysClass: str
     return { days: `已过期${Math.abs(diffDays)}天`, daysClass: 'grey‌' }
   } else if (diffDays === 0) {
     return { days: '今天到期', daysClass: 'grey‌' }
-  } else if (diffDays <= 30) {
-    return { days: `剩余${diffDays}天`, daysClass: 'danger' }
+  } else if (diffDays <= 60) {
+    return { days: `剩余${diffDays}天`, daysClass: 'red' }
   } else if (diffDays <= 90) {
-    return { days: `剩余${diffDays}天`, daysClass: 'warn' }
+    return { days: `剩余${diffDays}天`, daysClass: 'orange' }
+  }  else if (diffDays <= 180) {
+    return { days: `剩余${diffDays}天`, daysClass: 'yellow' }
   } else {
-    return { days: `剩余${diffDays}天`, daysClass: 'ok' }
+    return { days: `剩余${diffDays}天`, daysClass: 'green' }
   }
 }
 const searchText = ref('')
@@ -115,9 +119,9 @@ const total = ref(0)
 const projects = ref<ProjectItem[]>([])
 
 const handleViewClick = (project: any) => {
-  console.log(project)
-  sessionStorage.setItem('projectName', (project as any)[columnId+'_title'])
-  sessionStorage.setItem('name', (project as any)[columnId+'_title'] || '')
+  // console.log(project)
+  sessionStorage.setItem('projectName', (project as any)[columnId+'_c_xmmc'])
+  sessionStorage.setItem('name', (project as any)[columnId+'_c_xmmc'] || '')
   sessionStorage.setItem('dmsId', (project as any)[columnId+'_id'] || '')
   router.push(`/project-detail/${(project as any)[columnId+'_id']}`)
 }
@@ -163,19 +167,30 @@ const getProjects = async () => {
     pageSize: pageSize.value,
     page: (page.value)
   }
+  if (searchText.value) {
+    params.conditionsList = JSON.stringify([
+      ...(params.conditionsList ? JSON.parse(params.conditionsList) : []),
+      {
+        columnId: columnId,
+        columnName: "c_xmmc",
+        condition: "like",
+        value: `%${searchText.value}%`
+      }
+    ]);
+  }
   params.conditionsList = JSON.stringify([
       ...(params.conditionsList ? JSON.parse(params.conditionsList) : []),
       {
         columnId: columnId,
         columnName: "c_zgxmhtdqrq",
         condition: "between",
-        value: [moment().format('YYYY-MM-DD HH:mm:ss'), '2099-01-01 00:00:00']
+        value: [moment().add(-10, 'days').format('YYYY-MM-DD HH:mm:ss'), '2099-01-01 00:00:00']
       }
   ]);
   
   const res = await api.getMultipleFormsOfJointInvestigation(params)
 
-  console.log(res)
+  // console.log(res)
   if ((res as any).code === 200) {
     projects.value = (res as any).content.data.map((item: any) => ({
       ...item,
@@ -186,7 +201,7 @@ const getProjects = async () => {
     projects.value = []
     total.value = 0
   }
-}
+} 
 
 // const getDmsDataList = async (params: any) => {
 //   return await api.getDmsDataList(params)

+ 2 - 2
vite.config.ts

@@ -3,8 +3,8 @@ import vue from '@vitejs/plugin-vue'
 import { resolve } from 'path'
 
 export default defineConfig({
-  base: './', // 相对路径部署
-  publicDir: 'public/', // 自定义 publicDir 名称
+  base: './',
+  publicDir: 'public/',
   plugins: [vue()],
   resolve: {
     alias: {

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini