From 4fbdf4c6b13d19b9be54900b5dcff29e2ca7ef01 Mon Sep 17 00:00:00 2001
From: riku <risaku@163.com>
Date: 星期二, 24 六月 2025 17:31:45 +0800
Subject: [PATCH] 巡查单据自动下载功能(待完成)

---
 src/utils/doc.js                                         |   25 ++
 public/餐饮巡查单据模板.doc                                      |    0 
 src/views/fysp/check/components/ArbitraryPhoto.vue       |   36 ++++
 src/views/fysp/task/components/CompMonitorObjEdit.vue    |    2 
 src/components/FYImageSelectDialog.vue                   |    1 
 src/views/fysp/check/ProCheckProxy.js                    |   18 +-
 src/views/fysp/check/components/CompSubTaskStatistic.vue |   42 ++++-
 src/components.d.ts                                      |    1 
 public/工地巡查单据模板.docx                                     |    0 
 src/views/fysp/scene/SceneInspectFile.vue                |  252 +++++++++++++++++++++++++++++++
 src/views/fysp/task/components/CompSubTaskList.vue       |   40 +++-
 src/views/fysp/task/TaskManage.vue                       |   12 +
 12 files changed, 388 insertions(+), 41 deletions(-)

diff --git "a/public/\345\267\245\345\234\260\345\267\241\346\237\245\345\215\225\346\215\256\346\250\241\346\235\277.docx" "b/public/\345\267\245\345\234\260\345\267\241\346\237\245\345\215\225\346\215\256\346\250\241\346\235\277.docx"
new file mode 100644
index 0000000..c1a8470
--- /dev/null
+++ "b/public/\345\267\245\345\234\260\345\267\241\346\237\245\345\215\225\346\215\256\346\250\241\346\235\277.docx"
Binary files differ
diff --git "a/public/\351\244\220\351\245\256\345\267\241\346\237\245\345\215\225\346\215\256\346\250\241\346\235\277.doc" "b/public/\351\244\220\351\245\256\345\267\241\346\237\245\345\215\225\346\215\256\346\250\241\346\235\277.doc"
new file mode 100644
index 0000000..b65f9c3
--- /dev/null
+++ "b/public/\351\244\220\351\245\256\345\267\241\346\237\245\345\215\225\346\215\256\346\250\241\346\235\277.doc"
Binary files differ
diff --git a/src/components.d.ts b/src/components.d.ts
index d9c2fb8..2b04de3 100644
--- a/src/components.d.ts
+++ b/src/components.d.ts
@@ -25,6 +25,7 @@
     ElCard: typeof import('element-plus/es')['ElCard']
     ElCascader: typeof import('element-plus/es')['ElCascader']
     ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
+    ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElCollapse: typeof import('element-plus/es')['ElCollapse']
     ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
diff --git a/src/components/FYImageSelectDialog.vue b/src/components/FYImageSelectDialog.vue
index 6e283c3..4d2602a 100644
--- a/src/components/FYImageSelectDialog.vue
+++ b/src/components/FYImageSelectDialog.vue
@@ -56,6 +56,7 @@
             :preview-src-list="
               readonly ? typeImgMap.get(activeId).map((v) => v.url) : []
             "
+            crossOrigin="Anonymous"
             :initial-index="i"
             @contextmenu="(e) => showContextMenu(e, i)"
             @click="onSelect(img, i)"
diff --git a/src/utils/doc.js b/src/utils/doc.js
index 25115f2..a757cfc 100644
--- a/src/utils/doc.js
+++ b/src/utils/doc.js
@@ -81,10 +81,20 @@
       let width = image.width;
       let height = image.height;
 
-      if (width > height && horizontalHeight && height > horizontalHeight && scale) {
+      if (
+        width > height &&
+        horizontalHeight &&
+        height > horizontalHeight &&
+        scale
+      ) {
         height = horizontalHeight;
         width = horizontalHeight * scale;
-      } else if (width <= height && verticalWidth && width > verticalWidth && scale) {
+      } else if (
+        width <= height &&
+        verticalWidth &&
+        width > verticalWidth &&
+        scale
+      ) {
         width = verticalWidth;
         height = verticalWidth * scale;
       }
@@ -144,12 +154,15 @@
         throw error;
       }
       const zip = new Pizzip(content);
-      const imageOptions = getImageOptions(imageSize);
       let doc = new docxtemplater()
         .setOptions({ paragraphLoop: true })
-        .loadZip(zip)
-        .attachModule(new ImageModule(imageOptions))
-        .compile();
+        .loadZip(zip);
+
+      if (imageSize) {
+        const imageOptions = getImageOptions(imageSize);
+        doc.attachModule(new ImageModule(imageOptions));
+      }
+      doc.compile();
       doc.resolveData(data).then(() => {
         try {
           doc.render();
diff --git a/src/views/fysp/check/ProCheckProxy.js b/src/views/fysp/check/ProCheckProxy.js
index 938422f..013e0c9 100644
--- a/src/views/fysp/check/ProCheckProxy.js
+++ b/src/views/fysp/check/ProCheckProxy.js
@@ -29,7 +29,7 @@
       //鏈�氳繃鏁伴噺
       unpassNum: 0,
       //鏁存敼鐜�
-      changePer: '0%',
+      changePer: '--',
       //閫氳繃鐜�
       passPer: '0%',
       //瀹℃牳鐜�
@@ -49,10 +49,10 @@
         status.uncheckNum++;
       else status.passNum++;
 
-      if (p.extension3 == proStatus.pass) {
+      if (p.extension3 != proStatus.unCheck) {
         status.proCheckedNum++;
       }
-      if (p.extension3 == proStatus.change_pass) {
+      if (p.extension3 == proStatus.change_pass || p.extension3 == proStatus.change_fail) {
         status.changeCheckedNum++;
       }
 
@@ -234,18 +234,18 @@
     else if (s.proCheckedNum < s.proNum) {
       type = 2;
     }
-    // 鏈暣鏀� 
-    else if (s.changeNum < s.proNum) {
-      type = 3;
-    }
     // 鏁存敼鏈鏍�
-    else if (s.changeCheckedNum == 0) {
+    else if (s.changeNum > 0 && s.changeCheckedNum == 0) {
       type = 4;
     }
     // 鏁存敼閮ㄥ垎瀹℃牳
-    else if (s.changeCheckedNum < s.changeNum) {
+    else if (s.changeNum > 0 && s.changeCheckedNum < s.changeNum) {
       type = 5;
     }
+    // 鏈暣鏀� 
+    else if (s.changeNum < s.proNum) {
+      type = 3;
+    }
     // 瀹屽叏瀹℃牳
     else {
       type = 6;
diff --git a/src/views/fysp/check/components/ArbitraryPhoto.vue b/src/views/fysp/check/components/ArbitraryPhoto.vue
index 55b11a5..703ceda 100644
--- a/src/views/fysp/check/components/ArbitraryPhoto.vue
+++ b/src/views/fysp/check/components/ArbitraryPhoto.vue
@@ -74,6 +74,7 @@
       // ],
       closeContextMenuListenr: undefined,
       // 鍙抽敭閫変腑鐨勫浘鐗�
+      selectedFileElement: undefined,
       selectedFile: undefined,
       selectedIndex: undefined,
       selectedTypeId: undefined
@@ -94,7 +95,7 @@
           };
         });
       return [
-        // { label: '澶嶅埗鍥剧墖', action: 'copy' },
+        // { label: '澶嶅埗鍒板壀璐存澘', action: 'copy' },
         { label: '绉诲姩鍒�', children: items }
       ];
     }
@@ -158,6 +159,7 @@
         left: `${event.clientX}px`,
         top: `${event.clientY}px`
       };
+      this.selectedFileElement = event.target;
       this.selectedTypeId = typeId;
       this.selectedIndex = index;
       this.selectedFile = this.typesMap.get(typeId)[index];
@@ -168,6 +170,7 @@
     handleMenuItem(item) {
       switch (item.action) {
         case 'copy':
+          this.drawImageToCanvas();
           break;
         case 'move':
           this.selectedFile.data.businesstypeid = item.value;
@@ -217,6 +220,37 @@
       //   }
       //   this.selectedFile.loading = false;
       // }, 2000);
+    },
+    drawImageToCanvas() {
+      const canvas = document.createElement('canvas');
+      const ctx = canvas.getContext('2d');
+      const img = this.selectedFileElement; // 鑾峰彇DOM寮曠敤
+      // img.crossOrigin = 'Anonymous';
+      canvas.width = img.naturalWidth;
+      canvas.height = img.naturalHeight;
+      ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
+      // this.copyCanvasToClipboard(canvas);
+      const dataUrl = canvas.toDataURL('image/png'); // 鍙互閫夋嫨鍏朵粬鏍煎紡濡�'image/jpeg'
+      // 鍒涘缓涓�涓复鏃剁殑textarea鍏冪礌鏉ュ鍒舵枃鏈�
+      const tempTextArea = document.createElement('textarea');
+      document.body.appendChild(tempTextArea);
+      tempTextArea.value = dataUrl;
+      tempTextArea.select();
+      document.execCommand('copy');
+      document.body.removeChild(tempTextArea);
+    },
+    copyCanvasToClipboard(canvas) {
+      canvas.toBlob((blob) => {
+        const item = new ClipboardItem({ 'image/png': blob }); // 浣犲彲浠ユ牴鎹渶瑕佹敼鍙樻牸寮忥紝濡� "image/jpeg"
+        navigator.clipboard.write([item]).then(
+          () => {
+            console.log('Image copied to clipboard');
+          },
+          (err) => {
+            console.error('Could not copy image: ', err);
+          }
+        );
+      }, 'image/png'); // 鍚屾牱锛岃繖閲屼篃鍙互鎸囧畾鍏朵粬鏍煎紡锛屽 'image/jpeg'
     }
   }
 };
diff --git a/src/views/fysp/check/components/CompSubTaskStatistic.vue b/src/views/fysp/check/components/CompSubTaskStatistic.vue
index 7f8d3e9..7e30a44 100644
--- a/src/views/fysp/check/components/CompSubTaskStatistic.vue
+++ b/src/views/fysp/check/components/CompSubTaskStatistic.vue
@@ -269,17 +269,39 @@
         completedTimes: 0,
         reviewTimes: 0
       };
-      this.monitorObjList.forEach((m) => {
-        if (this.sceneType == undefined || m.sceneTypeId == this.sceneType) {
-          _res.total++;
-          const times = parseInt(m.extension1);
-          if (times) {
-            _res.completedScenes++;
-            _res.completedTimes += times;
-            _res.reviewTimes += times - 1;
-          }
+
+      // Fixme 2025.5.30: 鐩墠姝ゅ鐨勭洃绠$増鏈俊鎭挓璁板綍鐨勪换鍔″畬鎴愭鏁版湁鍋忓樊锛屾墍浠ユ殏鏃跺厛鏀逛负閫氳繃宸℃煡浠诲姟鏈韩杩涜缁熻
+      /**************************************************************************************/
+      // this.monitorObjList.forEach((m) => {
+      //   if (this.sceneType == undefined || m.sceneTypeId == this.sceneType) {
+      //     _res.total++;
+      //     const times = parseInt(m.extension1);
+      //     if (times) {
+      //       _res.completedScenes++;
+      //       _res.completedTimes += times;
+      //       _res.reviewTimes += times - 1;
+      //     }
+      //   }
+      // });
+      /**************************************************************************************/
+
+      /**************************************************************************************/
+      _res.total = this.monitorObjList.filter(
+        (m) => this.sceneType == undefined || m.sceneTypeId == this.sceneType
+      ).length;
+      _res.completedTimes = this.subtasks.length;
+      const map = new Map();
+      const uniqueArr = [];
+      for (let item of this.subtasks) {
+        if (!map.has(item.data.sceneId)) {
+          map.set(item.data.sceneId, true);
+          uniqueArr.push(item);
         }
-      });
+      }
+      _res.completedScenes = uniqueArr.length;
+      _res.reviewTimes = _res.completedTimes - _res.completedScenes;
+
+      /**************************************************************************************/
       return _res;
     }
   },
diff --git a/src/views/fysp/scene/SceneInspectFile.vue b/src/views/fysp/scene/SceneInspectFile.vue
new file mode 100644
index 0000000..ddd17c8
--- /dev/null
+++ b/src/views/fysp/scene/SceneInspectFile.vue
@@ -0,0 +1,252 @@
+<template>
+  <el-dialog
+    :model-value="modelValue"
+    @update:model-value="handleDialogChange"
+    title="宸℃煡鍗曟嵁涓嬭浇"
+    class="dialog-wrapper"
+    v-loading="loading"
+  >
+    <el-scrollbar
+      ref="scrollbarRef"
+      height="50vh"
+      v-loading="loading"
+      :always="true"
+    >
+      <el-checkbox-group v-model="checkList">
+        <el-space direction="vertical" alignment="flex-start" fill>
+          <el-checkbox
+            v-for="(item, index) in sceneInfoList"
+            :key="item.scense.guid"
+            :value="index"
+            :class="(item.invalid ? 'checkbox-invalid' : '') + ' checkbox'"
+          >
+            <div>
+              <el-text size="large">{{ item.scense.name }}</el-text>
+            </div>
+            <div class="m-t-4">
+              <el-text size="small">{{
+                '鍦板潃锛�' + item.scense.location
+              }}</el-text>
+            </div>
+            <el-space class="m-t-4">
+              <el-tag>
+                {{
+                  item.scense.cityname +
+                  item.scense.districtname +
+                  item.scense.townname
+                }}
+                <!-- {{ item.scense.districtname }}
+                {{ item.scense.townname }} -->
+              </el-tag>
+              <el-tag>{{ item.scense.type }}</el-tag>
+
+              <!-- {{ item.scense.contacts }}
+              {{ item.scense.contactst }} -->
+            </el-space>
+          </el-checkbox>
+        </el-space>
+      </el-checkbox-group>
+    </el-scrollbar>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button type="danger" @click="cancel">鍙栨秷</el-button>
+        <el-button type="primary" :loading="docLoading" @click="handelDownload">
+          涓嬭浇
+        </el-button>
+        <el-button type="default" :loading="docLoading" @click="handelPrint">
+          鎵撳嵃
+        </el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+<script setup>
+/**
+ * 鍦烘櫙宸℃煡鍗曟嵁鑷姩涓嬭浇
+ */
+import { ref, watch } from 'vue';
+import { exportDocx } from '@/utils/doc';
+import sceneApi from '@/api/fysp/sceneApi';
+
+const props = defineProps({
+  // 瀵硅瘽妗嗗紑鍏�
+  modelValue: Boolean,
+  // 鍦烘櫙鍩虹淇℃伅鏁扮粍
+  value: Array
+});
+
+const emits = defineEmits(['update:modelValue']);
+
+const loading = ref(false);
+const scrollbarRef = ref();
+const sceneInfoList = ref([]);
+const checkList = ref([]);
+const docLoading = ref(false);
+
+watch(
+  () => [props.modelValue, props.value],
+  (nV, oV) => {
+    if (nV[0] && nV[1] && nV[1] != oV[1]) {
+      fetchSceneInfo(nV[1]);
+    }
+  }
+);
+
+function fetchSceneInfo(sceneIdList) {
+  loading.value = true;
+  sceneInfoList.value = [];
+  checkList.value = [];
+  sceneIdList.forEach((sid) => {
+    sceneApi
+      .getSceneDetail(sid)
+      .then((res) => {
+        sceneInfoList.value.push(res.data);
+        checkList.value.push(sceneInfoList.value.length - 1);
+
+        //鍦烘櫙
+        // if (res.data.scense) sceneInfoList.value = res.data.scense;
+        // if (res.data.subScene) {
+        //   formSubScene.value = res.data.subScene;
+        // } else {
+        //   formSubScene.value = {
+        //     sGuid: formScene.value.guid
+        //   };
+        // }
+      })
+      .finally(() => {
+        loading.value = false;
+        scrollbarRef.value.setScrollTop(0);
+      });
+  });
+}
+
+function handleDialogChange(value) {
+  emits('update:modelValue', value);
+}
+
+// 鏍煎紡鍖栧満鏅俊鎭紝鐢熸垚鍙傛暟缁撴瀯
+function parseParam() {
+  const selected = sceneInfoList.value.filter((v, i) => {
+    return checkList.value.indexOf(i) != -1;
+  });
+  const param = selected.map((v) => {
+    switch (v.scense.typeid) {
+      // 宸ュ湴
+      case 1:
+        return {
+          type: v.scense.typeid,
+          params: {
+            district: v.scense.districtname,
+            name: v.scense.name,
+            employerUnit: v.scense.csEmployerUnit,
+            constructionUnit: v.subScene.csConstructionUnit,
+            timeRange: v.subScene.csStartTime
+              ? `${v.subScene.csStartTime}鑷�${v.subScene.csEndTime}`
+              : undefined,
+            stage: v.subScene.siExtension1,
+            contacts: v.subScene.csConstructionContacts,
+            contactsTel: v.subScene.csConstructionContactsTel,
+            location: v.scense.location
+          }
+        };
+      // 椁愰ギ
+      case 5:
+        return {
+          type: v.scense.typeid,
+          params: {
+            district: v.scense.districtname,
+            location: v.scense.location,
+            name: v.scense.name,
+            contacts: v.scense.contacts,
+            contactsTel: v.scense.contactst
+          }
+        };
+      // default:
+      //   v.invalid = true;
+      //   return undefined;
+    }
+  });
+
+  param.forEach((p) => {
+    for (const key in p.params) {
+      let value = p.params[key];
+      if (value == undefined) {
+        // 鑻ュ睘鎬х己澶憋紝鍒欐敼涓�20涓┖鏍肩锛屽搴攚ord涓�10涓腑鏂囧瓧绗︾殑闀垮害
+        p.params[key] = '                    ';
+      }
+    }
+  });
+
+  return param;
+}
+
+// 鏍规嵁鍦烘櫙绫诲瀷锛岀敓鎴愬搴旂殑word鏂囨。
+function generateDoc(param) {
+  param.forEach((p) => {
+    let template, _param;
+    switch (p.type) {
+      // 宸ュ湴
+      case 1:
+        template = '/宸ュ湴宸℃煡鍗曟嵁妯℃澘.docx';
+        _param = p.params;
+        break;
+      // 椁愰ギ
+      case 5:
+        break;
+      default:
+        break;
+    }
+
+    exportDocx(template, _param, `${_param.name}宸℃煡鍗曟嵁.docx`).finally(
+      () => (docLoading.value = false)
+    );
+  });
+}
+
+// 涓嬭浇word鏂囨。
+function download(file) {}
+
+// 鎵撳嵃word鏂囨。
+function print(file) {}
+
+function filePrepare() {
+  const param = parseParam();
+  if (param) {
+    return generateDoc(param);
+  }
+}
+
+// 鐐瑰嚮涓嬭浇鎸夐挳鎿嶄綔
+function handelDownload() {
+  const file = filePrepare();
+  if (file) {
+    download(file);
+  }
+}
+
+// 鐐瑰嚮鎵撳嵃鎸夐挳鎿嶄綔
+function handelPrint() {
+  const file = filePrepare();
+  if (file) {
+    print(file);
+  }
+}
+
+// 鍙栨秷鎿嶄綔
+function cancel() {
+  // 鍏抽棴瀵硅瘽妗�
+  handleDialogChange(false);
+}
+</script>
+<style scoped>
+.checkbox {
+  border: var(--el-border);
+  padding: 8px;
+}
+.checkbox-invalid {
+  border-color: var(--el-color-error);
+}
+:deep(.el-checkbox) {
+  height: auto;
+}
+</style>
diff --git a/src/views/fysp/task/TaskManage.vue b/src/views/fysp/task/TaskManage.vue
index f844b5e..24129b6 100644
--- a/src/views/fysp/task/TaskManage.vue
+++ b/src/views/fysp/task/TaskManage.vue
@@ -202,10 +202,16 @@
   computed: {
     // 鎬讳换鍔$姸鎬佺粺璁�
     taskStatus() {
+      let total = 0,
+        inspected = 0;
+      this.curMonitorObjList.forEach((obj) => {
+        total += parseInt(obj.monitornum);
+        inspected += obj.extension1 ? parseInt(obj.extension1) : 0;
+      });
       return [
-        { name: '鍦烘櫙鏁�', value: 100 },
-        { name: '鏈贰鏌�', value: 0 },
-        { name: '宸插贰鏌�', value: 0 }
+        { name: '鍦烘櫙鏁�', value: total },
+        { name: '鏈贰鏌�', value: total - inspected },
+        { name: '宸插贰鏌�', value: inspected }
       ];
     }
   },
diff --git a/src/views/fysp/task/components/CompMonitorObjEdit.vue b/src/views/fysp/task/components/CompMonitorObjEdit.vue
index 517f03b..cccd5ea 100644
--- a/src/views/fysp/task/components/CompMonitorObjEdit.vue
+++ b/src/views/fysp/task/components/CompMonitorObjEdit.vue
@@ -274,7 +274,7 @@
       });
     },
     deleteMov(item) {
-      if (item.extension1) {
+      if (!(item.extension1 == null || item.extension1 == undefined || item.extension1 == '0')) {
         ElMessage({
           message: '宸茬洃绠″満鏅棤娉曠Щ闄�',
           type: 'error'
diff --git a/src/views/fysp/task/components/CompSubTaskList.vue b/src/views/fysp/task/components/CompSubTaskList.vue
index 03ef062..2ff7199 100644
--- a/src/views/fysp/task/components/CompSubTaskList.vue
+++ b/src/views/fysp/task/components/CompSubTaskList.vue
@@ -1,13 +1,22 @@
 <template>
   <el-row justify="space-between">
     <el-text>鍗曟棩璁″垝</el-text>
-    <el-button
-      v-show="create && data && data.length > 0"
-      type="success"
-      size="small"
-      @click="add"
-      >浠诲姟璋冩暣</el-button
-    >
+    <div>
+      <el-button
+        type="success"
+        size="small"
+        plain
+        @click="handleInspectFileDownload"
+        >鍗曟嵁涓嬭浇</el-button
+      >
+      <el-button
+        v-show="create && data && data.length > 0"
+        type="success"
+        size="small"
+        @click="add"
+        >浠诲姟璋冩暣</el-button
+      >
+    </div>
   </el-row>
   <el-divider />
   <div>
@@ -70,11 +79,13 @@
       @cancel="dialogVisible = false"
     ></CompSubTaskEdit>
   </el-dialog>
+  <SceneInspectFile v-model="downloadDialog" :value="downloadSceneList"></SceneInspectFile>
 </template>
 <script setup>
 import { ref, computed, watch, onMounted, onUnmounted } from 'vue';
 import { ElMessageBox, ElNotification, ElMessage } from 'element-plus';
 import CompSubTaskEdit from './CompSubTaskEdit.vue';
+import SceneInspectFile from "@/views/fysp/scene/SceneInspectFile.vue";
 import subtaskApi from '@/api/fysp/subtaskApi';
 
 const props = defineProps({
@@ -86,12 +97,14 @@
   // 鏄惁鏄剧ず娣诲姞浠诲姟鎸夐挳
   create: Boolean,
   loading: Boolean,
-  createLoading:Boolean,
+  createLoading: Boolean
 });
 
 const dialogVisible = ref(false);
 const activeItem = ref(null);
 const data = computed(() => props.modelValue);
+const downloadDialog = ref(false);
+const downloadSceneList = ref([])
 
 const emit = defineEmits(['submit', 'add', 'remove', 'update:modelValue']);
 
@@ -102,7 +115,7 @@
       cancelButtonText: '鍙栨秷',
       type: 'warning'
     }).then(() => {
-      return subtaskApi.deleteSubtask(item.stguid).then(res=>{
+      return subtaskApi.deleteSubtask(item.stguid).then((res) => {
         if (res == 1) {
           const index = data.value.indexOf(item);
           data.value.splice(index, 1);
@@ -110,9 +123,9 @@
           emit('update:modelValue', data.value);
           emit('remove', item);
         } else {
-          Promise.reject('鍒犻櫎宸℃煡浠诲姟澶辫触')
+          Promise.reject('鍒犻櫎宸℃煡浠诲姟澶辫触');
         }
-      })
+      });
     });
   }
 }
@@ -139,4 +152,9 @@
 onUnmounted(() => {
   dialogVisible.value = false;
 });
+
+function handleInspectFileDownload() {
+  downloadSceneList.value = data.value.map(v=>v.scenseid)
+  downloadDialog.value = true
+}
 </script>

--
Gitblit v1.9.3