App.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. <template>
  2. <div class="mirror-setting">
  3. <!-- 图片预览 -->
  4. <el-dialog v-model="dialogVisible">
  5. <img
  6. v-if="checkSourceIsImage(dialogImageUrl)"
  7. style="width: 100%; height: 500px; object-fit: scale-down"
  8. w-full
  9. :src="dialogImageUrl"
  10. alt="Preview Image"
  11. />
  12. <video
  13. v-if="checkSourceIsVideo(dialogImageUrl)"
  14. style="width: 100%"
  15. w-full
  16. controls
  17. :src="dialogImageUrl"
  18. />
  19. <audio
  20. v-if="checkSourceIsAudio(dialogImageUrl)"
  21. style="width: 100%"
  22. w-full
  23. controls
  24. :src="dialogImageUrl"
  25. />
  26. </el-dialog>
  27. <!-- 分镜配置 -->
  28. <div class="project-title">
  29. <el-input
  30. class="title"
  31. type="textarea"
  32. :autosize="{ minRows: 1, maxRows: 4 }"
  33. v-model="project.title"
  34. />
  35. <el-button type="primary" @click="saveProject">保存</el-button>
  36. </div>
  37. <div class="content">
  38. <el-table
  39. :key="data.list.length"
  40. class="main-table"
  41. key="id"
  42. border
  43. v-dragable="dragOptions"
  44. :data="data.list"
  45. header-row-class-name="t-head"
  46. header-cell-class-name="t-cell"
  47. >
  48. <!-- <template v-for="item in columns" :key="item.prop">
  49. <el-table-column :prop="item.prop" :label="item.label" />
  50. 大纲
  51. </template>
  52. :on-preview="handlePictureCardPreview" :on-remove="handleRemove"
  53. -->
  54. <el-table-column prop="name" label="大纲">
  55. <template v-slot="{ row }">
  56. <el-input
  57. type="textarea"
  58. :autosize="{ minRows: 3 }"
  59. v-model="row.name"
  60. :row="3"
  61. placeholder="概括拍摄内容"
  62. />
  63. </template>
  64. </el-table-column>
  65. <el-table-column prop="desc" label="分镜描述">
  66. <template v-slot="{ row }">
  67. <el-input
  68. class="gray"
  69. type="textarea"
  70. :autosize="{ minRows: 3 }"
  71. v-model="row.desc"
  72. :row="3"
  73. placeholder="详细描述分镜"
  74. />
  75. </template>
  76. </el-table-column>
  77. <!-- show-overflow-tooltip -->
  78. <el-table-column prop="clip" label="已拍摄片段">
  79. <template v-slot="{ row }">
  80. <el-upload
  81. ref="upload"
  82. v-model:file-list="row.fileList"
  83. class="list-upload-style"
  84. :class="{
  85. activefileList: row.fileList && row.fileList.length == 1,
  86. }"
  87. list-type="picture-card"
  88. :action="uploadFileUrl"
  89. :on-success="handleUploadSuccess"
  90. :limit="1"
  91. >
  92. <div
  93. class="uploadImg"
  94. v-if="row.fileList && row.fileList.length == 0"
  95. >
  96. <el-icon><Plus /></el-icon>
  97. </div>
  98. <template #file="{ file }">
  99. <div style="width: 100%">
  100. <img
  101. class="el-upload-list__item-thumbnail"
  102. :src="getCoverUrl(file.url)"
  103. alt=""
  104. />
  105. <span class="el-upload-list__item-actions">
  106. <span
  107. class="el-upload-list__item-preview"
  108. @click="handlePictureCardPreview(file)"
  109. >
  110. <el-icon><zoom-in /></el-icon>
  111. </span>
  112. <span
  113. class="el-upload-list__item-delete"
  114. @click="handleRemove(row)"
  115. >
  116. <el-icon><Delete /></el-icon>
  117. </span>
  118. </span>
  119. </div>
  120. </template>
  121. </el-upload>
  122. </template>
  123. </el-table-column>
  124. <el-table-column prop="words" label="台词文案">
  125. <template v-slot="{ row }">
  126. <el-input
  127. class="gray"
  128. type="textarea"
  129. :autosize="{ minRows: 3 }"
  130. v-model="row.words"
  131. placeholder="点击输入台词"
  132. />
  133. </template>
  134. </el-table-column>
  135. <el-table-column prop="marks" label="备注">
  136. <template v-slot="{ row, $index }">
  137. <div class="marksDiv">
  138. <el-input
  139. class="gray"
  140. type="textarea"
  141. :autosize="{ minRows: 3 }"
  142. v-model="row.marks"
  143. placeholder="点击输入内容"
  144. />
  145. <span
  146. class="table-delete"
  147. @click="handleTableRemove($index, row)"
  148. >
  149. <el-icon><Delete /></el-icon>
  150. </span>
  151. </div>
  152. </template>
  153. </el-table-column>
  154. </el-table>
  155. </div>
  156. <div class="add-handle">
  157. <el-button type="primary" @click="handleAdd">
  158. <el-icon class="el-icon--right">
  159. <Plus />
  160. </el-icon>
  161. 添加
  162. <el-input class="add-line" type="text" v-model="addLine" size="small">
  163. </el-input>
  164. </el-button>
  165. </div>
  166. </div>
  167. </template>
  168. <script lang="ts" setup>
  169. import { vDragable } from "./dragable";
  170. import { ElMessage } from "element-plus";
  171. import { reactive, ref, onMounted, computed } from "vue";
  172. import type { UploadFile } from "element-plus";
  173. import { uploadFile as uploadFileUrl } from "@/request";
  174. import {
  175. getCaseScriptInfo,
  176. CaseScriptSaveOrUpdate,
  177. } from "@/app/mirror/store/script";
  178. import linkIco from "@/assets/image/fire.ico";
  179. import musicHeadphones from "@/assets/image/music.png";
  180. const link = document.querySelector<HTMLLinkElement>("#app-icon")!;
  181. link.setAttribute("href", linkIco);
  182. const caseId = ref(null);
  183. const project = reactive({
  184. title: "我的脚本",
  185. });
  186. document.title = project.title;
  187. const dialogImageUrl = ref("");
  188. const dialogVisible = ref(false);
  189. const disabled = ref(false);
  190. const addLine = ref(1);
  191. const active = ref(1);
  192. const dragOptions = [
  193. // {
  194. // selector: "thead tr", // add drag support for column
  195. // option: {
  196. // // sortablejs's option
  197. // animation: 150,
  198. // onEnd: (evt) => {
  199. // let oldCol: any = {};
  200. // Object.assign(oldCol, columns.value[evt.oldIndex]);
  201. // columns.value.splice(evt.oldIndex, 1); // 因为新增了数据,所以要移除原来的列的index要在原来的基础上
  202. // setTimeout(() => {
  203. // columns.value.splice(evt.newIndex, 0, oldCol); // 把原来的列数据添加到新的位置,然后再从原位置移除它,触发table的重绘
  204. // }, 30);
  205. // console.log(evt.oldIndex, evt.newIndex);
  206. // },
  207. // },
  208. // },
  209. {
  210. selector: "tbody", // add drag support for row
  211. option: {
  212. // sortablejs's option
  213. animation: 150,
  214. onEnd: (evt: any) => {
  215. // let oldItem = sortList.value[evt.oldIndex];
  216. // let sortLists = sortList.value.filter(
  217. // (_, index) => index !== evt.oldIndex
  218. // );
  219. // sortLists.splice(evt.newIndex, 0, oldItem);
  220. // sortList.value = sortLists;
  221. let list = JSON.parse(JSON.stringify(data.newSortList));
  222. const target = list.splice(evt.oldIndex, 1);
  223. list.splice(evt.newIndex, 0, target[0]);
  224. data.newSortList = list;
  225. console.log(evt.oldIndex, evt.newIndex, data.newSortList, data.list);
  226. },
  227. },
  228. },
  229. ];
  230. const columns = ref([
  231. // { prop: "id", label: "ID", hidden: true, },
  232. { prop: "name", label: "大纲" },
  233. { prop: "desc", label: "分镜描述" },
  234. { prop: "clip", label: "已拍摄片段" },
  235. { prop: "words", label: "台词文案" },
  236. { prop: "marks", label: "备注" },
  237. ]);
  238. const checkSourceIsVideo = computed(() => (url: string) => {
  239. return url.includes(".mp4");
  240. });
  241. const checkSourceIsAudio = computed(() => (url: string) => {
  242. return url.includes(".mp3");
  243. });
  244. const checkSourceIsImage = computed(() => (url: string) => {
  245. return url.includes(".jpg") || url.includes(".png") || url.includes(".gif");
  246. });
  247. const getCoverUrl = computed(() => (url: string) => {
  248. switch (true) {
  249. case url.includes(".mp4"):
  250. return (
  251. url + "?x-oss-process=video/snapshot,t_0,f_jpg,w_0,h_0,m_fast,ar_auto"
  252. );
  253. case url.includes(".mp3") || url.includes(".wmv"):
  254. return musicHeadphones;
  255. default:
  256. return url;
  257. }
  258. });
  259. const data = reactive({
  260. list: [{ id: 1, name: "", desc: "", fileList: [] }],
  261. newSortList: [],
  262. });
  263. const sortList = ref([0]);
  264. onMounted(() => {
  265. caseId.value = GetRequest("caseId");
  266. getCaseScriptList();
  267. console.log("caseId", caseId); //query传参
  268. });
  269. function getCaseScriptList() {
  270. getCaseScriptInfo(caseId.value)
  271. .then((res) => {
  272. project.title = res.name;
  273. data.list = res.content;
  274. data.newSortList = res.content;
  275. const idList = data.list.map((ele) => ele.id);
  276. active.value = Math.max.apply(null, idList) || 1;
  277. sortList.value = data.list.map((_, index) => index);
  278. })
  279. .catch((err) => {
  280. console.log(err);
  281. });
  282. }
  283. function handleAdd() {
  284. // let content = sortList.value.map((index) => data.list[index]);
  285. // data.list.length = 0;
  286. // Object.assign(data.list, content);
  287. console.log("add", data.newSortList);
  288. for (var i = 1; i <= addLine.value; i++) {
  289. console.log(i);
  290. data.newSortList.push({
  291. id: active.value + 1,
  292. name: "",
  293. desc: "",
  294. fileList: [],
  295. });
  296. }
  297. active.value++;
  298. data.list = data.newSortList;
  299. sortList.value = data.list.map((_, index) => index);
  300. }
  301. const handleRemove = (data) => {
  302. data.fileList = [];
  303. };
  304. const handleTableRemove = (index, datas) => {
  305. data.newSortList = data.newSortList.filter((ele) => ele.id !== datas.id);
  306. console.log("saveProject", data.newSortList);
  307. data.list = data.newSortList;
  308. // let content = sortList.value.map((index) => data.list[index]);
  309. // data.list.length = 0;
  310. // content.splice(index, 1);
  311. // Object.assign(data.list, content);
  312. // sortList.value = content.map((_, index) => index);
  313. console.log("saveProject", index, datas, data.list);
  314. };
  315. const handlePictureCardPreview = (file: UploadFile) => {
  316. dialogImageUrl.value = file.url!;
  317. dialogVisible.value = true;
  318. };
  319. const saveProject = () => {
  320. // let content = sortList.value.map((index) => data.list[index]);
  321. console.log("saveProject", data.list, data.newSortList);
  322. CaseScriptSaveOrUpdate({
  323. caseId: caseId.value,
  324. name: project.title,
  325. content: data.newSortList,
  326. }).then((res) => {
  327. console.log("saveProject");
  328. });
  329. };
  330. function handleUploadSuccess(response: any, uploadFile: UploadFile) {
  331. uploadFile.url = response.data;
  332. console.log(response, uploadFile);
  333. }
  334. function GetRequest(value) {
  335. var url = decodeURI(window.location.search); //?id="123456"&name="www";
  336. var object = {};
  337. if (url.indexOf("?") != -1) {
  338. //url中存在问号,也就说有参数。
  339. var str = url.substr(1); //得到?后面的字符串
  340. var strs = str.split("&"); //将得到的参数分隔成数组[id="123456",name="www"];
  341. for (var i = 0; i < strs.length; i++) {
  342. object[strs[i].split("=")[0]] = strs[i].split("=")[1]; //得到{id:'123456',name:'www'}
  343. }
  344. }
  345. return object[value];
  346. }
  347. </script>
  348. <style lang="scss" scoped></style>
  349. <style lang="scss">
  350. body,
  351. #app {
  352. margin: 0;
  353. padding: 0;
  354. }
  355. .mirror-setting {
  356. width: 100%;
  357. min-height: 100%;
  358. padding-top: 80px;
  359. min-height: calc(100vh - 80px);
  360. margin: 0 auto;
  361. background: #eee;
  362. .content {
  363. margin: 0 auto;
  364. display: flex;
  365. padding: 0 40px;
  366. }
  367. .t-head {
  368. border: 1px solid #ddd;
  369. /* padding: 10px; */
  370. /* display: flex; */
  371. position: relative;
  372. background-color: #eee;
  373. }
  374. tbody {
  375. /* border-top: 20px solid transparent; */
  376. }
  377. .t-head th {
  378. margin-bottom: 20px;
  379. }
  380. .project-title {
  381. display: flex;
  382. padding: 0 40px;
  383. align-items: center;
  384. /* justify-content: center; */
  385. }
  386. .project-title .title {
  387. font-size: 28px;
  388. min-height: 0;
  389. height: auto;
  390. background-color: transparent !important;
  391. /* width: 300px; */
  392. margin: 30px 0;
  393. }
  394. .el-textarea__inner {
  395. background-color: transparent;
  396. box-shadow: none;
  397. resize: none;
  398. }
  399. .gray .el-textarea__inner {
  400. background: rgba(227, 225, 225, 0.2);
  401. }
  402. .add-handle {
  403. padding: 30px 0;
  404. display: flex;
  405. justify-content: center;
  406. }
  407. .add-line {
  408. margin: 0 10px;
  409. width: 30px;
  410. }
  411. .add-line .el-input__wrapper {
  412. box-shadow: none;
  413. background: rgba(23, 41, 46, 0.2);
  414. }
  415. .add-line input {
  416. color: white;
  417. text-align: center;
  418. }
  419. .activefileList {
  420. .el-upload-list--picture-card {
  421. }
  422. .el-upload--picture-card {
  423. display: none;
  424. }
  425. }
  426. .list-upload-style {
  427. width: 100%;
  428. text-align: center;
  429. .el-upload-list,
  430. .el-upload--text {
  431. width: 100%;
  432. }
  433. .el-upload-list__item-thumbnail,
  434. .el-upload--picture-card {
  435. min-height: 73px;
  436. height: 73px;
  437. width: 100%;
  438. }
  439. .uploadImg,
  440. .el-upload-list__item {
  441. width: 100%;
  442. min-height: 73px;
  443. height: 73px;
  444. line-height: 73px;
  445. .el-upload-list__item-thumbnail {
  446. width: 100%;
  447. max-width: 100px;
  448. object-fit: cover;
  449. }
  450. }
  451. }
  452. .marksDiv {
  453. position: relative;
  454. .table-delete {
  455. position: absolute;
  456. right: -10px;
  457. top: -3px;
  458. }
  459. }
  460. }
  461. </style>