Browse Source

附件材料效果实现

songxy 10 months ago
parent
commit
cf9b263e02

+ 98 - 0
client/src/components/PreviewImage/ImagePreview.ts

@@ -0,0 +1,98 @@
+export type ImagePreviewOption = {
+  maxZoom: number
+  minZoom: number
+  fullScreen: boolean
+}
+class ImagePreview {
+  private currentX: number = 0
+  private currentY: number = 0
+  private xOffset: number = 0
+  private yOffset: number = 0
+  private initialX: number = 0
+  private initialY: number = 0
+
+  private maxZoom: number = 6
+  private minZoom: number = 0.5
+  private initZoom: number = 1
+
+  private initRotate: number = 0
+
+  private isMove: boolean = false
+
+  private el: HTMLElement
+
+  constructor(option?: ImagePreviewOption) {
+    this.maxZoom = option?.['maxZoom'] ?? 6
+    this.minZoom = option?.['minZoom'] ?? 0.5
+  }
+
+  bindMouseEvent(el: HTMLElement) {
+    this.el = el
+    this.el.addEventListener('mousedown', (event: any) => {
+      this.downHandler(event)
+    })
+    this.el.addEventListener('mousemove', (event: any) => {
+      this.moveHandler(event)
+    })
+    this.el.addEventListener('mouseup', (event: any) => {
+      this.upHandler(event)
+    })
+    this.el.addEventListener('mouseleave', (event: any) => {
+      this.upHandler(event)
+    })
+  }
+  downHandler(event: any) {
+    event.preventDefault()
+    if (event.button === 0) {
+      this.isMove = true
+      this.initialX = event.clientX - this.xOffset
+      this.initialY = event.clientY - this.yOffset
+      this.el.style.cursor = 'move'
+    }
+  }
+  moveHandler(event: any) {
+    event.preventDefault()
+    if (this.isMove) {
+      this.currentX = event.clientX - this.initialX
+      this.currentY = event.clientY - this.initialY
+      this.xOffset = this.currentX
+      this.yOffset = this.currentY
+      this.setTranslate()
+    }
+  }
+  upHandler(event) {
+    if (event.button === 0) {
+      this.isMove = false
+      this.el.style.cursor = 'pointer'
+    }
+  }
+  zoomHandler(num: number) {
+    if (num === 1 && this.initZoom >= this.maxZoom) return
+    if (num === -1 && this.initZoom <= this.minZoom) return
+    this.initZoom = this.initZoom + num * 0.5
+    this.setTranslate()
+  }
+  rotateHandler = (num: number) => {
+    if (num !== 0) {
+      this.initRotate += num * 90
+      this.setTranslate()
+    } else {
+      //重置
+      this.initRotate = 0
+      this.currentX =
+        this.currentY =
+        this.xOffset =
+        this.yOffset =
+        this.initialX =
+        this.initialY =
+          0
+      this.initZoom = 1
+      this.el.style.transform = 'none'
+    }
+  }
+  setTranslate() {
+    this.el.style.transform = `translate3d(${this.currentX}px, ${this.currentY}px, 0) scale(${this.initZoom}) rotate(${this.initRotate}deg)`
+  }
+}
+
+export default ImagePreview

+ 126 - 0
client/src/components/PreviewImage/index.vue

@@ -0,0 +1,126 @@
+<template>
+  <div class="preView">
+    <div class="preview_box">
+      <div class="content">
+        <img ref="imgRef" :src="props.src" />
+        <ul class="img_tool">
+          <li @click="zoomHandler(1)">
+            <el-icon><ZoomIn /></el-icon>
+          </li>
+          <li @click="zoomHandler(-1)">
+            <el-icon><ZoomOut /></el-icon>
+          </li>
+          <li @click="rotateHandler(-1)">
+            <el-icon><RefreshLeft /></el-icon>
+          </li>
+          <li @click="rotateHandler(1)">
+            <el-icon><RefreshRight /></el-icon>
+          </li>
+          <li @click="rotateHandler(0)">
+            <el-icon><Refresh /></el-icon>
+          </li>
+        </ul>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import ImagePreview from './ImagePreview.ts'
+defineOptions({
+  name: 'PreView'
+})
+const props = withDefaults(
+  defineProps<{
+    src: string
+    visiable: boolean
+    fullScreen: boolean
+    width: number
+    height: number
+    title: string
+  }>(),
+  {
+    visiable: true,
+    fullScreen: false,
+    width: 60,
+    height: 90,
+    title: '资源预览'
+  }
+)
+const emit = defineEmits<{
+  (e: 'close')
+}>()
+
+const imgRef = ref<HTMLElement>()
+let imagePreview: ImagePreview
+const zoomHandler = (num: number) => {
+  imagePreview.zoomHandler(num)
+}
+const rotateHandler = (num: number) => {
+  imagePreview.rotateHandler(num)
+}
+onMounted(() => {
+  imagePreview = new ImagePreview()
+  imagePreview.bindMouseEvent(imgRef.value as HTMLElement)
+})
+</script>
+
+<style lang="scss" scoped>
+.preView {
+  z-index: 9999999;
+  position: absolute;
+  top: 0px;
+  bottom: 0px;
+  left: 0px;
+  right: 0px;
+  background: rgba(0, 0, 0, 0.5);
+  > .preview_box {
+    position: absolute;
+    left: 0px;
+    right: 0px;
+    top: 0px;
+    bottom: 0px;
+    margin: auto;
+    padding: 20px;
+    padding-top: 10px;
+    > .content {
+      height: 100%;
+      padding: 0px 20px;
+      position: relative;
+      text-align: center;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      overflow: hidden;
+      > img {
+        display: block;
+        cursor: pointer;
+        user-drag: none;
+        margin-top: 8%;
+        user-select: none;
+        -moz-user-select: none;
+        -webkit-user-drag: none;
+        -webkit-user-select: none;
+        &.scale {
+          transition-duration: 0.2s;
+        }
+      }
+      > .img_tool {
+        display: flex;
+        background: rgba(0, 0, 0, 0.8);
+        border-radius: 5px;
+        padding: 15px 25px;
+        position: absolute;
+        bottom: 10px;
+        > li {
+          color: #fff;
+          font-size: 26px;
+          line-height: 26px;
+          padding: 0px 10px;
+          cursor: pointer;
+        }
+      }
+    }
+  }
+}
+</style>

+ 24 - 0
client/src/router/modules/remaining.ts

@@ -313,6 +313,30 @@ const remainingRouter: AppRouteRecordRaw[] = [
           title: '发出的日志'
         }
       },
+      {
+        path: 'projectReport',
+        component: () => import('@/views/OaSystem/marketCenter/projectReport/index.vue'),
+        name: 'ProjectReport',
+        meta: {
+          title: '产值填报'
+        }
+      },
+      {
+        path: 'projectCheck',
+        component: () => import('@/views/OaSystem/marketCenter/projectCheck/index.vue'),
+        name: 'ProjectCheck',
+        meta: {
+          title: '产值审核'
+        }
+      },
+      {
+        path: 'projectCheckPass',
+        component: () => import('@/views/OaSystem/marketCenter/projectCheckPass/index.vue'),
+        name: 'ProjectCheckPass',
+        meta: {
+          title: '产值审核通过'
+        }
+      },
       {
         path: 'newCustomer',
         component: () => import('@/views/OaSystem/marketCenter/khxjPage/index.vue'),

+ 2 - 1
client/src/utils/dict.ts

@@ -157,5 +157,6 @@ export enum DICT_TYPE {
   HK_TYPES = 'hk_type', //户口性质
   POLITY_TYPES = 'polity_type', //政治面貌
   STAFF_STATE_TYPES = 'staff_state_type',
-  SEX_TYPES = 'sex_type'
+  SEX_TYPES = 'sex_type',
+  PROJECT_REPORT_STATUS = 'project_report_status'
 }

+ 4 - 4
client/src/utils/tree.ts

@@ -403,16 +403,16 @@ export const filterNodeMethod = (value, data) => {
   return data.name.includes(value)
 }
 
-export const treeValuePath = (treeData, nodeId) => {
+export const treeValuePath = (treeData, nodeId, key = 'id', value = 'label') => {
   function traverse(nodes) {
     for (const node of nodes) {
-      if (node.id === nodeId) {
-        return [node.label]
+      if (node[key] === nodeId) {
+        return [node[value]]
       }
       if (node.children) {
         const path = traverse(node.children)
         if (path) {
-          return [node.label, ...path]
+          return [node[value], ...path]
         }
       }
     }

+ 236 - 0
client/src/views/OaSystem/marketCenter/projectCheck/index.vue

@@ -0,0 +1,236 @@
+<template>
+  <div class="oa-sys-list-view">
+    <div class="btnBox" style="margin-bottom: 15px; text-align: right">
+      <el-button type="primary" @click="passBatchProjectReportHandle"> 批量通过</el-button>
+    </div>
+    <div class="tableBox">
+      <div class="table" ref="tableRef">
+        <el-table
+          stripe
+          :data="dataSource"
+          style="width: 100%; height: 100%"
+          :style="{ height: tableHeight + 'px' }"
+          row-key="id"
+          :header-cell-style="{
+            background: '#E5F0FB',
+            color: '#233755',
+            height: '50px'
+          }"
+          @selection-change="handleSelectionChange"
+          v-loading="loading"
+        >
+          <el-table-column type="selection" width="55" />
+          <el-table-column label="序号" width="100" align="center">
+            <template #default="scope">{{ scope.$index + 1 }}</template>
+          </el-table-column>
+          <el-table-column :show-overflow-tooltip="true" prop="xmmc" label="项目名称" width="280" />
+          <el-table-column :show-overflow-tooltip="true" prop="xmbm" label="项目部门" width="250" />
+          <el-table-column label="项目经理" prop="xmjl" width="160" />
+          <el-table-column label="项目状态" width="120" align="center">
+            <template #default="scope">{{ getCustomerType(scope.row.xmzt) }}</template>
+          </el-table-column>
+          <el-table-column label="合同额(万元)" width="140" align="center">
+            <template #default="scope">{{ scope.row.virtualAmount }}</template>
+          </el-table-column>
+          <el-table-column label="已回款" width="140" align="center">
+            <template #default="scope">{{ scope.row.returnAmount }}</template>
+          </el-table-column>
+          <el-table-column label="本年初始进度" width="140" align="center">
+            <template #default="scope">{{ scope.row.finishProgress }}</template>
+          </el-table-column>
+          <el-table-column label="现进度" width="220" align="center">
+            <template #default="scope">
+              <span>{{ scope.row.reportProgress }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="进度说明" width="220" align="center">
+            <template #default="scope">
+              <span>{{ scope.row.progressComment }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="本年产值" width="140" align="center">
+            <template #default="scope">{{ scope.row.periodValue }}</template>
+          </el-table-column>
+          <el-table-column label="剩余产值(万元)" width="160" align="center">
+            <template #default="scope">{{ scope.row.surplusValue }}</template>
+          </el-table-column>
+          <el-table-column label="考核比例" width="140" align="center">
+            <template #default="scope">
+              <span>{{ scope.row.shareRatio }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="本年考核产值(万元)" width="180" align="center">
+            <template #default="scope">{{ scope.row.periodCheckValue }}</template>
+          </el-table-column>
+          <el-table-column label="状态" width="140" align="center">
+            <template #default="scope">{{ scope.row.periodCheckValue }}</template>
+          </el-table-column>
+          <el-table-column label="操作" fixed="right" width="260">
+            <template #default="scope">
+              <dialog-confirm
+                title="确认审核通过吗?"
+                confirmButtonText="确定"
+                cancelButtonText="取消"
+                @confirm="passProjectReportHandle(scope.row)"
+                placement="top-end"
+                width="280"
+              >
+                <template #reference>
+                  <el-button type="primary" plain round> 通过 </el-button>
+                </template>
+              </dialog-confirm>
+              <el-button type="danger" plain round @click="rejectClickHandle(scope.row)">
+                驳回
+              </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+    </div>
+    <el-dialog title="驳回" v-model="rejectVisiabled" width="600px" append-to-body>
+      <div class="timeLineBox">
+        <el-input
+          type="textarea"
+          v-model="rejectData.comments"
+          rows="5"
+          placeholder="请填写驳回理由"
+        />
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="rejectVisiabled = false">取消</el-button>
+          <el-button type="primary" @click="rejectProjectReportHandle">确定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script lang="ts" setup>
+import request from '@/config/axios'
+import { getDictOptions } from '@/utils/dict'
+
+defineOptions({ name: 'ProjectCheck' })
+
+const dataSource = ref([])
+
+const tableRef: any = ref(null)
+const tableHeight: any = ref(0)
+const message = useMessage()
+
+const loading = ref(false)
+const getProjectCheckByList = async () => {
+  loading.value = true
+  const result: any = await request.get(
+    {
+      url: '/projectReport/list-check'
+    },
+    '/business'
+  )
+  dataSource.value = result ?? []
+  loading.value = false
+}
+
+const passProjectReportHandle = async (data: any) => {
+  const result: any = await request.post(
+    {
+      url: '/projectReport/pass',
+      data: { id: data['auditId'], reportId: data['reportId'] }
+    },
+    '/business'
+  )
+  if (result) {
+    message.success('通过成功!')
+    getProjectCheckByList()
+  }
+}
+const selections = ref<any>()
+const passBatchProjectReportHandle = async () => {
+  const data = selections.value.map((item) => {
+    return {
+      id: item['auditId'],
+      reportId: item['reportId']
+    }
+  })
+  const result: any = await request.post(
+    {
+      url: '/projectReport/pass-batch',
+      data
+    },
+    '/business'
+  )
+  if (result) {
+    message.success('批量审核成功!')
+    getProjectCheckByList()
+  }
+}
+const handleSelectionChange = (arr) => {
+  selections.value = arr
+}
+interface RejectDataType {
+  id: string
+  reportId: string
+  comments?: string
+}
+const rejectVisiabled = ref<boolean>(false)
+const rejectData = reactive<RejectDataType>({
+  id: '',
+  reportId: '',
+  comments: ''
+})
+const rejectClickHandle = (row: RejectDataType) => {
+  rejectData['id'] = row['auditId']
+  rejectData['reportId'] = row['reportId']
+  rejectVisiabled.value = true
+}
+const rejectProjectReportHandle = async () => {
+  const result: any = await request.post(
+    {
+      url: '/projectReport/reject',
+      data: rejectData
+    },
+    '/business'
+  )
+  rejectVisiabled.value = false
+  if (result) {
+    message.success('驳回成功!')
+    getProjectCheckByList()
+  }
+}
+const customerType: any = ref([])
+const getCustomerType = (str) => {
+  return customerType.value.find((item) => item.value == str)?.label ?? str
+}
+
+onMounted(() => {
+  customerType.value = getDictOptions('customer_type')
+  tableHeight.value = tableRef.value.clientHeight
+  getProjectCheckByList()
+})
+</script>
+<style lang="scss" scoped>
+.titleBox {
+  display: flex;
+  justify-content: space-between;
+  line-height: 60px;
+  font-weight: 600;
+  font-size: 16px;
+  color: #121518;
+}
+.tableBox {
+  margin-top: 0 !important;
+}
+.searchBox {
+  .customer {
+    width: 300px !important;
+  }
+  .company {
+    width: 320px !important;
+  }
+  .area {
+    width: 280px !important;
+  }
+  .type {
+    width: 200px;
+  }
+}
+</style>

+ 220 - 0
client/src/views/OaSystem/marketCenter/projectCheckPass/index.vue

@@ -0,0 +1,220 @@
+<template>
+  <div class="oa-sys-list-view">
+    <div class="btnBox" style="margin-bottom: 15px; text-align: right">
+      <el-button type="primary" @click="startProjectReportPeriodHandle"> 开启填报</el-button>
+      <el-button type="danger" @click="archiveProjectReportPeriodHandle"> 归档</el-button>
+    </div>
+    <div class="tableBox">
+      <div class="table" ref="tableRef">
+        <table>
+          <thead>
+            <tr>
+              <th>项目部门</th>
+              <th>本年汇总产值</th>
+              <th>项目名称</th>
+              <th>项目经理</th>
+              <th>项目状态</th>
+              <th>合同额(万元)</th>
+              <th>已回款</th>
+              <th>本年初始进度</th>
+              <th>现进度</th>
+              <th>进度说明</th>
+              <th>本年产值</th>
+              <th>剩余产值(万元)</th>
+              <th>考核比例</th>
+              <th>本年考核产值(万元)</th>
+            </tr>
+          </thead>
+          <tbody>
+            <template v-for="(item, index) in dataSource" :key="index">
+              <tr>
+                <td :rowspan="item['children'].length + 1">{{ item['xmbm'] }}</td>
+                <td :rowspan="item['children'].length + 1">{{ item['totalPeriodValue'] }}</td>
+                <td>{{ item['xmmc'] }}</td>
+                <td>{{ item['xmjl'] }}</td>
+                <td>{{ getCustomerType(item['xmzt']) }}</td>
+                <td>{{ item['virtualAmount'] }}</td>
+                <td>{{ item['returnAmount'] }}</td>
+                <td>{{ item['finishProgress'] }}</td>
+                <td>{{ item['reportProgress'] }}</td>
+                <td>{{ item['progressComment'] }}</td>
+                <td>{{ item['periodValue'] }}</td>
+                <td>{{ item['surplusValue'] }}</td>
+                <td>{{ item['shareRatio'] }}</td>
+                <td>{{ item['periodCheckValue'] }}</td>
+              </tr>
+              <tr v-for="(cItem, cIndex) in item['children']" :key="cIndex">
+                <td>{{ cItem['xmmc'] }}</td>
+                <td>{{ cItem['xmjl'] }}</td>
+                <td>{{ getCustomerType(cItem['xmzt']) }}</td>
+                <td>{{ cItem['virtualAmount'] }}</td>
+                <td>{{ cItem['returnAmount'] }}</td>
+                <td>{{ cItem['finishProgress'] }}</td>
+                <td>{{ cItem['reportProgress'] }}</td>
+                <td>{{ cItem['progressComment'] }}</td>
+                <td>{{ cItem['periodValue'] }}</td>
+                <td>{{ cItem['surplusValue'] }}</td>
+                <td>{{ cItem['shareRatio'] }}</td>
+                <td>{{ cItem['periodCheckValue'] }}</td>
+              </tr>
+            </template>
+          </tbody>
+        </table>
+      </div>
+    </div>
+  </div>
+</template>
+<script lang="ts" setup>
+import request from '@/config/axios'
+import { getDictOptions } from '@/utils/dict'
+
+defineOptions({ name: 'ProjectCheckPass' })
+
+const dataSource = ref([])
+
+const tableRef: any = ref(null)
+const tableHeight: any = ref(0)
+const message = useMessage()
+
+const loading = ref(false)
+const rowMaps = ref<{
+  [key: string]: number
+}>()
+const getProjectPassByList = async () => {
+  loading.value = true
+  const result: any = await request.get(
+    {
+      url: '/projectReport/list-pass'
+    },
+    '/business'
+  )
+  const tObj = {}
+  Object.assign(tObj, result[0])
+  tObj['xmbm'] = '测试123'
+  const nResult = [].concat(result ?? [], result ?? [], result ?? []).concat([tObj])
+  //@ts-ignore
+  nResult.sort((a: string, b: string) => a.xmbm.localeCompare(b.xmbm))
+  dataSource.value = getRowMapByList(nResult, 'xmbm')
+  loading.value = false
+}
+const getRowMapByList = (arrs: any[], key: string) => {
+  const rowsMap = {}
+  arrs.forEach((item, index) => {
+    if (rowsMap[item[key]] == null) {
+      rowsMap[item[key]] = [item]
+    } else {
+      rowsMap[item[key]].push(item)
+    }
+  })
+  const keys = Object.keys(rowsMap)
+  const nArr: any[] = []
+  keys.forEach((key) => {
+    const totalPeriodValue = rowsMap[key].reduce((a, b) => a + b['periodValue'], 0)
+    const arrs = rowsMap[key].splice(0, 1)
+    const item = arrs[0]
+    item['totalPeriodValue'] = totalPeriodValue
+    item['children'] = rowsMap[key]
+    nArr.push(item)
+  })
+  console.log(nArr)
+  return nArr
+}
+const customerType: any = ref([])
+const getCustomerType = (str) => {
+  return customerType.value.find((item) => item.value == str)?.label ?? str
+}
+const startProjectReportPeriodHandle = async () => {
+  const result: any = await request.get(
+    {
+      url: '/projectReportPeriod/start'
+    },
+    '/business'
+  )
+  if (result) {
+    message.success('开启成功!')
+  }
+}
+const archiveProjectReportPeriodHandle = async () => {
+  const result: any = await request.get(
+    {
+      url: '/projectReportPeriod/archive'
+    },
+    '/business'
+  )
+  if (result) {
+    message.success('开启成功!')
+  }
+}
+onMounted(() => {
+  customerType.value = getDictOptions('customer_type')
+  tableHeight.value = tableRef.value.clientHeight
+  getProjectPassByList()
+})
+</script>
+<style lang="scss" scoped>
+.titleBox {
+  display: flex;
+  justify-content: space-between;
+  line-height: 60px;
+  font-weight: 600;
+  font-size: 16px;
+  color: #121518;
+}
+.tableBox {
+  margin-top: 0 !important;
+}
+.searchBox {
+  .customer {
+    width: 300px !important;
+  }
+  .company {
+    width: 320px !important;
+  }
+  .area {
+    width: 280px !important;
+  }
+  .type {
+    width: 200px;
+  }
+}
+.tableBox {
+  > .table {
+    table {
+      border-collapse: collapse;
+      width: 100%;
+      th,
+      td {
+        padding: 15px;
+        color: #233755;
+        font-size: 1rem;
+        text-align: center;
+        &.ellipsis {
+          overflow: hidden;
+          white-space: nowrap;
+          overflow: hidden;
+          text-overflow: ellipsis;
+        }
+      }
+      > thead {
+        background: #e5f0fb;
+      }
+      > tbody {
+        td {
+          border-bottom: 1px solid #ebeef5;
+        }
+      }
+    }
+  }
+}
+:deep(.el-table__row) {
+  > td {
+    &:nth-child(1) {
+      border-left: var(--el-table-border);
+      border-right: var(--el-table-border);
+    }
+    &:nth-child(2) {
+      border-right: var(--el-table-border);
+    }
+  }
+}
+</style>

+ 249 - 0
client/src/views/OaSystem/marketCenter/projectReport/index.vue

@@ -0,0 +1,249 @@
+<template>
+  <div class="oa-sys-list-view">
+    <div class="tableBox">
+      <div class="table" ref="tableRef">
+        <el-table
+          stripe
+          :data="dataSource"
+          style="width: 100%; height: 100%"
+          :style="{ height: tableHeight + 'px' }"
+          row-key="id"
+          :header-cell-style="{
+            background: '#E5F0FB',
+            color: '#233755',
+            height: '50px'
+          }"
+          v-loading="loading"
+        >
+          <el-table-column label="序号" width="100" align="center">
+            <template #default="scope">{{ scope.$index + 1 }}</template>
+          </el-table-column>
+          <el-table-column :show-overflow-tooltip="true" prop="xmmc" label="项目名称" width="280" />
+          <el-table-column label="项目经理" prop="xmjl" width="160" />
+          <el-table-column label="项目状态" width="120" align="center">
+            <template #default="scope">{{ getCustomerType(scope.row.xmzt) }}</template>
+          </el-table-column>
+          <el-table-column label="合同额(万元)" width="140" align="center">
+            <template #default="scope">{{ scope.row.virtualAmount }}</template>
+          </el-table-column>
+          <el-table-column label="已回款" width="140" align="center">
+            <template #default="scope">{{ scope.row.returnAmount }}</template>
+          </el-table-column>
+          <el-table-column label="本年初始进度" width="140" align="center">
+            <template #default="scope">{{ scope.row.finishProgress }}</template>
+          </el-table-column>
+          <el-table-column label="现进度" width="220" align="center">
+            <template #default="scope">
+              <span v-if="!scope.row.isEditor">{{ scope.row.reportProgress }}%</span>
+              <el-input
+                v-else
+                v-model="scope.row.reportProgress"
+                @change="reportProgressHandle(scope.row)"
+              >
+                <template #append>%</template>
+              </el-input>
+            </template>
+          </el-table-column>
+          <el-table-column label="进度说明" width="220" align="center">
+            <template #default="scope">
+              <span v-if="!scope.row.isEditor">{{ scope.row.progressComment }}</span>
+              <el-input v-else v-model="scope.row.progressComment" />
+            </template>
+          </el-table-column>
+          <el-table-column label="本年产值" width="140" align="center">
+            <template #default="scope">{{ scope.row.periodValue }}</template>
+          </el-table-column>
+          <el-table-column label="剩余产值(万元)" width="160" align="center">
+            <template #default="scope">{{ scope.row.surplusValue }}</template>
+          </el-table-column>
+          <el-table-column label="考核比例" width="140" align="center">
+            <template #default="scope">
+              <span v-if="!scope.row.isEditor">{{ scope.row.shareRatio }}</span>
+              <el-input v-else v-model="scope.row.shareRatio" />
+            </template>
+          </el-table-column>
+          <el-table-column label="本年考核产值(万元)" width="180" align="center">
+            <template #default="scope">{{ scope.row.periodCheckValue }}</template>
+          </el-table-column>
+          <el-table-column label="状态" width="140" align="center">
+            <template #default="scope">
+              {{ getDictLabel(DICT_TYPE.PROJECT_REPORT_STATUS, scope.row.reportStatus) }}
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" fixed="right" width="260">
+            <template #default="scope">
+              <el-button type="primary" plain round @click="getProjectReportAuditList(scope.row)">
+                查看
+              </el-button>
+              <el-button
+                type="primary"
+                v-if="['00', '11'].includes(scope.row.reportStatus) || !scope.row.reportId"
+                plain
+                round
+                @click="editorHandle(scope.row)"
+              >
+                <span v-if="scope.row.isEditor">保存</span>
+                <span v-else>编辑</span>
+              </el-button>
+              <el-button
+                v-if="['00', '11'].includes(scope.row.reportStatus)"
+                type="primary"
+                plain
+                round
+                @click="commitProjectReport(scope.row)"
+              >
+                审核
+              </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+    </div>
+    <el-dialog title="审核记录" v-model:visible="auditorVisiabled" width="600px" append-to-body>
+      <div class="timeLineBox">
+        <el-timeline style="max-width: 600px">
+          <el-timeline-item
+            v-for="(activity, index) in activities"
+            :key="index"
+            type="primary"
+            :hollow="true"
+            :timestamp="activity.auditDate"
+          >
+            <p>
+              <span>审核人:{{ activity.auditor }}</span>
+              <span>状态:{{ activity.auditStatus }}</span>
+            </p>
+            <p> {{ activity.comments }}</p>
+          </el-timeline-item>
+        </el-timeline>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+<script lang="ts" setup>
+import request from '@/config/axios'
+import { getDictOptions, DICT_TYPE, getDictLabel } from '@/utils/dict'
+import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
+
+defineOptions({ name: 'ProjectReport' })
+
+const dataSource = ref([])
+
+const tableRef: any = ref(null)
+const tableHeight: any = ref(0)
+const message = useMessage()
+
+const loading = ref(false)
+const getProjectReportByList = async () => {
+  loading.value = true
+  const result: any = await request.get(
+    {
+      url: '/projectReport/list'
+    },
+    '/business'
+  )
+  result.forEach((item) => {
+    item['isEditor'] = false
+  })
+  dataSource.value = result ?? []
+  loading.value = false
+}
+const { wsCache } = useCache()
+const user = wsCache.get(CACHE_KEY.USER)
+const saveProjectReport = async (data = {}) => {
+  const sendData = {}
+  Object.assign(sendData, data)
+  sendData['reportProgress'] = data['reportProgress'] / 100
+  data['xmbm'] = user.user.deptName ? user.user.deptName : '部门信息'
+  const result: any = await request.post(
+    {
+      url: '/projectReport/save',
+      data
+    },
+    '/business'
+  )
+  if (result) {
+    message.success('保存成功!')
+  }
+}
+
+const commitProjectReport = async (row: { reportId: string }) => {
+  const result: any = await request.get(
+    {
+      url: '/projectReport/commit',
+      params: {
+        id: row.reportId
+      }
+    },
+    '/business'
+  )
+  if (result) {
+    message.success('提交审核成功!')
+    getProjectReportByList()
+  }
+}
+const customerType: any = ref([])
+const getCustomerType = (str) => {
+  return customerType.value.find((item) => item.value == str)?.label ?? str
+}
+const reportProgressHandle = (row) => {
+  if (row.reportProgress) {
+    row.periodValue = ((row.reportProgress - row.finishProgress) / 100) * row.virtualAmount
+    row.surplusValue = ((100 - row.reportProgress) / 100) * row.virtualAmount
+    row.periodCheckValue = row.shareRatio * row.periodValue
+  }
+}
+const editorHandle = (row) => {
+  if (row.isEditor) {
+    saveProjectReport(row)
+  }
+  row.isEditor = !row.isEditor
+}
+const auditorVisiabled = ref<boolean>(false)
+const activities = ref<any[]>([])
+
+const getProjectReportAuditList = async (row: { reportId: string }) => {
+  const result: any = await request.get(
+    {
+      url: '/projectReportAudit/list',
+      params: {
+        reportId: row.reportId
+      }
+    },
+    '/business'
+  )
+  console.log(result)
+}
+onMounted(() => {
+  customerType.value = getDictOptions('customer_type')
+  tableHeight.value = tableRef.value.clientHeight
+  getProjectReportByList()
+})
+</script>
+<style lang="scss" scoped>
+.titleBox {
+  display: flex;
+  justify-content: space-between;
+  line-height: 60px;
+  font-weight: 600;
+  font-size: 16px;
+  color: #121518;
+}
+.tableBox {
+  margin-top: 0 !important;
+}
+.searchBox {
+  .customer {
+    width: 300px !important;
+  }
+  .company {
+    width: 320px !important;
+  }
+  .area {
+    width: 280px !important;
+  }
+  .type {
+    width: 200px;
+  }
+}
+</style>

+ 1 - 0
client/src/views/OaSystem/projectCenter/projectDetail/components/fjcl/index.scss

@@ -51,6 +51,7 @@
           height: 0px;
           flex-grow: 1;
           overflow-y: auto;
+          position: relative;
         }
       }
     }

+ 50 - 27
client/src/views/OaSystem/projectCenter/projectDetail/components/fjcl/index.vue

@@ -30,7 +30,9 @@
                   <el-dropdown-item v-if="data.type !== 3" @click="uploadClick(data)">
                     上传
                   </el-dropdown-item>
-                  <el-dropdown-item @click="downloadHandle(data)">下载</el-dropdown-item>
+                  <el-dropdown-item v-if="data.type === 3" @click="downloadHandle(data)">
+                    下载
+                  </el-dropdown-item>
                   <el-dropdown-item v-if="data.type === 3" @click="deleteHandle(data)">
                     删除
                   </el-dropdown-item>
@@ -44,17 +46,25 @@
     <input type="file" ref="fileRef" style="display: none" @change="fileChageHandle" />
     <div class="previewBox">
       <div class="tools">
-        <h4>{{ fileInfo?.['fileName'] }}</h4>
+        <h4>{{ fileInfo?.['name'] }}</h4>
         <ul>
-          <li @click="downloadFile"><i class="down_icon"></i>下载</li>
+          <li @click="downloadHandle(fileInfo)"><i class="down_icon"></i>下载</li>
         </ul>
       </div>
-      <div class="contentBox">
-        <vue-office-pdf
-          :src="fileInfo?.['fileUrl']"
-          @rendered="renderedHandler"
-          @error="errorHandler"
-        />
+      <div class="contentBox" v-if="['.pdf'].includes(fileInfo.suffix)">
+        <vue-office-pdf :src="fileInfo?.['fileUrl']" @error="errorHandler" />
+      </div>
+      <div class="contentBox" v-else-if="['.docx'].includes(fileInfo.suffix)">
+        <vue-office-docx :src="fileInfo?.['fileUrl']" @error="errorHandler" />
+      </div>
+      <div class="contentBox" v-else-if="['.xlsx'].includes(fileInfo.suffix)">
+        <vue-office-excel :src="fileInfo?.['fileUrl']" @error="errorHandler" />
+      </div>
+      <div
+        class="contentBox"
+        v-else-if="['.jpg', '.jpeg', '.png', '.gif'].includes(fileInfo.suffix)"
+      >
+        <preview-image :src="fileInfo?.['fileUrl']" />
       </div>
     </div>
   </div>
@@ -64,6 +74,9 @@ import { onMounted } from 'vue'
 import { useRoute } from 'vue-router'
 import request from '@/config/axios'
 import VueOfficePdf from '@vue-office/pdf'
+import VueOfficeDocx from '@vue-office/docx'
+import VueOfficeExcel from '@vue-office/excel'
+import PreviewImage from '@/components/PreviewImage/index.vue'
 import { listToTree, treeValuePath } from '@/utils/tree'
 import '@vue-office/excel/lib/index.css'
 import '@vue-office/docx/lib/index.css'
@@ -75,27 +88,19 @@ const defaultProps = {
   children: 'children',
   label: 'name'
 }
-const handleNodeClick = (data: Tree) => {
-  console.log(data['type'])
-  if (data['type'] === 3) {
-    queryProjectMaterial(data['id']).then((resultData) => {
-      fileInfo.value = resultData
-    })
-  }
-}
 
 interface FileType {
   id: string
-  projectId: string
-  fileld: string
-  fileName: string
-  fileSuffix: string
+  name: string
+  suffix: string
   fileUrl: string
 }
-const fileInfo = ref<FileType>()
-const renderedHandler: () => void = () => {
-  console.log('加载完成')
-}
+const fileInfo = ref<FileType>({
+  id: '',
+  name: '',
+  suffix: '',
+  fileUrl: ''
+})
 const errorHandler: (err: any) => void = (err: any) => {
   console.log(err)
 }
@@ -108,7 +113,7 @@ interface Tree {
   pid: string
   nodeType: number
   fileUrl?: string
-  fileSuffix?: string
+  suffix?: string
   children?: Array<Tree>
 }
 const treeData = ref<Tree[]>([])
@@ -127,7 +132,7 @@ const queryProjectMaterialByTree = (): void => {
         .map((item) => {
           if (item.type !== 3) {
             return {
-              label: item['extendData']['mark'],
+              label: item['extendData']['name'],
               id: item['id'],
               pid: item['pid'],
               children: []
@@ -139,6 +144,15 @@ const queryProjectMaterialByTree = (): void => {
     treeData.value = listToTree(resultData)
   })
 }
+const handleNodeClick = (data: Tree) => {
+  console.log(data)
+  if (data['type'] === 3) {
+    const extendData = data['extendData']
+    fileInfo.value.name = extendData['name']
+    fileInfo.value.suffix = extendData['suffix']
+    fileInfo.value.fileUrl = extendData['fileUrl']
+  }
+}
 /***
  * 获取材料
  */
@@ -160,7 +174,9 @@ const currentMaterialPath = ref<string>()
 const uploadClick = (data) => {
   currentMaterialsId.value = data.id as string
   if (data.type !== 3) {
+    console.log(materialPathTree.value)
     const arr: string[] = treeValuePath(materialPathTree.value, data.id)
+    arr.unshift('', '项目验收')
     if (arr && arr.length > 0) {
       currentMaterialPath.value = arr.join('/')
       currentMaterialsId.value = data.id
@@ -182,8 +198,15 @@ const uploadProjectMaterial = (file): void => {
   })
 }
 const downloadHandle = (data: any) => {
+  const downA = document.createElement('a')
+  downA.setAttribute('target', '_bank')
+  downA.href = data['extendData']['fileUrl']
+  downA.download = data.name
+  downA.click()
+  return
   queryProjectMaterial(data['id']).then((result) => {
     const downA = document.createElement('a')
+    downA.setAttribute('target', '_bank')
     downA.href = result.fileUrl
     downA.download = result.name
     downA.click()

+ 2 - 2
client_h5/.env.dev

@@ -1,5 +1,5 @@
-VITE_BASE_URL='https://oa.zjugis.com:28080'
-# VITE_BASE_URL='http://10.10.10.7:18080'
+# VITE_BASE_URL='https://oa.zjugis.com:28080'
+VITE_BASE_URL='http://10.10.10.7:18080'
 # VITE_BASE_URL='http://localhost:6090/'
 
 

+ 5 - 1
client_h5/src/components/flowForm.vue

@@ -72,6 +72,9 @@
       </template>
     </div>
     <div class="fixed-btn" v-if="isSubmitVisabled">
+      <!-- <van-button v-if="isCallbackActivity" round block type="primary" @click="submitHandle">
+          退回
+      </van-button> -->
       <van-button :disabled="isSubmitDisabled" round block type="primary" @click="submitHandle">
           转件
       </van-button>
@@ -91,6 +94,7 @@ import { getTemplateOpinionListByFlowInstanceId, getOpinionListByFlowInstanceId
 defineOptions({
   name: 'FlowForm'
 })
+const isCallbackActivity = ref<boolean>(false)
 const isSubmitVisabled = ref<boolean>(true)
 const isSubmitDisabled = ref<boolean>(false)
 const userInfo = JSON.parse(localStorage.getItem('_userInfo') as string);
@@ -103,7 +107,7 @@ async function initSimpleUserMap() {
 }
 initSimpleUserMap()
 
-defineProps<{
+const props = defineProps<{
   data: any
 }>();
 const emit = defineEmits<{

+ 2 - 0
client_h5/src/router/index.ts

@@ -14,6 +14,8 @@ const router = createRouter({
 })
 // 路由加载前
 router.beforeEach((to, from, next) => {
+  next()
+  return;
   getUserInfoPromise(true).then((isLogin) => {
     next(); 
     if (!isLogin) {