ソースを参照

Merge branch 'dev-frontend' of http://gitlab.pubdata.cn/liangyibo/CameraMachine into dev-frontend

DESKTOP-1OI7FFK\WZTX 9 ヶ月 前
コミット
c790d09a2f

+ 3 - 1
frontend/package.json

@@ -15,11 +15,13 @@
     "pinia": "3.0.1",
     "pinia-plugin-persistedstate": "4.2.0",
     "vue": "3.5.13",
-    "vue-router": "4.5.0"
+    "vue-router": "4.5.0",
+    "crypto-js": "^4.1.1"
   },
   "devDependencies": {
     "@types/node": "22.13.5",
     "@vitejs/plugin-vue": "5.2.1",
+    "@types/crypto-js": "^4.1.1",
     "@vue/tsconfig": "0.7.0",
     "sass-embedded": "^1.85.1",
     "typescript": "5.7.2",

+ 14 - 2
frontend/src/apis/user.ts

@@ -1,4 +1,4 @@
-import { GET,POST } from "@/utils/http";
+import { GET,POST ,UPLOAD } from "@/utils/http";
 import type { UserRequest } from "@/apis/types/user";
 
 export async function getUserInfo(data:UserRequest){
@@ -11,8 +11,20 @@ export async function login(data:UserRequest){
 }
 
 
+export function upload(params: any) {
+    const form = new FormData()
+    Object.keys(params).map((item) => {
+      form.append(item, params[item])
+    })
+    return POST('/upload', form, {
+      cancelRepeatKey: `${Date.now}${Math.random()}`,
+      headers: {
+        'Content-Type': 'multipart/form-data',
+      },
+    })
+  }
 // 获取验证码
 export function sendCode(data:UserRequest) {
     return POST('/api/auth/send_code',data)
   }
-  
+  

BIN
frontend/src/assets/images/Photography/loading-bg.png


BIN
frontend/src/assets/images/Photography/wenjian.png


BIN
frontend/src/assets/images/Photography/zhuangshi.png


ファイルの差分が大きいため隠しています
+ 7 - 0
frontend/src/assets/images/xinxi.svg


+ 122 - 0
frontend/src/components/upload/index.vue

@@ -0,0 +1,122 @@
+<template>
+  <el-upload class="upload-warp" action="#" list-type="picture-card" :http-request="upload">
+
+    <div class="flex col">
+      <el-icon>
+        <Plus />
+      </el-icon>
+      <div class="add-text">添加</div>
+    </div>
+    <template #file="{ file }">
+      <div>
+        <img class="el-upload-list__item-thumbnail" :src="file.url" alt="" />
+        <span class="el-upload-list__item-actions">
+          <span class="el-upload-list__item-preview" @click="handlePictureCardPreview(file)">
+            <el-icon><zoom-in /></el-icon>
+          </span>
+          <span v-if="!disabled" class="el-upload-list__item-delete" @click="handleDownload(file)">
+            <el-icon>
+              <Download />
+            </el-icon>
+          </span>
+          <span v-if="!disabled" class="el-upload-list__item-delete" @click="handleRemove(file)">
+            <el-icon>
+              <Delete />
+            </el-icon>
+          </span>
+        </span>
+      </div>
+    </template>
+  </el-upload>
+
+  <el-dialog v-model="dialogVisible">
+    <img w-full :src="dialogImageUrl" alt="Preview Image" />
+  </el-dialog>
+</template>
+<script lang="ts" setup>
+import { ref, defineEmits } from 'vue'
+import { Delete, Download, Plus, ZoomIn } from '@element-plus/icons-vue'
+import type { UploadFile } from 'element-plus'
+import { imagesUpload } from '@/utils/appconfig'
+import { ElMessage } from 'element-plus'
+import * as  userApi from '@/apis/user'
+
+// 定义自定义事件
+const emit = defineEmits(['input']);
+
+interface Props {
+  value: string
+  hasDel?: boolean
+  hasSize?: boolean
+  width?: number
+  height?: number
+  fileSize?: number
+  styleWidth?: string
+  accept?: string[]
+}
+const props = withDefaults(defineProps<Props>(), {
+  value: '',
+  hasDel: true,
+  hasSize: true,
+  width: 0,
+  height: 0,
+  fileSize: imagesUpload.fileSize,
+  styleWidth: '160',
+  accept: () => imagesUpload.accept
+})
+
+const dialogImageUrl = ref('')
+
+const dialogVisible = ref(false)
+
+const disabled = ref(false)
+
+const handleRemove = (file: UploadFile) => {
+  console.log(file)
+}
+
+const handlePictureCardPreview = (file: UploadFile) => {
+  dialogImageUrl.value = file.url!
+  dialogVisible.value = true
+}
+
+const handleDownload = (file: UploadFile) => {
+  console.log(file)
+}
+// 上传
+const upload = async (params) => {
+  const isLt2M = params.file.size / 1024 < props.fileSize
+  if (props.accept.indexOf(params.file.name.toLocaleLowerCase().split('.')[params.file.name.toLocaleLowerCase().split('.').length - 1]) < 0) {
+    ElMessage.error('请上传' + props.accept.join(',') + '格式文件')
+    return false
+  }
+  if (!isLt2M) {
+    ElMessage.error('上传文件大小不能超过 ' + props.fileSize + 'KB!')
+    return false
+  }
+  const res = await userApi.upload({
+    file: params.file
+  })
+  if (!props.hasSize) {
+    emit('input', res.data.url)
+    return;
+  }
+  emit('input', res.data.url)
+}
+</script>
+
+
+<style lang="scss" scoped>
+.upload-warp {
+  ::v-deep {
+    .el-upload--picture-card {
+      background-color: #fff;
+
+      .el-icon,
+      .add-text {
+        color: #2957FF;
+      }
+    }
+  }
+}
+</style>

+ 11 - 1
frontend/src/router/index.ts

@@ -31,7 +31,17 @@ const routes: RouteRecordRaw[] = [
         path: "/photography/shot",
         name: "PhotographyShot",
         component: () => import("@/views/Photography/shot.vue"),
-    }
+    },
+    {
+        path: "/photography/detail",
+        name: "PhotographyDetail",
+        component: () => import("@/views/Photography/detail.vue"),
+    },
+    {
+        path: "/photography/seniorDetail",
+        name: "PhotographySeniorDetail",
+        component: () => import("@/views/Photography/seniorDetail.vue"),
+    },
 ];
 
 

+ 2 - 1
frontend/src/style.css

@@ -61,8 +61,9 @@ button:focus-visible {
 #app {
   max-width: 1280px;
   margin: 0 auto;
-  padding: 2rem;
+  /*  padding: 2rem;*/
   text-align: center;
+  width: 100%;
 }
 
 @media (prefers-color-scheme: light) {

+ 11 - 0
frontend/src/utils/appconfig.ts

@@ -0,0 +1,11 @@
+export const imagesUpload = {
+  accept: ['jpg', 'png', 'gif'],
+  fileSize: 2048 * 10,
+  maxLength: 5
+}
+
+export const imagesUploadLimit = {
+  accept: ['jpg', 'jpeg', 'png'],
+  fileSize: 2048 * 10,
+  fileLimit: '20MB'
+}

+ 1 - 2
frontend/src/utils/appfun.ts

@@ -1,7 +1,6 @@
-import { ossResize, ossImageInfo } from './oss'
+import { ossResize } from './oss'
 export  {
   // 格式化图片
   ossResize,
   // 获取图片信息
-  ossImageInfo,
 } 

+ 127 - 80
frontend/src/utils/oss.ts

@@ -1,117 +1,164 @@
-
-import axios from "axios";
-
 /**
  * oss resize 参数
  * 详情见 https://help.aliyun.com/document_detail/44688.html?spm=a2c4g.11186623.6.751.7434663c1Ne07e
+ *
+ * oss watermark 参数
+ * 详情见 https://help.aliyun.com/document_detail/44957.html
+ *
+ * resize 参数被提到最外层(兼容 + 使用最频繁)
+ * 其他(如 watermark)使用对象
  */
 
-interface OSSResizeOptions {
-  scale?: number;
-  w?: number;
-  h?: number;
-  watermark?: string;
-  [key: string]: any;
-}
+import encBase64Url from 'crypto-js/enc-base64url'
+import encUtf8 from 'crypto-js/enc-utf8'
+
+export type IOSSResize =
+  | {
+      m: 'lfit' | 'mfit' | 'fixed' | 'fixed' | 'fill' | 'pad'
+      w?: number
+      h?: number
+      l?: number
+      s?: number
+      color?: string
+      limit?: 0 | 1
+    }
+  | {
+      p?: number
+    }
 
-interface OSSImageInfoOptions {
-  [key: string]: any;
+export type IOSSOptions = {
+  watermark?: IOSSWatermark | IOSSWatermark[]
+  quality?: IOSSQuality
+} & {
+  [key: string]: object
 }
 
-interface ImageInfo {
-  [key: string]: any;
+export type IOSSWatermark = {
+  t?: number
+  g?: 'nw' | 'north' | 'ne' | 'west' | 'center' | 'east' | 'sw' | 'south' | 'se'
+  x?: number
+  y?: number
+  voffset?: number
+
+  // 图片
+  image?: string
+  P?: number
+
+  // 文字
+  text?: string
+  type?: string
+  size?: number
+  shadow?: number
+  rotate?: number
+  fill?: 0 | 1
+  color?: string
+
+  // 混合
+  order?: 0 | 1
+  align?: 0 | 1 | 2
+  interval?: number
+} & (
+  | {
+      image: string
+      // text?: string
+    }
+  | {
+      // image?: string
+      text: string
+    }
+)
+export type IOSSQuality = {
+  q?: number
+  Q?: number
 }
 
+function createParams(key: string, options: Record<string, any>) {
+  const list = [key]
+  Object.keys(options).forEach((key) => {
+    const value = options[key]
+    if (value !== undefined && value !== null) {
+      list.push(`${key}_${value}`)
+    }
+  })
 
-
-function createParmas(key: string, value: any) {
-  return `${key}_${value}`
+  return list.join(',')
 }
 
-export function ossResize(url: string, options: OSSResizeOptions = {}): string {
+export function ossResize(
+  url?: string,
+  resize: IOSSResize = {},
+  options: IOSSOptions = {}
+) {
   if (!url) return ''
   try {
     const target = new URL(url)
     const parmas = target.searchParams
 
-    const list = ['image/resize']
-    //scale 追加scale 参数 默认为2
-    if(options.scale === undefined){
-      options.scale  = 2
-    }
-    if(options.w ){
-      options.w = options.w*options.scale
-    }
-    if(options.h){
-      options.h = options.h*options.scale
-    }
+    const { watermark, quality, ...otherOptions } = options
+    const list = ['image']
 
+    // 尺寸调整
+    if ('m' in resize || 'p' in resize) {
+      list.push(createParams('resize', resize))
+    }
 
-/*
     // 水印
-    if (options.watermark) {
-      list.push(
-        createParmas('watermark', {
-          ...options.watermark,
-        })
+    if (watermark) {
+      ;(Array.isArray(watermark) ? watermark : [watermark]).forEach(
+        (watermark) => {
+          list.push(createParams('watermark', watermark))
+        }
       )
     }
 
-*/
-
-
-    Object.keys(options).forEach((key) => {
-      if(!['watermark','scale'].includes(key)){
-        const value = options[key]
-        list.push(createParmas(key, value))
-      }
+    // 图片质量
+    list.push(
+      createParams(
+        'quality',
+        quality ?? {
+          Q: 80,
+        }
+      )
+    )
 
+    // 其他
+    Object.keys(otherOptions).forEach((key) => {
+      list.push(createParams(key, otherOptions[key]))
     })
 
-
-    const watermark = []
-    if (options.watermark) {
-      watermark.push('image/watermark')
-      watermark.push('image_' + options.watermark)
-    }
-
-    let str = list.join(',')
-    if(watermark.length  >0){
-      if(str){
-        str+= ','+watermark.join(',')
-      }else{
-        str+= watermark.join(',')
-      }
-    }
-    parmas.set('x-oss-process',str + '/quality,Q_80')
+    parmas.set('x-oss-process', list.join('/'))
 
     return target.toString()
   } catch (error) {
-    console.warn(url)
-    console.warn(error)
+    console.error(error)
     return url
   }
 }
 
+export function createWaterMarkText(text: string) {
+  return encBase64Url.stringify(encUtf8.parse(text))
+}
 
-
-export async function ossImageInfo(url: string, options: OSSImageInfoOptions = {}): Promise<ImageInfo | undefined> {
-  if (!url) return ''
-  try {
-    const target = new URL(url)
-    const parmas = target.searchParams
-    const list = ['image/info']
-    Object.keys(options).forEach((key) => {
-      const value = options[key]
-      list.push(createParmas(key, value))
-    })
-    parmas.set('x-oss-process', list.join(',') + '/quality,Q_80')
-    const ossUrl = target.toString()
-    const { data } = await axios.get(ossUrl)
-    return data
-  } catch (error) {
-    console.error(error)
-    return undefined
+export function splitText(text: string) {
+  if (!text) return ''
+  const reg = /^[\u0000-\u00ff]$/
+  const len = text.length
+  let result = ''
+  let count = 0
+  for (let i = 0; i < len; i += 1) {
+    const char = text.charAt(i)
+    count += reg.test(char) ? 1 : 2
+
+    if (count === 32 && i === len - 1) {
+      result += char
+      break
+    } else if (count > 29) {
+      result += '...'
+      break
+    } else {
+      result += char
+    }
   }
-}
 
+  return result
+}

+ 150 - 0
frontend/src/views/Photography/components/LoadingDialog.vue

@@ -0,0 +1,150 @@
+<template>
+  <el-dialog
+    v-model="visible"
+    :show-close="false"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    width="400px"
+    custom-class="loading-dialog-EL"
+    align-center
+    append-to-body
+  >
+    <div class="loading-content mar-top-10">
+      <div class="progress-container">
+        <div class="progress-bar">
+          <div 
+            class="progress-inner"
+            :style="{ width: `${progress}%` }"
+          ></div>
+        </div>
+        <span class="progress-text">{{ progress }}%</span>
+      </div>
+      
+      <div class="message">{{ message }}</div>
+      
+      <el-button
+        :disabled="showButton"
+        type="primary"
+        class="action-button   button--primary1  mar-top-20"
+        @click="handleButtonClick"
+      >
+        {{ buttonText }}
+      </el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { ref, defineProps, defineEmits , watch } from 'vue'
+
+interface Props {
+  modelValue: boolean
+  progress?: number
+  message?: string
+  showButton?: boolean
+  buttonText?: string
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  progress: 0,
+  message: '正在为您处理,请稍后',
+  showButton: false,
+  buttonText: '全部处理完毕,点击打开最终图片目录'
+})
+
+const emit = defineEmits<{
+  (e: 'update:modelValue', value: boolean): void
+  (e: 'button-click'): void
+}>()
+
+const visible = ref(props.modelValue)
+
+// 监听visible变化
+watch(() => visible.value, (newVal) => {
+  emit('update:modelValue', newVal)
+})
+
+// 监听props.modelValue变化
+watch(() => props.modelValue, (newVal) => {
+  visible.value = newVal
+})
+
+const handleButtonClick = () => {
+  emit('button-click')
+}
+</script>
+<style>
+.loading-dialog-EL{
+  .el-dialog__header {
+    display: none;
+    padding: 0;
+  }
+ .el-dialog__body {
+    background-image: url(@/assets/images/Photography/loading-bg.png);  /* 添加背景图片 */
+    background-size: cover;  /* 确保图片覆盖整个区域 */
+    background-position: center;  /* 图片居中显示 */
+    background-repeat: no-repeat;  /* 防止图片重复 */
+    padding: 40px 20px;  /* 添加内边距 */
+  }
+
+}
+</style>
+
+<style lang="scss" scoped>
+.loading-dialog {
+  :deep(.el-dialog__header) {
+    display: none;
+    padding: 0;
+  }
+  
+  :deep(.el-dialog__body) {
+    padding:0;
+  }
+}
+
+.loading-content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  text-align: center;
+}
+
+.progress-container {
+  width: 100%;
+  margin-bottom: 20px;
+  display: flex;
+  align-items: center;
+  gap: 10px;
+
+  .progress-bar {
+    flex: 1;
+    height: 6px;
+    background: #E4E7ED;
+    border-radius: 3px;
+    overflow: hidden;
+
+    .progress-inner {
+      height: 100%;
+      background: linear-gradient(90deg, #2FB0FF,#B863FB);
+      border-radius: 3px;
+      transition: width 0.3s ease;
+    }
+  }
+
+  .progress-text {
+    min-width: 45px;
+    color: #606266;
+    font-size: 14px;
+  }
+}
+
+.message {
+  color: #606266;
+  font-size: 14px;
+  margin-bottom: 20px;
+}
+
+.action-button {
+  padding: 10px  20px;
+}
+</style> 

+ 240 - 0
frontend/src/views/Photography/detail.vue

@@ -0,0 +1,240 @@
+<template>
+  <div class="detail-container">
+    <!-- 主图LOGO部分 -->
+    <div class="logo-section flex left top">
+      <div class="fw-600">主图LOGO:</div>
+      <upload v-model="queryParams.logoImage"></upload>
+    </div>
+    <el-divider />
+    <!-- 选择详情模板部分 -->
+    <div class="template-section ">
+      <div class="flex between">
+        <span>选择详情模版</span>
+        <div class="template-pagination">
+          <el-pagination background layout="prev, pager, next" v-model:current-page="queryParams.current"
+            v-model:page-size.sync="queryParams.size" :total="totalPage" @current-change="onCurrentChange"
+            @size-change="onSizeChange" />
+        </div>
+      </div>
+
+      <div class="template-list">
+        <div v-for="(template, index) in visibleTemplates" :key="index" class="template-item"
+          @click="queryParams.templateId = template.id">
+          <div class="select-warp" :class="queryParams.templateId == template.id ? 'active' : ''">
+            <el-icon color="#FFFFFF">
+              <Select />
+            </el-icon>
+          </div>
+          <div class="template-info">
+            <span class="mar-left-10">{{ template.templateId }}</span>
+            <div class="template-view" @click="viewTemplate(template)">查看</div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 详情资料准备部分 -->
+    <div class="data-prep-section">
+
+      <div class="flex-item left">详情资料准备 (2选1)</div>
+      <div class="flex-item left">
+        <el-radio-group v-model="queryParams.radio1" class="ml-4">
+          <el-radio label="1" size="large">EXCEL文件选择</el-radio>
+          <el-radio label="2" size="large">系统对接(和业务员联系)</el-radio>
+        </el-radio-group>
+      </div>
+      <div v-if="dataPrepOption === 'excel'" class="excel-upload">
+        <el-row :gutter="20">
+          <el-col :span="1"></el-col>
+          <el-col :span="4">商品基础资料EXCEL文件选择:</el-col>
+          <el-col :span="18">
+            <el-input type="textarea" v-model="excelFilePath" />
+          </el-col>
+          <el-col :span="1"></el-col>
+
+        </el-row>
+      </div>
+    </div>
+
+    <!-- 开始生成按钮 -->
+    <button class="generate-button button--primary1" @click="generate">开始生成</button>
+  </div>
+</template>
+
+<script lang="ts" setup>
+
+
+import { ref, computed, reactive } from 'vue';
+import { Select } from '@element-plus/icons-vue'
+
+
+import upload from '@/components/upload'
+
+// 模拟数据
+const templates = [
+  { id: 1101, templateId: '某某模版编号1', preview: '...' },
+  { id: 1102, templateId: '某某模版编号2', preview: '...' },
+  { id: 1103, templateId: '某某模版编号3', preview: '...' },
+  { id: 1104, templateId: '某某模版编号4', preview: '...' },
+  { id: 1105, templateId: '某某模版编号5', preview: '...' },
+  { id: 1106, templateId: '某某模版编号6', preview: '...' },
+  { id: 1107, templateId: '某某模版编号7', preview: '...' },
+  { id: 1108, templateId: '某某模版编号8', preview: '...' },
+  { id: 1109, templateId: '某某模版编号9', preview: '...' },
+  { id: 1110, templateId: '某某模版编号10', preview: '...' },
+  { id: 1111, templateId: '某某模版编号11', preview: '...' },
+  { id: 1112, templateId: '某某模版编号12', preview: '...' },
+  // 更多模板...
+];
+const itemsPerPage = 4; // 每页显示的模板数量
+// 状态变量
+const currentPage = ref(1);
+const selectedTemplateIndex = ref(0);
+const dataPrepOption = ref('excel');
+const excelFilePath = ref('D:\\MyDocuments\\PythonCode\\MyPython\\red_dragonfly\\deal_pics\\auto_capture_V2\\auto_photo');
+const totalPage = ref(3);
+
+const queryParams = reactive({
+  logoImage: '',
+  size: 1,
+  current: 1,
+  templateId: 1101,
+  radio1: 1
+})
+
+// 计算属性,获取当前页可见的模板
+const visibleTemplates = computed(() => {
+  const startIndex = (queryParams.current - 1) * itemsPerPage;
+  const data = templates.slice(startIndex, startIndex + itemsPerPage);
+  return data
+});
+// 查看模板详情
+const viewTemplate = (template) => {
+  console.log('查看模板详情', template);
+};
+const onCurrentChange = (page) => {
+  queryParams.current = page;
+};
+const onSizeChange = (data) => {
+
+};
+
+// 开始生成操作
+const generate = () => {
+  console.log('开始生成');
+  // 这里添加实际生成主图和详情的逻辑
+};
+</script>
+
+<style lang="scss" scoped>
+.detail-container {
+  padding: 20px 0 20px 20px;
+  max-width: 1200px;
+  background-color: #EAECED;
+  width: 100%;
+
+}
+
+.logo-section,
+.template-section,
+.data-prep-section {
+  margin-bottom: 20px;
+}
+
+.logo-upload {
+  border: 1px dashed #ccc;
+  border-radius: 5px;
+  padding: 50px 0;
+  text-align: center;
+  cursor: pointer;
+}
+
+.template-pagination button {
+  margin-right: 5px;
+}
+
+.template-pagination span {
+  display: inline-block;
+  width: 20px;
+  height: 20px;
+  line-height: 20px;
+  text-align: center;
+  border: 1px solid #ccc;
+  border-radius: 5px;
+  margin-right: 5px;
+  cursor: pointer;
+}
+
+
+.template-list {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 10px;
+  margin-top: 10px;
+
+
+  .template-item {
+    width: calc(25% - 34px);
+    border: 1px solid #ccc;
+    border-radius: 10px;
+    padding: 10px;
+    height: 320px;
+    cursor: pointer;
+    background: #f0f0f0;
+    position: relative;
+    overflow: hidden;
+
+    .template-info {
+      position: absolute;
+      bottom: 0;
+      left: 0;
+      background: rgba($color: #000000, $alpha: .3);
+      width: 100%;
+      height: 36px;
+      line-height: 36px;
+      color: #eee;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+
+      .template-view {
+        background: #DFE2E3;
+        color: #3366FF;
+        height: 30px;
+        line-height: 30px;
+        padding: 0 10px;
+        border-radius: 4px;
+        margin-right: 10px;
+        font-size: 14px;
+      }
+    }
+  }
+}
+
+
+
+.excel-upload {
+  width: 100%;
+  background: #F7F7F7;
+  padding: 20px 0;
+}
+
+.generate-button {
+  padding: 10px 20px;
+  color: white;
+  border: none;
+  border-radius: 5px;
+  cursor: pointer;
+}
+
+.select-warp {
+  width: 18px;
+  height: 18px;
+  border-radius: 4px;
+  background-color: #fff;
+
+  &.active {
+    background-color: #1677FF;
+  }
+}
+</style>

+ 281 - 0
frontend/src/views/Photography/seniorDetail.vue

@@ -0,0 +1,281 @@
+<template>
+  <div class="image-config">
+    <div class="config-card">
+      <!-- 图片报图与货号图生成 -->
+      <div class="section">
+        <div class="section-title">
+          <img src="@/assets/images/Photography/zhuangshi.png" style="width: 32px; height: 32px;" />
+          图片报图与货号图生成
+        </div>
+        <div class="section-content">
+
+
+          <div class="instruction-out flex top left">
+            <img style="fill: #000" src="@/assets/images/xinxi.svg" />
+            <ol class="instruction-list">
+              <li>请在下方确认图片拍摄过程中的顺序,确保所有拍摄的图片的顺序一致。</li>
+              <li>使用中英文语号分隔。</li>
+              <li>图片的名称不能随意修改,否则无法正常生成详情页。</li>
+              <li>现有图片名称有:俯视、侧视、后视、鞋底、内里</li>
+            </ol>
+            <el-icon class="close-icon">
+              <Close />
+            </el-icon>
+          </div>
+
+          <!-- 货号文件夹 -->
+          <div class="form-item">
+            <div class="label">货号文件夹:</div>
+            <div class="folder-warp">
+              <div class="folder-input">
+                <el-input style="width: 60%;" v-model="folderPath" type="textarea" placeholder="请选择货号文件夹" />
+                <el-button class="check-button" type="primary">
+                  <img src="@/assets/images/Photography/wenjian.png" style="width: 14px; " />
+                  选择目标文件夹</el-button>
+              </div>
+              <div class="hint">
+                <el-icon>
+                  <Warning />
+                </el-icon> <text>选择货号的上级文件夹</text>
+              </div>
+            </div>
+          </div>
+
+          <!-- 报图模式 -->
+          <div class="form-item">
+            <div class="label">抠图模式:</div>
+            <el-radio-group v-model="reportMode">
+              <el-radio label="normal">普通模式</el-radio>
+              <el-radio label="optimized">精细化报图</el-radio>
+            </el-radio-group>
+          </div>
+        </div>
+      </div>
+      <el-divider />
+
+      <!-- 详情高级配置 -->
+      <div class="section">
+        <div class="section-title">
+          <img src="@/assets/images/Photography/zhuangshi.png" style="width: 32px; height: 32px;" />
+          详情高级配置
+        </div>
+        <div class="section-content">
+          <!-- 图片顺序 -->
+          <div class="form-item">
+            <div class="label">图片顺序:</div>
+            <el-input v-model="imageOrder" placeholder="请输入图片顺序" class="specific-page-input">
+              <template #append>
+                <el-button class="explain-btn" link type="primary">说明</el-button>
+              </template>
+            </el-input>
+          </div>
+
+          <!-- 同款检验 -->
+          <div class="form-item">
+            <div class="label">同款检验:</div>
+            <el-checkbox v-model="checkSimilar">同款下货号必须齐全</el-checkbox>
+          </div>
+
+          <!-- 可指定页面独修改 -->
+          <div class="form-item">
+            <div class="label">可指定页面独修改:</div>
+            <el-input v-model="specificPage" placeholder="请输入入需要单独修改的页面,示例:4:1 (需修改模版的编号:第一张)"
+              class="specific-page-input">
+              <template #append>
+                <el-button class="explain-btn" link type="primary">说明</el-button>
+              </template>
+            </el-input>
+          </div>
+        </div>
+      </div>
+
+      <!-- 底部按钮 -->
+      <div class="footer">
+        <el-button class="button--primary1  footer-button" type="primary">保存配置</el-button>
+      
+        <el-button class="button--primary1 footer-button" type="primary" @click="startProcess">开始处理</el-button>
+      </div>
+  
+    </div>
+
+
+    <loading-dialog v-model="dialogVisible" :progress="progress" :message="message" :show-button="showButton"
+      @button-click="handleComplete" />
+  </div>
+
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import { Close, Warning } from '@element-plus/icons-vue'
+import LoadingDialog from '@/views/Photography/components/LoadingDialog.vue'
+
+const folderPath = ref('')
+const reportMode = ref('normal')
+const checkSimilar = ref(false)
+const specificPage = ref('')
+const imageOrder = ref('俯视、侧视、后跟、鞋底、内里、组合、组合2、组合3')
+
+
+const dialogVisible = ref(false)
+const progress = ref(0)
+const message = ref('正在为您处理,请稍后')
+const showButton = ref(false)
+const startProcess = () => {
+  dialogVisible.value = true
+  progress.value = 0
+  showButton.value = false
+
+  // 模拟进度更新
+  const interval = setInterval(() => {
+    if (progress.value < 100) {
+      progress.value += 10
+    } else {
+      clearInterval(interval)
+      message.value = '全部处理完毕'
+      showButton.value = true
+    }
+  }, 500)
+}
+
+const handleComplete = () => {
+  dialogVisible.value = false
+  // 这里可以添加打开目录的逻辑
+  console.log('打开目录')
+}
+
+</script>
+
+<style lang="scss" scoped>
+.image-config {
+  padding: 20px;
+
+  .config-card {
+    background: #EAECED;
+    padding: 20px;
+
+    .card-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+
+      .header-icons {
+        display: flex;
+        gap: 8px;
+      }
+    }
+  }
+
+  .section {
+    margin-bottom: 24px;
+
+    .section-title {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      font-weight: bold;
+      margin-bottom: 16px;
+      color: #474747;
+    }
+
+    .section-content {
+      padding-left: 16px;
+    }
+  }
+
+  .instruction-out {
+    background: #EAF3FF;
+    border-radius: 4px;
+    border: 1px solid #CBE1FF;
+    padding: 10px 20px;
+    width: 80%;
+    position: relative;
+
+    .instruction-list {
+      margin: 0px 0 0 10px;
+      padding-left: 20px;
+
+      li {
+        margin-bottom: 4px;
+        text-align: left;
+        font-size: 14px;
+      }
+    }
+
+    .close-icon {
+      position: absolute;
+      top: 19px;
+      right: 19px;
+    }
+  }
+
+
+
+  .form-item {
+    margin: 16px 0;
+    display: flex;
+
+
+    .label {
+      min-width: 120px;
+      margin-right: 12px;
+    }
+
+    .folder-warp {
+      width: 100%;
+      display: flex;
+      flex-direction: column;
+
+      .folder-input {
+        flex: 1;
+        display: flex;
+        align-items: center;
+
+        .check-button {
+          background: #DFE2E3;
+          border-radius: 6px;
+          padding: 6px 12px;
+          color: #2957FF;
+          margin-left: 20px;
+        }
+
+      }
+    }
+
+    .hint {
+      text-align: left;
+      color: #999;
+      margin-top: 6px;
+      font-size: 14px;
+      color: #FF4C00;
+      font-style: normal;
+    }
+
+    .specific-page-input {
+      flex: 1;
+    }
+  }
+
+  .image-order {
+    flex: 1;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+
+  .footer {
+    display: flex;
+    justify-content: center;
+    margin-top: 24px;
+    .footer-button{
+      padding: 10px  20px;
+    }
+  }
+}
+
+.explain-btn {
+  padding-left: 20px;
+  padding-right: 20px;
+  color: #2957FF !important;
+}
+</style>

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません