ソースを参照

Merge branch 'xj' of http://192.168.0.115:3000/bill/public-fuse into xj

gemercheung 1 年間 前
コミット
7959d77bd1

+ 2 - 2
.env.development

@@ -1,6 +1,6 @@
 VITE_APP_APP="fire"
-# VITE_SEVER_URL="https://192.168.0.25"
-VITE_SEVER_URL="https://xj-mix3d.4dkankan.com"
+VITE_SEVER_URL="https://192.168.0.25"
+# VITE_SEVER_URL="https://xj-mix3d.4dkankan.com"
 VITE_DEVCODE_URL="https://192.168.0.25/code"
 VITE_SWKK_URL="https://test.4dkankan.com"
 VITE_SERVICE_URL="https://test.4dkankan.com"

+ 1 - 0
src/app/criminal/view/example/index.vue

@@ -46,6 +46,7 @@
       >
         <CaseEditMenu
           :caseId="row.caseId"
+          :title="row.caseTitle"
           :prevMenu="[
             { key: 'info', label: '编辑案件', onClick: () => editHandler(row) },
           ]"

+ 1 - 0
src/app/fire/view/dispatch/index.vue

@@ -54,6 +54,7 @@
         </template>
         <template v-else>
           <CaseEditMenu
+            :title="row.projectSn"
             :prev-menu="[
               {
                 key: 'info',

+ 42 - 13
src/app/mirror/App.vue

@@ -31,6 +31,7 @@
       <el-input
         class="title"
         type="textarea"
+        maxlength="100"
         :autosize="{ minRows: 1, maxRows: 4 }"
         v-model="project.title"
       />
@@ -60,6 +61,7 @@
               :autosize="{ minRows: 3 }"
               v-model="row.name"
               :row="3"
+              maxlength="1000"
               placeholder="概括拍摄内容"
             />
           </template>
@@ -71,6 +73,7 @@
               class="gray"
               type="textarea"
               :autosize="{ minRows: 3 }"
+              maxlength="1000"
               v-model="row.desc"
               :row="3"
               placeholder="详细描述分镜"
@@ -84,9 +87,11 @@
               ref="upload"
               v-model:file-list="row.fileList"
               class="list-upload-style"
+              :accept="DrawFormats.toString()"
               :class="{
                 activefileList: row.fileList && row.fileList.length == 1,
               }"
+            :before-upload="beforeUpload"
               list-type="picture-card"
               :action="uploadFileUrl"
               :on-success="handleUploadSuccess"
@@ -130,6 +135,7 @@
           <template v-slot="{ row }">
             <el-input
               class="gray"
+              maxlength="1000"
               type="textarea"
               :autosize="{ minRows: 3 }"
               v-model="row.words"
@@ -143,6 +149,7 @@
             <div class="marksDiv">
               <el-input
                 class="gray"
+                  maxlength="1000"
                 type="textarea"
                 :autosize="{ minRows: 3 }"
                 v-model="row.marks"
@@ -195,9 +202,9 @@ link.setAttribute("href", linkIco);
 
 const caseId = ref(null);
 const project = reactive({
-  title: "",
+  title: '我的脚本',
 });
-
+const DrawFormats = [".jpg", ".jpeg", ".png",".mp4",".m4v",".mp3",".aac", ".wav"]
 const isNotFound = ref(false);
 
 const dialogImageUrl = ref("");
@@ -253,14 +260,25 @@ const columns = ref([
   { prop: "words", label: "台词文案" },
   { prop: "marks", label: "备注" },
 ]);
+const beforeUpload = async (file: File) => {
+    const fileType = file.name
+      .substring(file.name.lastIndexOf("."))
+      .toUpperCase();
+    if (!DrawFormats.some((type) => type.toUpperCase() === fileType)) {
+      ElMessage.error(`请上传${DrawFormats}格式的文件`);
+      return false;
+    } else {
+      return true;
+    }
+  };
 const checkSourceIsVideo = computed(() => (url: string) => {
-  return url.includes(".mp4");
+  return url.includes(".mp4") || url.includes(".m4v");
 });
 const checkSourceIsAudio = computed(() => (url: string) => {
-  return url.includes(".mp3");
+  return url.includes(".mp3") || url.includes(".aac") || url.includes(".wav");
 });
 const checkSourceIsImage = computed(() => (url: string) => {
-  return url.includes(".jpg") || url.includes(".png") || url.includes(".gif");
+  return url.includes(".jpg") || url.includes(".png") || url.includes(".jpeg") || url.includes(".gif");
 });
 
 const getCoverUrl = computed(() => (url: string) => {
@@ -269,7 +287,7 @@ const getCoverUrl = computed(() => (url: string) => {
     //   return (
     //     url + "?x-oss-process=video/snapshot,t_0,f_jpg,w_0,h_0,m_fast,ar_auto"
     //   );
-    case url.includes(".mp3") || url.includes(".wmv"):
+    case url.includes(".mp3") ||  url.includes(".aac") || url.includes(".wmv") || url.includes(".wav"):
       return musicHeadphones;
     default:
       return url;
@@ -302,16 +320,17 @@ onMounted(async () => {
 function getCaseScriptList() {
   getCaseScriptInfo(caseId.value)
     .then((res) => {
-      project.title = res.name;
-      data.list = res.content || [];
-      data.newSortList = res.content || [];
+      project.title = res && res.name || '我的脚本';
+      data.list = res && res.content || [];
+      data.newSortList = res && res.content || [];
       const idList = data.list.map((ele) => ele.id);
       active.value = idList.length == 0 ? 0 : Math.max.apply(null, idList) || 1;
       sortList.value = data.list.map((_, index) => index);
       console.log("getCaseScriptList", idList, active.value);
       Array.from(data.list).forEach((item) => {
         item.fileList.forEach(async (file: File, index) => {
-          if ((file as any).url.includes(".mp4")) {
+          console.log("getCaseScriptList", file);
+          if (file && !file.cover &&( (file as any).url.includes(".mp4") || (file as any).url.includes(".m4v"))) {
             const res = await CaseScriptGetCover((file as any).url);
             (item.fileList[index] as any).cover = res;
           } else {
@@ -328,7 +347,10 @@ function handleAdd() {
   // let content = sortList.value.map((index) => data.list[index]);
   // data.list.length = 0;
   // Object.assign(data.list, content);
-  console.log("add", data.newSortList);
+  console.log("add", data.list, data.newSortList);
+  if(data.newSortList.length == 0){
+
+  }
   for (var i = 1; i <= addLine.value; i++) {
     console.log(i);
     data.newSortList.push({
@@ -366,11 +388,18 @@ const handlePictureCardPreview = (file: UploadFile) => {
 
 const saveProject = () => {
   // let content = sortList.value.map((index) => data.list[index]);
+  let apiDataList = data.newSortList.map((item) => {
+    let asData = data.list.find(ele => ele.id === item.id) || {};
+    return {
+      ...item,
+      ...asData,
+    }
+  });
   console.log("saveProject", data.list, data.newSortList);
   CaseScriptSaveOrUpdate({
     caseId: caseId.value,
     name: project.title,
-    content: data.newSortList,
+    content: apiDataList,
   }).then((res) => {
     console.log("saveProject");
     ElMessage.success("保存成功");
@@ -379,7 +408,7 @@ const saveProject = () => {
 async function handleUploadSuccess(response: any, uploadFile: UploadFile) {
   uploadFile.url = response.data;
   console.log("handleUploadSuccess", uploadFile.url);
-  if (uploadFile.url!.includes(".mp4")) {
+  if (uploadFile.url!.includes(".mp4") || uploadFile.url!.includes(".m4v")) {
     const res = await CaseScriptGetCover(uploadFile.url!);
     (uploadFile as any).cover = res;
   } else {

+ 6 - 1
src/request/config.ts

@@ -27,6 +27,7 @@ import {
   getCaseScriptSaveOrUpdateUrl,
   cameraVersionUpload,
   cameraVersionAppUpload,
+  getCaseHasDownloadProcess,
 } from "./urls";
 
 // 不需要登录就能请求的接口
@@ -73,4 +74,8 @@ export const successCode = [0, "000000", 200];
 // baseURL
 export const baseURL = import.meta.env.DEV ? "/api" : "";
 
-export const notOpenUrls: string[] = [uploadModel, getDownloadProcess];
+export const notOpenUrls: string[] = [
+  uploadModel,
+  getDownloadProcess,
+  getCaseHasDownloadProcess,
+];

+ 6 - 5
src/request/urls.ts

@@ -199,7 +199,6 @@ export const caseExtractDetailExport = "/fusion-xj/caseExtractDetail/downDocx";
 export const getCaseImgTag = "/fusion-xj/caseImgTag/info";
 export const saveCaseImgTag = "/fusion-xj/caseImgTag/saveOrUpdate";
 
-
 // 火调链接地址设置密码
 export const setCasePsw = "/fusion-xj/web/fireProject/updateRandomCode";
 export const getCasePsw = "/fusion-xj/web/fireProject/getRandCode";
@@ -227,6 +226,10 @@ export const uploadAttachFile = "/web/fireProject/uploadFile";
 export const uploadAttachImage = "/web/fireProject/uploadImage";
 /** ------------------------------------------ */
 
+export const checkCaseHasDownload = `/fusion-xj/offlinePackage/checkDown`;
+export const getCaseHasDownloadProcess = `/fusion-xj/offlinePackage/process`;
+export const downloadCaseScene = "/fusion-xj/offlinePackage/down";
+
 // 下载校验
 export const checkHasDownload = "/fusion-xj/sceneDownLog/checkDownLoad";
 // 下载获取进度条
@@ -248,12 +251,10 @@ export const caseApiUpdateSort = "/fusion-xj/caseImg/updateSort";
 export const getSysSetting = `/fusion-xj/systemSetting/info`;
 export const updateSysSetting = `/fusion-xj/systemSetting/save`;
 
-
 // 脚本管理
 export const getCaseScriptInfoUrl = `/fusion-xj/caseScript/info`;
 export const getCaseScriptSaveOrUpdateUrl = `/fusion-xj/caseScript/saveOrUpdate`;
-export const getVideoCover = `/fusion-xj/caseScript/ffmpegVideoImage`
-
+export const getVideoCover = `/fusion-xj/caseScript/ffmpegVideoImage`;
 
 // 固件管理
 export const cameraVersionUpload = `/fusion-xj/cameraVersion/addAndUpload`;
@@ -265,4 +266,4 @@ export const cameraVersionList = `/fusion-xj/cameraVersion/list`;
 export const cameraVersionAppUpload = `/fusion-xj/cameraVersionApp/addAndUpload`;
 export const cameraVersionAppUpdate = `/fusion-xj/cameraVersionApp/update`;
 export const cameraVersionAppDelete = `/fusion-xj/cameraVersionApp/delete`;
-export const cameraVersionAppList = `/fusion-xj/cameraVersionApp/list`;
+export const cameraVersionAppList = `/fusion-xj/cameraVersionApp/list`;

+ 1 - 1
src/view/case/addPhotoFile.vue

@@ -30,7 +30,7 @@
               <el-icon><Document /></el-icon>
               <span class="name">{{ file.name }}</span>
             </div>
-            <el-icon @click.stop="removeFile()"><Close /></el-icon>
+            <el-icon v-if="!caseFile.id" @click.stop="removeFile()"><Close /></el-icon>
           </div>
         </template>
       </el-upload>

+ 4 - 1
src/view/case/addPhotoFileAll.vue

@@ -14,6 +14,7 @@
         v-model:file-list="fileList"
         :http-request="httpsApi"
         :on-preview="previewFile"
+        :on-exceed="handleExceed"
         :accept="photoFormats"
         :on-remove="handleRemove"
       >
@@ -88,7 +89,9 @@ const { size, previewFile, file, accept, percentage, format } = useUpload({
   maxSize: maxFileSize,
   formats: DrawFormats,
 });
-
+const handleExceed = () => {
+  return ElMessage.error(`最大上传数量为10个`);
+}
 const upload = async (file: File) => {
     const fileType = file.name
       .substring(file.name.lastIndexOf("."))

+ 142 - 0
src/view/case/download.vue

@@ -0,0 +1,142 @@
+<template>
+  <!-- hideFloor: state === State.package -->
+  <div>
+    <div class="title">
+      {{ stateTitle[state] }}
+    </div>
+
+    <div v-if="state === State.package">
+      <div
+        class="text"
+        style="display: flex; justify-content: space-between; margin-top: 15px"
+      >
+        <span>{{ filename }}</span>
+        <span>{{ percent }}%</span>
+      </div>
+      <div style="pointer-events: none">
+        <el-slider v-model="percent" :show-tooltip="false" />
+      </div>
+    </div>
+    <div v-else-if="state === State.readDown">
+      <span>正在下载中……</span>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed, onMounted, onUnmounted, ref } from "vue";
+import saveAs from "@/util/file-serve";
+import {
+  checkCaseHasDownload as checkHasDownload,
+  getCaseHasDownloadProcess as getDownloadProcess,
+  downloadCaseScene as downloadScene,
+  axios,
+} from "@/request";
+import { ElLoading, ElMessage } from "element-plus";
+import { QuoteScene, SceneType } from "@/store/scene";
+import { QuiskExpose } from "@/helper/mount";
+
+const props = defineProps<{ caseId: number; title: string }>();
+enum State {
+  uncreate,
+  package,
+  readDown,
+}
+const getState = (type: number) => {
+  const stateTypes = [
+    { codes: [0, 2], state: State.uncreate },
+    { codes: [1], state: State.package },
+    { codes: [3], state: State.readDown },
+  ];
+  return (
+    stateTypes.find((stateType) => stateType.codes.includes(type))?.state ||
+    State.uncreate
+  );
+};
+
+const state = ref<State>(State.uncreate);
+const count = ref<number>(0);
+const filename = ref<string>(props.title + ".zip");
+const downloadURL = ref<string>();
+const percent = ref(0);
+
+const stateTitle = {
+  [State.uncreate]: "下载案件离线数据包,可在本地运行查看。",
+  [State.package]: "正在打包案件离线数据",
+  [State.readDown]: filename.value,
+};
+
+const params = {
+  caseId: props.caseId,
+};
+// 初始化
+const initial = async () => {
+  const res = await axios.get(checkHasDownload, { params });
+  state.value = getState(res.data.downloadStatus);
+  count.value = res.data.count;
+  downloadURL.value = res.data.downloadUrl;
+
+  if (state.value === State.uncreate) {
+    const downRes = await axios.get(downloadScene, { params });
+    state.value = getState(downRes.data.downloadStatus);
+    // const unCountFlag =
+    //   count.value == 0 ||(await axios.get(downloadScene, { params })).data.downloadStatus !== 1;
+    if (state.value === State.uncreate) {
+      ElMessage.error("下载失败,请联系管理员");
+      throw "暂无剩余下载次数";
+    }
+  }
+
+  if (state.value === State.package) {
+    await new Promise<void>((resolve) => requestUpdateURL(resolve));
+  } else {
+    downloadURL.value = res.data.downloadUrl;
+  }
+};
+
+// 下载
+const download = () => {
+  if (!downloadURL.value) {
+    ElMessage.error("下载链接未生成,请稍等!");
+    throw "下载链接未生成,请稍等!";
+  } else {
+    if (!downloadURL.value.startsWith("/")) {
+      downloadURL.value = "/" + downloadURL.value;
+    }
+    console.error("downloadURL.value", downloadURL.value);
+    return saveAs(downloadURL.value, filename.value);
+  }
+};
+
+// 进度请求
+let timer: any;
+const requestUpdateURL = async (callback: () => void) => {
+  const res = await axios.get(getDownloadProcess, { params });
+
+  percent.value = parseInt(res.data.percent);
+
+  downloadURL.value = percent.value === 100 ? res.data.url : null;
+  if (downloadURL.value) {
+    state.value = State.readDown;
+    callback();
+  } else {
+    timer = setTimeout(() => requestUpdateURL(callback), 1000);
+  }
+};
+
+onUnmounted(() => clearTimeout(timer));
+
+defineExpose<QuiskExpose>({
+  submit: async () => {
+    await initial();
+    const loading = ElLoading.service({
+      lock: true,
+      text: "下载中",
+      background: "rgba(255, 255, 255, 0.4)",
+    });
+    await download();
+    loading.close();
+    ElMessage.success("下载完成");
+  },
+});
+</script>

+ 45 - 8
src/view/case/draw/selectMapImage.vue

@@ -5,9 +5,19 @@
         <el-button :icon="Search" />
       </template>
     </el-input>
-    <div class="search-result" v-show="keyword" ref="resultEl"></div>
+    <div class="rrr">
+      <div class="search-result" v-show="keyword && showSearch" ref="resultEl"></div>
+      <div class="search-sh" v-show="keyword">
+        <el-button style="width: 100%" @click="showSearch = !showSearch">
+          {{ showSearch ? "收起" : "展开" }}搜索结果
+        </el-button>
+      </div>
+    </div>
   </div>
-  <div class="def-select-map" ref="mapEl"></div>
+  <div class="def-select-map-layout">
+    <div class="def-select-map" ref="mapEl"></div>
+  </div>
+
   <div class="def-map-info" v-if="info">
     <p><span>纬度</span>{{ info.lat }}</p>
     <p><span>经度</span>{{ info.lng }}</p>
@@ -26,9 +36,16 @@ export type MapImage = { blob: Blob | null; search: MapInfo | null };
 type MapInfo = { lat: number; lng: number; zoom: number; text: string };
 
 const keyword = ref("");
+const showSearch = ref(true);
 const info = ref<MapInfo>();
 const searchInfo = ref<MapInfo>();
 
+watchEffect(() => {
+  if (keyword.value) {
+    showSearch.value = true;
+  }
+});
+
 const mapEl = ref<HTMLDivElement>();
 const resultEl = ref<HTMLDivElement>();
 const searchAMap = ref<any>();
@@ -68,7 +85,9 @@ watchEffect(async (onCleanup) => {
 
   placeSearch.on("listElementClick", (e) => {
     setSearch(e.data);
+    showSearch.value = false;
   });
+
   placeSearch.on("complete", function (result) {
     setTimeout(() => {
       const markers = map.getAllOverlays("marker");
@@ -91,8 +110,12 @@ watchEffect(async (onCleanup) => {
     };
   };
   //绑定地图移动与缩放事件
-  map.on("moveend", () => (info.value = getMapInfo()));
-  map.on("zoomend", () => (info.value = getMapInfo()));
+  map.on("moveend", () => {
+    info.value = getMapInfo();
+  });
+  map.on("zoomend", () => {
+    info.value = getMapInfo();
+  });
   searchAMap.value = placeSearch;
 
   onCleanup(() => {
@@ -130,11 +153,15 @@ defineExpose<QuiskExpose>({
   z-index: 2;
 }
 
-.search-result {
+.rrr {
   position: absolute;
   left: 0;
   right: 0;
   z-index: 1;
+}
+
+.search-sh,
+.search-result {
   overflow: hidden;
 
   &.show {
@@ -160,9 +187,19 @@ defineExpose<QuiskExpose>({
   }
 }
 
-.def-select-map {
-  width: 540px;
-  height: 390px;
+.def-select-map-layout {
+  --scale: 1.5;
+  width: 100%;
+  padding-top: calc((390 / 540) * 100%);
+  position: relative;
   z-index: 1;
 }
+
+.def-select-map {
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+}
 </style>

+ 14 - 1
src/view/case/editMenu.vue

@@ -20,13 +20,14 @@
 <script setup lang="ts">
 import { computed } from "vue";
 import { getFuseCodeLink, checkScenesOpen, MenuItem, getSWKKSyncLink } from "./help";
-import { showCaseScenes, addCaseScenes, shareCase } from "./quisk";
+import { showCaseScenes, addCaseScenes, shareCase, downloadCase } from "./quisk";
 import { RouteName, router } from "@/router";
 import { copyCase, getCaseSceneList } from "@/store/case";
 import { alert } from "@/helper/message";
 
 const props = defineProps<{
   caseId: number;
+  title: string;
   prevMenu?: MenuItem[];
   lastMenu?: MenuItem[];
 }>();
@@ -103,6 +104,18 @@ const menus = computed(() => {
         window.open(`mirror.html?caseId=${caseId}`);
       },
     },
+    {
+      key: "download",
+      label: "下载",
+      onClick: async () => {
+        const scenes = await getCaseSceneList(caseId);
+        if (!scenes.length) {
+          alert("当前案件下无场景,请先添加场景。");
+        } else {
+          downloadCase({ caseId, title: props.title });
+        }
+      },
+    },
     ...(props.lastMenu || []).map((item) => ({
       ...item,
       onClick: () => item.onClick(caseId),

+ 20 - 2
src/view/case/quisk.ts

@@ -11,6 +11,8 @@ import SelectFuseImage, { FuseImage } from "./draw/selectFuseImage.vue";
 import SelectMapImage, { MapImage } from "./draw/selectMapImage.vue";
 import { quiskMountFactory } from "@/helper/mount";
 import { nextTick } from "vue";
+import { axios, checkCaseHasDownload, checkHasDownload } from "@/request";
+import CaseDownload from "./download.vue";
 
 export const addCaseFile = quiskMountFactory(AddCaseFile, {
   title: "上传附件",
@@ -27,7 +29,6 @@ export const addCaseImgFileAll = quiskMountFactory(addPhotoFileAll, {
   width: 500,
 });
 
-
 export const addCaseScenes = quiskMountFactory(AddScenes, {
   title: "添加场景",
   width: 800,
@@ -57,10 +58,27 @@ export const selectFuseImage = quiskMountFactory(SelectFuseImage, {
 
 export const selectMapImage = quiskMountFactory(SelectMapImage, {
   title: "选择地址",
-  width: 588,
+  width: 800,
 })<MapImage>;
 
 export const shareCase = quiskMountFactory(ShareCase, {
   title: "分享",
   enterText: "复制链接及密码",
 })<string>;
+
+export type caseDownloadProps = { caseId: number; title: string };
+export const downloadCase = async (props: caseDownloadProps) => {
+  const params = {
+    caseId: props.caseId,
+  };
+  const res = await axios.get(checkCaseHasDownload, { params });
+  const hideFloor = Number(res.data.downloadStatus) !== 3;
+
+  const sceneDownloadDialog = quiskMountFactory(CaseDownload, {
+    title: "案件离线包下载",
+    width: 500,
+    hideFloor: hideFloor,
+    enterText: "下 载",
+  });
+  return await sceneDownloadDialog(props);
+};

+ 6 - 0
vite.config.ts

@@ -54,6 +54,12 @@ export default ({ mode }: any) =>
           changeOrigin: true,
           rewrite: (path) => path.replace(new RegExp(`^/api`), ""),
         },
+        "/oss": {
+          secure: false,
+          // target: dev ? devUrl : "mix3d.4dkankan.com",
+          target: loadEnv(mode, process.cwd()).VITE_SEVER_URL,
+          changeOrigin: true,
+        },
         "/fusion-xj": {
           secure: false,
           // target: dev ? devUrl : "mix3d.4dkankan.com",