detail.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744
  1. <template>
  2. <headerBar title="主图与详情生成" :menu="[
  3. {
  4. type: 'setting'
  5. }
  6. ]" />
  7. <div class="detail-container">
  8. <div>
  9. <!-- 主图LOGO部分 -->
  10. <div class="logo-section flex left top">
  11. <div class="section-title">
  12. <img src="@/assets/images/Photography/zhuangshi.png" style="width: 32px; height: 32px;" />
  13. 主图LOGO:
  14. </div>
  15. <upload :value="form.logo_path" @input="onInput"></upload>
  16. </div>
  17. <el-divider />
  18. <!-- 图片抠图与货号图生成 -->
  19. <div class="section">
  20. <div class="section-title">
  21. <img src="@/assets/images/Photography/zhuangshi.png" style="width: 32px; height: 32px;" />
  22. 图片抠图与货号图生成
  23. </div>
  24. <div class="section-content">
  25. <div v-if="showTips" class="instruction-out flex top left">
  26. <img style="fill: #000" src="@/assets/images/xinxi.svg" />
  27. <ol class="instruction-list">
  28. <li>请在下方确认图片拍摄过程中的顺序,确保所有拍摄的图片的顺序一致。</li>
  29. <li>使用中英文语号分隔。</li>
  30. <li>图片的名称不能随意修改,否则无法正常生成详情页。</li>
  31. <li>现有图片名称有:俯视、侧视、后视、鞋底、内里</li>
  32. </ol>
  33. <el-icon @click="showTips = false" class="close-icon">
  34. <Close />
  35. </el-icon>
  36. </div>
  37. <!-- 货号文件夹 -->
  38. <!-- <div class="form-item">
  39. <div class="label">货号文件夹:</div>
  40. <div class="folder-warp">
  41. <div class="folder-input">
  42. <el-input style="width: 60%;" v-model="folderPath" type="textarea" :rows="2" readonly
  43. placeholder="请选择货号文件夹" />
  44. <el-button class="check-button" type="primary" @click="selectFolder">
  45. <img src="@/assets/images/Photography/wenjian.png" style="width: 14px; " />
  46. 选择目标文件夹</el-button>
  47. </div>
  48. <div class="hint">
  49. <el-icon>
  50. <Warning />
  51. </el-icon> <text>选择货号的上级文件夹</text>
  52. </div>
  53. </div>
  54. </div>
  55. -->
  56. </div>
  57. </div>
  58. <el-divider />
  59. <!-- 选择详情模板部分 -->
  60. <div class="template-section ">
  61. <div class="flex between">
  62. <div class="section-title">
  63. <img src="@/assets/images/Photography/zhuangshi.png" style="width: 32px; height: 32px;" />
  64. 选择详情模版
  65. </div>
  66. <div class="template-pagination">
  67. <el-pagination background layout="prev, pager, next" v-model:current-page="queryParams.current"
  68. v-model:page-size.sync="queryParams.size" :total="totalPage" @current-change="onCurrentChange"
  69. @size-change="onSizeChange" />
  70. </div>
  71. </div>
  72. <div class="template-list">
  73. <div v-for="(template, index) in visibleTemplates" :key="index" class="template-item"
  74. @click="form.selectTemplate = template">
  75. <el-image :src="template.template_cover_image" fit="contain" class="cur-p"
  76. style="width: 100%; display: block;" />
  77. <div class="select-warp" :class="form.selectTemplate.id == template.id ? 'active' : ''">
  78. <el-icon color="#FFFFFF">
  79. <Select />
  80. </el-icon>
  81. </div>
  82. <div class="template-info">
  83. <span class="mar-left-10 chaochu_1">{{ template.template_name }}</span>
  84. <div class="template-view" @click="viewTemplate(template)">查看</div>
  85. </div>
  86. </div>
  87. </div>
  88. </div>
  89. <el-divider />
  90. <!-- 详情高级配置 -->
  91. <div class="section">
  92. <div class="section-title">
  93. <img src="@/assets/images/Photography/zhuangshi.png" style="width: 32px; height: 32px;" />
  94. 详情高级配置
  95. </div>
  96. <div class="section-content">
  97. <!-- 图片顺序 -->
  98. <div class="form-item">
  99. <div class="label">图片顺序:</div>
  100. <el-input v-model="imageOrder" placeholder="请输入图片顺序" class="specific-page-input">
  101. <template #append>
  102. <el-button class="explain-btn" link type="primary">说明</el-button>
  103. </template>
  104. </el-input>
  105. </div>
  106. <!-- 同款检验 -->
  107. <!-- <div class="form-item">
  108. <div class="label">同款检验:</div>
  109. <el-checkbox v-model="checkSimilar">同款下货号必须齐全</el-checkbox>
  110. </div>
  111. -->
  112. <!-- 可指定页面独修改 -->
  113. <!-- <div class="form-item">
  114. <div class="label">可指定页面独修改:</div>
  115. <el-input v-model="specificPage" placeholder="请输入入需要单独修改的页面,示例:4:1 (需修改模版的编号:第一张)"
  116. class="specific-page-input">
  117. <template #append>
  118. <el-button class="explain-btn" link type="primary">说明</el-button>
  119. </template>
  120. </el-input>
  121. </div>
  122. -->
  123. </div>
  124. </div>
  125. <el-divider />
  126. <!-- 详情资料准备部分 -->
  127. <div class="data-prep-section">
  128. <div class="flex-item left">
  129. <div class="section-title">
  130. <img src="@/assets/images/Photography/zhuangshi.png" style="width: 32px; height: 32px;" />
  131. 详情资料准备 (2选1)
  132. <el-button v-if="form.dataType == '1'" type="text" class="mar-left-10 fs-16" @click="downloadExcel">下载商品基础资料模版</el-button>
  133. </div>
  134. </div>
  135. <div class="flex-item left">
  136. <el-radio-group v-model="form.dataType" class="ml-4">
  137. <el-radio label="1" size="large">EXCEL文件选择</el-radio>
  138. <el-radio label="2" size="large">系统对接(和业务员联系)</el-radio>
  139. </el-radio-group>
  140. </div>
  141. <div v-if="form.dataType == '1'" class="excel-upload">
  142. <div class="flex bottom between">
  143. <div style="max-width: 160px;" class="mar-left-20">商品基础资料EXCEL文件选择:</div>
  144. <div class="flex bottom mar-left-20" style="flex-grow: 1;">
  145. <el-input type="textarea" v-model="form.excel_path" />
  146. </div>
  147. <el-button class="select-button button--primary1 mar-left-20" type="primary" @click="selectExcel">
  148. <img src="@/assets/images/Photography/wenjian.png" style="width: 16px; margin-right: 4px;" />
  149. 选择</el-button>
  150. </div>
  151. </div>
  152. </div>
  153. </div>
  154. <!-- 底部按钮 -->
  155. <div class="footer">
  156. <!-- <el-button class="button--primary1 footer-button" type="primary" @click="saveConfig">保存配置</el-button> -->
  157. <!-- <el-button class="button--primary1 footer-button" type="primary" @click="startProcess">开始处理</el-button> -->
  158. <el-button class="button--primary1 footer-button" type="primary" @click="generate">开始生成</el-button>
  159. </div>
  160. </div>
  161. <loading-dialog v-model="loadingDialogVisible" :progress="progress" :message="message"
  162. :disabled-button="disabledButton" @button-click="handleComplete">
  163. <template v-if="partErrList && partErrList.length > 0" #errList>
  164. <div v-for="(item, idx) in partErrList" :key="idx">
  165. <span>{{ item.goods_art_no }}</span>:<span>{{ item.info }}</span>
  166. </div>
  167. </template>
  168. </loading-dialog>
  169. <el-dialog v-model="dialogVisible">
  170. <img style="width: 100%;" :src="dialogImageUrl" alt="Preview Image" />
  171. </el-dialog>
  172. </template>
  173. <script lang="ts" setup>
  174. import { getCompanyTemplatesApi } from '@/apis/other'
  175. import tokenInfo from '@/stores/modules/token';
  176. import useUserInfo from "@/stores/modules/user";
  177. import { useRoute, useRouter } from 'vue-router'
  178. import { ElMessage, ElMessageBox } from 'element-plus'
  179. import headerBar from '@/components/header-bar/index.vue'
  180. import { ref, computed, reactive, onMounted } from 'vue';
  181. import { Select } from '@element-plus/icons-vue'
  182. import upload from '@/components/upload'
  183. import client from "@/stores/modules/client";
  184. import icpList from '@/utils/ipc'
  185. const clientStore = client();
  186. import { getRouterUrl } from '@/utils/appfun'
  187. import { Close, Warning } from '@element-plus/icons-vue'
  188. import LoadingDialog from '@/views/Photography/components/LoadingDialog.vue'
  189. import { useCheckInfo } from '@/composables/userCheck';
  190. useCheckInfo();
  191. const showTips = ref(true)
  192. const folderPath = ref('') //货号文件夹
  193. // const reportMode = ref('normal') // 抠图模式
  194. const imageOrder = ref('俯视、侧视、后跟、鞋底、内里、组合、组合2、组合3') // 图片顺序
  195. const checkSimilar = ref(false) // 同款检验
  196. const specificPage = ref('') // 可指定页面独修改
  197. // 路由和状态管理初始化
  198. const route = useRoute();
  199. const router = useRouter();
  200. // 完成目录
  201. const completeDirectory = ref('')
  202. const loadingDialogVisible = ref(false)
  203. const progress = ref(0)
  204. const message = ref('正在为您处理,请稍后')
  205. const disabledButton = ref(true)
  206. let templates = ref([])
  207. let goods_art_nos = ref([])
  208. let partErrList = ref([])
  209. const excel_template_url = ref('')
  210. // 定义一个定时器变量
  211. const INTERVAL = ref<number | NodeJS.Timeout | null>(null);
  212. // 状态变量
  213. const totalPage = ref(0);
  214. const itemsPerPage = 4; // 每页显示的模板数量
  215. const dialogVisible = ref(false);
  216. const dialogImageUrl = ref('');
  217. const queryParams = reactive({ // 分页查询参数
  218. size: 1,
  219. current: 1,
  220. })
  221. const form = reactive({
  222. selectTemplate: {}, //选中的模板
  223. dataType: '1', // 1: 选择excel文件 2: 系统对接
  224. logo_path: '', // 主图LOGO
  225. excel_path: '', // 商品基础资料EXCEL文件选择
  226. })
  227. onMounted(() => {
  228. const goods_art_data = route.query.goods_art_nos
  229. goods_art_nos.value = Array.isArray(goods_art_data) ? goods_art_data : [goods_art_data]
  230. getCompanyTemplates()
  231. })
  232. // 计算属性,获取当前页可见的模板
  233. const visibleTemplates = computed(() => {
  234. const startIndex = (queryParams.current - 1) * itemsPerPage;
  235. const data = templates.value.slice(startIndex, startIndex + itemsPerPage);
  236. return data
  237. });
  238. // 查看模板详情
  239. const viewTemplate = (template) => {
  240. // 展示大图
  241. dialogVisible.value = true
  242. dialogImageUrl.value = template.template_preview_image
  243. };
  244. // 获取模版列表
  245. const getCompanyTemplates = async () => {
  246. const { data } = await getCompanyTemplatesApi()
  247. templates.value = data.list
  248. // 默认选中第一个模板
  249. if (templates.value.length > 0) {
  250. form.selectTemplate = templates.value[0]
  251. }
  252. excel_template_url.value = data.excel_template_url
  253. // 计算总页数
  254. totalPage.value = Math.ceil(templates.value.length / itemsPerPage);
  255. }
  256. const downloadExcel = () => {
  257. const a = document.createElement('a')
  258. a.href = excel_template_url.value,
  259. a.download = '商品基础资料模版'
  260. document.body.appendChild(a)
  261. a.click()
  262. setTimeout(() => {
  263. document.body.removeChild(a);
  264. }, 1000);
  265. }
  266. const onCurrentChange = (page) => {
  267. queryParams.current = page;
  268. };
  269. const onSizeChange = (data) => {
  270. };
  271. // 开始生成操作
  272. const generate = async function () {
  273. const tokenInfoStore = tokenInfo();
  274. const token = tokenInfoStore.getToken; // 使用 getToken() 获取 token
  275. let temp_list = []
  276. templates.value.map(item => {
  277. temp_list.push({
  278. template_id: item.template_id,
  279. template_local_classes: item.template_local_classes,
  280. })
  281. })
  282. const params = {
  283. goods_art_no: JSON.parse(JSON.stringify(goods_art_nos.value)),
  284. logo_path: form.logo_path || '',
  285. temp_name: form.selectTemplate.template_id || '',
  286. excel_path: form.dataType == '1' ? form.excel_path : '',
  287. template_image_order: form.selectTemplate.template_image_order,
  288. temp_list,
  289. token,
  290. }
  291. // 开启进度弹窗
  292. progress.value = 0
  293. openLoadingDialog(20)
  294. console.log("params", "color:#3f7cff", params);
  295. clientStore.ipc.removeAllListeners(icpList.generate.generatePhotoDetail);
  296. clientStore.ipc.send(icpList.generate.generatePhotoDetail, params);
  297. clientStore.ipc.on(icpList.generate.generatePhotoDetail, (event, result) => {
  298. console.log('result', result)
  299. partErrList.value = []
  300. clientStore.ipc.removeAllListeners(icpList.generate.generatePhotoDetail);
  301. clearInterval(INTERVAL.value)
  302. if (result.code === 0) {
  303. const { output_folder, list } = result.data
  304. const allSuccess = list.every(item => item.success);
  305. const allFailure = list.every(item => !item.success);
  306. if (allSuccess) {
  307. console.log("全部成功")
  308. handleSuccess(output_folder, '全部生成成功')
  309. } else if (allFailure) {
  310. console.log("全部失败");
  311. handleFailure(list)
  312. } else {
  313. console.log("部分成功,部分失败");
  314. handlePartSuccess(output_folder, list)
  315. }
  316. } else {
  317. console.log('code全部生成失败')
  318. handleFail(result.msg)
  319. }
  320. //生成失败 (接口请求失败)
  321. function handleFail(errorMsg: string) {
  322. loadingDialogVisible.value = false
  323. disabledButton.value = false
  324. if (errorMsg) {
  325. ElMessage.error(errorMsg)
  326. }
  327. }
  328. // 全部生成成功
  329. function handleSuccess(href, loadingMsg) {
  330. completeDirectory.value = href
  331. progress.value = 100
  332. disabledButton.value = false
  333. message.value = loadingMsg
  334. }
  335. // 部分成功
  336. function handlePartSuccess(output_folder: string, partSuccessList) {
  337. let errorList = []
  338. partSuccessList.map(item => {
  339. if (!item.success) {
  340. errorList.push(item)
  341. }
  342. })
  343. partErrList.value = errorList
  344. handleSuccess(output_folder, '部分货号生成失败')
  345. }
  346. // 全部生成失败
  347. function handleFailure(partSuccessList) {
  348. let errorList = []
  349. partSuccessList.map(item => {
  350. if (!item.success) {
  351. errorList.push(item)
  352. }
  353. })
  354. partErrList.value = errorList
  355. completeDirectory.value = ''
  356. progress.value = 100
  357. disabledButton.value = true
  358. message.value = '全部货号生成失败'
  359. }
  360. });
  361. }
  362. const openLoadingDialog = (timer: number) => {
  363. loadingDialogVisible.value = true
  364. disabledButton.value = true
  365. // 根据传入的秒数计算每次增加的进度值
  366. const step = 100 / timer
  367. INTERVAL.value = setInterval(() => {
  368. if (progress.value < 100) {
  369. progress.value = Math.round(progress.value + step)
  370. }
  371. }, 1000)
  372. }
  373. const onInput = (value) => {
  374. form.logo_path = value
  375. }
  376. function selectExcel() {
  377. clientStore.ipc.removeAllListeners(icpList.utils.openFile);
  378. clientStore.ipc.send(icpList.utils.openFile, {
  379. filters: [
  380. { name: '支持xls,xlsx', extensions: ['xlsx', 'xls'] }
  381. ],
  382. title: "选择基础文件资料"
  383. });
  384. clientStore.ipc.on(icpList.utils.openFile, async (event, result) => {
  385. form.excel_path = result
  386. clientStore.ipc.removeAllListeners(icpList.utils.openFile);
  387. })
  388. }
  389. const Router = useRouter()
  390. //打开主图详情
  391. function openPhotographySeniorDetail() {
  392. const { href } = Router.resolve({
  393. name: 'PhotographySeniorDetail'
  394. })
  395. clientStore.ipc.removeAllListeners(icpList.utils.openMain);
  396. let params = {
  397. title: '详情高级配置',
  398. width: 1000,
  399. height: 630,
  400. frame: true,
  401. id: "PhotographySeniorDetail",
  402. url: getRouterUrl(href)
  403. }
  404. clientStore.ipc.send(icpList.utils.openMain, params);
  405. }
  406. const handleComplete = () => {
  407. loadingDialogVisible.value = false
  408. // 这里可以添加打开目录的逻辑
  409. clientStore.ipc.removeAllListeners(icpList.utils.shellFun);
  410. let params = {
  411. action: 'openPath',
  412. params: completeDirectory.value?.replaceAll('/', '\\')
  413. }
  414. clientStore.ipc.send(icpList.utils.shellFun, params);
  415. }
  416. const selectFolder = () => {
  417. clientStore.ipc.removeAllListeners(icpList.utils.openDirectory);
  418. clientStore.ipc.send(icpList.utils.openDirectory);
  419. clientStore.ipc.on(icpList.utils.openDirectory, async (event, result) => {
  420. folderPath.value = result
  421. clientStore.ipc.removeAllListeners(icpList.utils.openDirectory);
  422. })
  423. }
  424. </script>
  425. <style lang="scss" scoped>
  426. .detail-container {
  427. background-color: #EAECED;
  428. width: 100%;
  429. min-width: 600px;
  430. padding: 20px;
  431. overflow: hidden;
  432. }
  433. .logo-section,
  434. .template-section,
  435. .data-prep-section {
  436. margin-bottom: 20px;
  437. }
  438. .logo-upload {
  439. border: 1px dashed #ccc;
  440. border-radius: 5px;
  441. padding: 50px 0;
  442. text-align: center;
  443. cursor: pointer;
  444. }
  445. .template-pagination button {
  446. margin-right: 5px;
  447. }
  448. .template-pagination span {
  449. display: inline-block;
  450. width: 20px;
  451. height: 20px;
  452. line-height: 20px;
  453. text-align: center;
  454. border: 1px solid #ccc;
  455. border-radius: 5px;
  456. margin-right: 5px;
  457. cursor: pointer;
  458. }
  459. .template-list {
  460. display: flex;
  461. flex-wrap: wrap;
  462. gap: 20px;
  463. margin-top: 10px;
  464. .template-item {
  465. width: calc(25% - 18px);
  466. border: 1px solid #ccc;
  467. border-radius: 10px;
  468. cursor: pointer;
  469. background: #f0f0f0;
  470. position: relative;
  471. overflow: hidden;
  472. .template-info {
  473. position: absolute;
  474. bottom: 0;
  475. left: 0;
  476. background: rgba($color: #000000, $alpha: .3);
  477. width: 100%;
  478. height: 36px;
  479. line-height: 36px;
  480. color: #eee;
  481. display: flex;
  482. align-items: center;
  483. justify-content: space-between;
  484. .template-view {
  485. background: #DFE2E3;
  486. color: #3366FF;
  487. height: 30px;
  488. line-height: 30px;
  489. padding: 0 10px;
  490. border-radius: 4px;
  491. margin-right: 10px;
  492. font-size: 14px;
  493. }
  494. }
  495. }
  496. }
  497. .excel-upload {
  498. width: 100%;
  499. background: #F7F7F7;
  500. padding: 20px 0;
  501. }
  502. .generate-button {
  503. padding: 10px 20px;
  504. color: white;
  505. border: none;
  506. border-radius: 5px;
  507. cursor: pointer;
  508. }
  509. .select-button {
  510. background: #DFE2E3 !important;
  511. color: #3366FF !important;
  512. height: 30px;
  513. line-height: 30px;
  514. padding: 0 10px;
  515. border-radius: 4px;
  516. margin-right: 10px;
  517. font-size: 14px;
  518. font-weight: 550;
  519. }
  520. .select-warp {
  521. width: 18px;
  522. height: 18px;
  523. border-radius: 4px;
  524. background-color: #fff;
  525. position: absolute;
  526. top: 10px;
  527. left: 10px;
  528. &.active {
  529. background-color: #1677FF;
  530. }
  531. }
  532. .section-title {
  533. display: flex;
  534. align-items: center;
  535. gap: 8px;
  536. font-weight: bold;
  537. margin-bottom: 16px;
  538. color: #474747;
  539. }
  540. .section {
  541. margin-bottom: 24px;
  542. .section-title {
  543. display: flex;
  544. align-items: center;
  545. gap: 8px;
  546. font-weight: bold;
  547. margin-bottom: 16px;
  548. color: #474747;
  549. }
  550. .section-content {
  551. padding-left: 16px;
  552. }
  553. }
  554. .instruction-out {
  555. background: #EAF3FF;
  556. border-radius: 4px;
  557. border: 1px solid #CBE1FF;
  558. padding: 10px 20px;
  559. width: 80%;
  560. position: relative;
  561. .instruction-list {
  562. margin: 0px 0 0 10px;
  563. padding-left: 20px;
  564. padding-right: 40px;
  565. width: 100%;
  566. li {
  567. margin-bottom: 4px;
  568. text-align: left;
  569. font-size: 14px;
  570. }
  571. }
  572. .close-icon {
  573. position: absolute;
  574. top: 12px;
  575. right: 19px;
  576. }
  577. }
  578. .form-item {
  579. margin: 16px 0;
  580. display: flex;
  581. .label {
  582. min-width: 120px;
  583. margin-right: 12px;
  584. }
  585. .folder-warp {
  586. width: 100%;
  587. display: flex;
  588. flex-direction: column;
  589. .folder-input {
  590. flex: 1;
  591. display: flex;
  592. align-items: center;
  593. .check-button {
  594. background: #DFE2E3;
  595. border-radius: 6px;
  596. padding: 6px 12px;
  597. color: #2957FF;
  598. margin-left: 20px;
  599. }
  600. }
  601. }
  602. .hint {
  603. text-align: left;
  604. color: #999;
  605. margin-top: 6px;
  606. font-size: 14px;
  607. color: #FF4C00;
  608. font-style: normal;
  609. }
  610. .specific-page-input {
  611. flex: 1;
  612. }
  613. }
  614. .image-order {
  615. flex: 1;
  616. display: flex;
  617. justify-content: space-between;
  618. align-items: center;
  619. }
  620. .footer {
  621. display: flex;
  622. justify-content: center;
  623. margin-top: 24px;
  624. .footer-button {
  625. padding: 10px 20px;
  626. }
  627. }
  628. .explain-btn {
  629. padding-left: 20px;
  630. padding-right: 20px;
  631. color: #2957FF !important;
  632. }
  633. </style>