From 52f60fcfc348ad26185c22a074b8551429ac5149 Mon Sep 17 00:00:00 2001
From: peng <peng.com>
Date: 星期六, 11 十月 2025 16:31:16 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/send_coupon' into send_coupon

---
 manager/src/views/store-tag/store-tag.vue            |  272 ++++++++++++
 seller/src/views/store-coupon/coupon_store.vue       |   11 
 seller/src/views/template/goodsCustomizeTemplate.vue |  790 +++++++++++++++++++++++++++++++++++
 manager/src/api/store-tag.js                         |   66 +++
 manager/src/views/seller/shop/shopDetail.vue         |  115 +++++
 seller/src/api/goods-customeize-template.js          |   47 ++
 6 files changed, 1,287 insertions(+), 14 deletions(-)

diff --git a/manager/src/api/store-tag.js b/manager/src/api/store-tag.js
new file mode 100644
index 0000000..c709585
--- /dev/null
+++ b/manager/src/api/store-tag.js
@@ -0,0 +1,66 @@
+import service from "@/libs/axios";
+
+
+export const getPageList = (data) =>{
+  return service({
+    url: "/lmk/store-tag/page",
+    method: "GET",
+    params: data
+  })
+}
+export const getTag = (data) =>{
+  return service({
+    url: "/lmk/store-tag/list",
+    method: "GET",
+    params: data
+  })
+}
+export const addStoreTag =(data) =>{
+  return service({
+    url: "/lmk/store-tag",
+    method: "POST",
+    data: data
+  })
+}
+export const bind =(data) =>{
+  return service({
+    url: "/lmk/store-tag/bind",
+    method: "POST",
+    data: data
+  })
+}
+export const editTag = (data)=>{
+  return service({
+    url: "/lmk/store-tag",
+    method: "PUT",
+    data: data
+  })
+}
+
+export const delTag = (data)=>{
+  return service({
+    url: "/lmk/store-tag/" + data,
+    method: "DELETE",
+  })
+}
+export const removeBatch = (data) =>{
+  return service({
+    url: "/lmk/store-tag/batch",
+    method: "DELETE",
+    data:data
+  })
+}
+
+export const getStoreTagsById =(data) =>{
+  return service({
+    url: "/lmk/store-tag/storeTags/" + data,
+    method: "GET",
+  })
+}
+
+export const delTagByStoreTagRefId = (data)=>{
+  return service({
+    url: "/lmk/store-tag/delTagByStoreTagRefId/" + data,
+    method: "DELETE",
+  })
+}
diff --git a/manager/src/views/seller/shop/shopDetail.vue b/manager/src/views/seller/shop/shopDetail.vue
index c6bccdb..952b59f 100644
--- a/manager/src/views/seller/shop/shopDetail.vue
+++ b/manager/src/views/seller/shop/shopDetail.vue
@@ -121,6 +121,29 @@
             </span>
           </p>
           <p class="item">
+            <span class="label">搴楅摵鏍囩锛�</span>
+            <span class="info">
+              <template v-if="tagList?.length > 0">
+              <!-- 閬嶅巻鏍囩鍒楄〃 -->
+                <span v-for="(tag, index) in tagList" :key="tag.id">
+                  {{ tag.tagName }}
+                    <button
+                      style="width: 20px; height: 20px; line-height: 1; border: none; background: #f0f0f0; border-radius: 50%; cursor: pointer; margin-left: 4px; font-size: 12px; display: inline-flex; align-items: center; justify-content: center;"
+                      @click.stop="handleDeleteTag(tag.id, index)"
+                      title="鍒犻櫎鏍囩"
+                    >
+                    脳
+                  </button>
+                  <template v-if="index !== tagList.length - 1">銆�</template>
+                </span>
+              </template>
+            <template v-else>鏆傛棤鏍囩</template>
+            </span>
+          </p>
+          <div style="display: flex;align-items: center;justify-content: center;">
+            <Button type="primary"  @click="addStoreTag(storeInfo)">鏂板鏍囩</Button>
+          </div>
+          <p class="item">
             <span class="label">搴楅摵绠�浠嬶細</span>
             <span class="info">
               {{storeInfo.storeDesc?storeInfo.storeDesc:'鏆傛湭瀹屽杽'}}
@@ -559,6 +582,22 @@
             </Row>
           </div>
         </TabPane>
+
+        <Modal
+          v-model="showModal"
+          title="鏂板鏍囩"
+          width="800"
+          :mask-closable="false"
+        >
+          <Select v-model="tagForm.storeTagId" placeholder="璇烽�夋嫨" @on-query-change="searchChange" filterable
+                  clearable style="width: 150px">
+            <Option v-for="item in tagOptions" :value="item.id" :key="item.id">{{ item.tagName }}</Option>
+          </Select>
+          <div slot="footer">
+            <Button @click="showModal = false">鍙栨秷</Button>
+            <Button type="primary" @click="handleSubmit" :loading="submitLoading">纭畾</Button>
+          </div>
+        </Modal>
       </Tabs>
     </Card>
 
@@ -566,7 +605,7 @@
 </template>
 
 <script>
-
+  import { getTag,bind,getStoreTagsById,delTagByStoreTagRefId } from "@/api/store-tag"
   import ossManage from "@/views/sys/oss-manage/ossManage";
   import * as RegExp from '@/libs/RegExp.js';
   import {getCategoryTree} from "@/api/goods";
@@ -577,15 +616,23 @@
   export default {
     name: "member",
     components: {
-
       ossManage,
     },
     data() {
       return {
+        tagOptions:[],
+        submitLoading:false,
+        tagForm:{
+          storeId:null,
+          storeTagId:"",
+        },
+        showModal:false,
         id: "",//搴楅摵id
         categories: [], //搴楅摵闈欓煶鑼冨洿
         loading: true, // 琛ㄥ崟鍔犺浇鐘舵��
-        storeInfo: {},//搴楅摵淇℃伅
+        storeInfo: {
+        },//搴楅摵淇℃伅
+        tagList:[],
         checkAllGroup: [], //閫変腑鐨勭粡钀ュ垎绫�
         selectDate: null, // 鐢宠鏃堕棿
 
@@ -846,7 +893,69 @@
       };
     },
     methods: {
+      handleDeleteTag(storeTagRefId){
+        console.log(storeTagRefId)
+        delTagByStoreTagRefId(storeTagRefId).then(res=>{
+          if (res.code === 200){
+            this.$Message.success("鍒犻櫎鎴愬姛")
+          }
+          this.getStoreTags();
+        })
+
+      },
+
+      getStoreTags(){
+        getStoreTagsById(this.id).then(res =>{
+          if (res.code === 200){
+            this.$set(this, "tagList", res.data);
+          }
+        })
+
+      },
+
+      searchChange(val){
+        this.getStoreTag(val)
+      },
+      getStoreTag(val){
+        const params = {
+          tagName: ''
+        }
+        if (val) {
+          params.tagName = val;
+        } else {
+          params.tagName = ''
+        }
+        getTag(params).then(res =>{
+          if (res.code ===200){
+            this.tagOptions = res.data
+          }
+        })
+      },
+      handleSubmit(){
+        if (this.tagForm.storeTagId === null || this.tagForm.storeTagId ===""){
+          this.$Message.error("璇烽�夋嫨鏍囩")
+         return
+        }
+        this.showModal = false;
+          bind(this.tagForm).then(res =>{
+            if (res.code === 200){
+
+          }
+          this.getStoreTags();
+            this.getStoreTag();
+        })
+
+      },
+      addStoreTag(info){
+        console.log(info)
+        this.tagForm.storeId = info.storeId;
+        this.tagForm.storeTagId = "";
+        this.showModal = true;
+      },
       init() {
+        this.getStoreTags();
+
+        this.getStoreTag();
         //鏌ュ簵閾哄熀鏈俊鎭�
         this.getStoreInfo();
         //鏌ヨ搴楅摵鍒嗙被
diff --git a/manager/src/views/store-tag/store-tag.vue b/manager/src/views/store-tag/store-tag.vue
new file mode 100644
index 0000000..14ae529
--- /dev/null
+++ b/manager/src/views/store-tag/store-tag.vue
@@ -0,0 +1,272 @@
+<template>
+  <div>
+    <card>
+      <Form
+        ref="searchForm"
+        @keydown.enter.native="handleSearch"
+        :model="searchForm"
+        inline
+        :label-width="70"
+        class="search-form"
+      >
+        <Form-item label="鏍囩鍚嶇О" prop="tagName">
+          <Input
+            type="text"
+            v-model="searchForm.tagName"
+            clearable
+            @on-clear="handleSearch"
+            @on-change="handleSearch"
+            style="width: 160px"
+          />
+        </Form-item>
+        <Button
+          @click="handleSearch"
+          type="primary"
+          icon="ios-search"
+          class="search-btn"
+        >鎼滅储</Button
+        >
+      </Form>
+      <Row class="operation padding-row">
+        <Button @click="openAdd" type="info">娣诲姞</Button>
+        <Button @click="delBatch" type="error">鎵归噺鍒犻櫎</Button>
+      </Row>
+      <Table
+        :loading="loading"
+        border
+        :columns="columns"
+        :data="tagList"
+        ref="table"
+        sortable="custom"
+        @on-sort-change="changeSort"
+        @on-selection-change="showSelect"
+      >
+        <template slot-scope="{ row, index }" slot="action">
+          <Button type="info" size="small" style="margin-right: 5px" @click="openEdit(row)">缂栬緫鏍囩</Button>
+          <Button type="error" size="small" style="margin-right: 5px" @click="del(row)">鍒犻櫎鏍囩</Button>
+        </template>
+      </Table>
+
+      <Row type="flex" justify="end" class="mt_10">
+        <Page
+          :current="searchForm.pageNumber"
+          :total="total"
+          :page-size="searchForm.pageSize"
+          @on-change="changePage"
+          @on-page-size-change="changePageSize"
+          :page-size-opts="[10, 20, 50]"
+          size="small"
+          show-total
+          show-elevator
+          show-sizer
+        ></Page>
+      </Row>
+
+      <Modal
+        v-model="modelShow"
+        :title="modelTitle"
+      >
+        <Form ref="form" :model="form" :label-width="70" :rules="rules">
+          <FormItem label="鏍囩鍚嶇О" prop="tagName">
+            <Input v-model="form.tagName" autocomplete="off"/>
+          </FormItem>
+        </Form>
+        <div slot="footer">
+          <Button type="text" @click="modelClose">鍙栨秷</Button>
+          <Button type="primary" :loading="submitLoading" @click="saveOrUpdate">鎻愪氦</Button>
+        </div>
+      </Modal>
+    </card>
+  </div>
+</template>
+<script>
+import { getPageList,addStoreTag,editTag,delTag,removeBatch } from "@/api/store-tag"
+  export default {
+    name: "store-tag",
+    data(){
+      return{
+        loading:false,
+        columns:[
+          {
+            type: 'selection',
+            width: 60,
+            align: 'center'
+          },
+          {
+            title:'鏍囩鍚嶇О',
+            key: 'tagName',
+            minWidth: 60,
+            tooltip: true,
+          },
+          {
+            title: '鎿嶄綔',
+            key: 'action',
+            slot: 'action',
+            minWidth: 150,
+            align: 'center'
+          }
+        ],
+        total:0,
+        tagList:[],
+        searchForm: {
+          // 鎼滅储妗嗗垵濮嬪寲瀵硅薄
+          pageNumber: 1, // 褰撳墠椤垫暟
+          pageSize: 10, // 椤甸潰澶у皬
+          tagName: '', // 鏍囩鍚嶇О
+        },
+        // 瀵硅瘽妗嗘爣棰�
+        modelTitle:'',
+        modelShow:false,
+        submitLoading:false,
+        // 琛ㄥ崟
+        form: {
+          id: '',
+          tagName: '',
+        },
+        rules: {
+          tagName: [
+            {required: true, message: "鏍囩鍚嶇О涓嶈兘涓虹┖", trigger: "blur"}
+          ],
+        },
+        //澶氶��
+        selectCount: 0, // 宸查�夋暟閲�
+        selectList: [], // 宸查�夋暟鎹垪琛�
+      }
+
+    },
+    mounted(){
+      this.init();
+    },
+    methods:{
+      init(){
+        this.getTagList()
+      },
+      openAdd(){
+        this.modelTitle = "鏂板鏍囩";
+        this.modelShow = true;
+        this.form={
+          id: '',
+          tagName: '',
+        }
+      },
+      openEdit(row){
+        this.form.id = row.id;
+        this.form.tagName = row.tagName;
+        this.modelTitle= "淇敼鏍囩";
+        this.modelShow = true;
+      },
+      saveOrUpdate() {
+        this.$refs.form.validate(valid => {
+          if (valid) {
+            this.submitLoading = true
+            if (this.form.id) {
+              // 淇敼
+              editTag(this.form).then(res => {
+                if (res.code === 200) {
+                  this.$Message.success("淇敼鎴愬姛");
+                  this.modelClose()
+                  this.getTagList()
+                }
+              })
+            } else {
+              // 鏂板
+              addStoreTag(this.form).then(res => {
+                if (res.code === 200) {
+                  this.$Message.success("娣诲姞鎴愬姛");
+                  this.modelClose()
+                  this.getTagList()
+                }
+              })
+            }
+          }
+        });
+      },
+      del(row){
+        delTag(row.id).then(res =>{
+          if (res.code === 200){
+            this.$Message.success("鍒犻櫎鎴愬姛")
+          }
+          this.getTagList();
+        })
+      },
+      delBatch() {
+        if (this.selectCount <= 0) {
+          this.$Message.warning("鎮ㄨ繕鏈�夋嫨瑕佸垹闄ょ殑鏁版嵁");
+          return;
+        }
+
+        // 鏄剧ず纭瀵硅瘽妗�
+        this.$Modal.confirm({
+          title: '纭鍒犻櫎',
+          content: `鎮ㄧ‘瀹氳鍒犻櫎閫変腑鐨� ${this.selectCount} 鏉℃暟鎹悧锛焋,
+          onOk: () => {
+            // 鐢ㄦ埛鐐瑰嚮纭鍚庢墽琛屽垹闄�
+            this.removeBatch();
+          },
+          onCancel: () => {
+            this.$Message.info('宸插彇娑堝垹闄�');
+          }
+        });
+      },
+
+      async removeBatch() {
+        try {
+          const res = await removeBatch(this.selectList);
+          if (res.code === 200) {
+            this.getTagList();
+            this.selectedRows = [];
+            this.selectCount = 0;
+            this.$Message.success('鍒犻櫎鎴愬姛');
+          } else {
+            this.$Message.error(res.message || '鍒犻櫎澶辫触');
+          }
+        } catch (err) {
+          console.log(err);
+          this.$Message.error('璇锋眰澶辫触锛岃閲嶈瘯');
+        }
+      },
+      // 鍏抽棴寮圭獥
+      modelClose() {
+        this.submitLoading = false
+        this.modelShow = false
+      },
+      getTagList(){
+        this.loading = true;
+        getPageList(this.searchForm).then((res) => {
+          this.loading = false;
+          if (res.code === 200) {
+            this.tagList = res.data;
+            this.total = res.total;
+          }
+        });
+        this.loading = false;
+      },
+      handleSearch(){
+        this.searchForm.pageNumber = 1;
+        this.searchForm.pageSize = 10;
+        this.getTagList();
+      },
+      // 鍒嗛〉 鏀瑰彉椤电爜
+      changePage(v) {
+        this.searchForm.pageNumber = v;
+        this.getTagList();
+      },
+      // 鍒嗛〉 鏀瑰彉椤垫暟
+      changePageSize(v) {
+        this.searchForm.pageNumber = 1;
+        this.searchForm.pageSize = v;
+        this.getTagList();
+      },
+      changeSort(){
+
+      },
+      showSelect(e){
+        this.selectList = e.map(d => d.id);
+        this.selectCount = e.length;
+      },
+    },
+  }
+</script>
+<style scoped lang="scss">
+
+</style>
diff --git a/seller/src/api/goods-customeize-template.js b/seller/src/api/goods-customeize-template.js
new file mode 100644
index 0000000..2003aa8
--- /dev/null
+++ b/seller/src/api/goods-customeize-template.js
@@ -0,0 +1,47 @@
+import service from "../libs/axios";
+
+export const getPage = (params) =>{
+  return service({
+    url: "/lmk/goodsCustomizeTemplate",
+    method: "GET",
+    params: params
+  })
+}
+
+export const edit = (params) =>{
+  return service({
+    url: "/lmk/goodsCustomizeTemplate",
+    method: "PUT",
+    data: params
+  })
+}
+
+export const add = (params) =>{
+  return service({
+    url: "/lmk/goodsCustomizeTemplate",
+    method: "POST",
+    data: params
+  })
+}
+
+
+export const del = (params) =>{
+  return service({
+    url: "/lmk/goodsCustomizeTemplate/" +params,
+    method: "DELETE",
+  })
+}
+
+export const changeStatus = (params) =>{
+  return service({
+    url: "/lmk/goodsCustomizeTemplate/changeStatus/" +params,
+    method: "PUT",
+  })
+}
+
+export const detail = (params) =>{
+  return service({
+    url: "/lmk/goodsCustomizeTemplate/" +params,
+    method: "GET",
+  })
+}
diff --git a/seller/src/views/store-coupon/coupon_store.vue b/seller/src/views/store-coupon/coupon_store.vue
index 64862b6..3c7a21c 100644
--- a/seller/src/views/store-coupon/coupon_store.vue
+++ b/seller/src/views/store-coupon/coupon_store.vue
@@ -28,17 +28,6 @@
               <Option value="GENERATE">鐢熸垚</Option>
             </Select>
           </FormItem>
-          <FormItem label="鍚敤鐘舵��:">
-            <Select
-              v-model="listQuery.status"
-              placeholder="鍏ㄩ儴鐘舵��"
-              clearable
-              style="width: 180px"
-            >
-              <Option value="ENABLE">鍚敤</Option>
-              <Option value="DISABLE">绂佺敤</Option>
-            </Select>
-          </FormItem>
         </Form>
       </div>
     </Card>
diff --git a/seller/src/views/template/goodsCustomizeTemplate.vue b/seller/src/views/template/goodsCustomizeTemplate.vue
new file mode 100644
index 0000000..7ad97b1
--- /dev/null
+++ b/seller/src/views/template/goodsCustomizeTemplate.vue
@@ -0,0 +1,790 @@
+<template>
+  <div class="coupon-management">
+    <!-- 鎼滅储鍖哄煙 -->
+    <Card class="filter-container">
+      <div class="filter-header">
+        <Icon type="ios-search" size="18" />
+        <span class="filter-title">绛涢�夋悳绱�</span>
+        <div class="filter-actions">
+          <Button type="primary" @click="getList" size="small">鏌ヨ鎼滅储</Button>
+          <Button @click="handleResetSearch" size="small" style="margin-left: 10px;">閲嶇疆</Button>
+          <Button type="primary" @click="addModal" size="small" style="margin-left: 10px;">鏂板</Button>
+        </div>
+      </div>
+      <div class="filter-content">
+        <Form
+          :model="listQuery"
+          :label-width="90"
+          class="search-form"
+          inline
+        >
+          <FormItem label="妯℃澘鍚嶇О">
+            <Input
+              v-model="listQuery.templateName"
+              style="width: 180px;"
+              clearable
+              placeholder="璇疯緭鍏ユā鏉垮悕绉�"
+            >
+            </Input>
+          </FormItem>
+          <FormItem label="鍚敤鐘舵��:">
+            <Select
+              v-model="listQuery.status"
+              placeholder="鍏ㄩ儴鐘舵��"
+              clearable
+              style="width: 180px"
+            >
+              <Option value="DISABLE">鏈惎鐢�</Option>
+              <Option value="ENABLE">宸茶捣鐢�</Option>
+            </Select>
+          </FormItem>
+
+
+        </Form>
+      </div>
+    </Card>
+
+    <!-- 琛ㄦ牸鍖哄煙 -->
+    <Card class="table-container">
+      <Table
+        :loading="listLoading"
+        border
+        :columns="tableColumns"
+        :data="list"
+        ref="table"
+        class="coupon-table"
+      >
+        <template slot-scope="{ row }" slot="action">
+          <Button
+            type="primary"
+            size="small"
+            style="margin-right: 5px"
+            @click="delTemplate(row)"
+          >鍒犻櫎</Button
+          >
+          <Button
+            type="primary"
+            size="small"
+            style="margin-right: 5px"
+            @click="updateTemplate(row)"
+          >淇敼</Button
+          >
+          <Button
+            type="primary"
+            size="small"
+            style="margin-right: 5px"
+            @click="changeStatus(row)"
+          >{{ row.status ==='ENABLE' ? '绂佺敤':'寮�鍚�'}}</Button
+          >
+        </template>
+      </Table>
+    </Card>
+
+    <!-- 鍒嗛〉鍖哄煙 -->
+    <div class="pagination-container">
+      <Page
+        :current="listQuery.pageNumber"
+        :page-size="listQuery.pageSize"
+        :total="total"
+        :page-size-opts="[10, 20, 30, 50]"
+        show-elevator
+        show-sizer
+        show-total
+        @on-change="handleCurrentChange"
+        @on-page-size-change="handleSizeChange"
+      />
+    </div>
+
+    <Modal
+      v-model="showModal"
+      :title="modalTitle"
+      width="800"
+      :mask-closable="false"
+    >
+      <Form
+        ref="form"
+        :model="form"
+        :label-width="90"
+        class="search-form"
+        :rules="formRules"
+        inline>
+        <Row>
+          <Col :span="24">
+            <FormItem label="妯℃澘鍚嶇О" prop="templateName">
+              <Input v-model="form.templateName" autocomplete="off"/>
+            </FormItem>
+          </Col>
+          <Col :span="24">
+            <FormItem label="閫夋嫨鏂板鏍囬">
+              <Button type="primary" @click="addTitle('TEXT')">娣诲姞鏂囧瓧鏍囬</Button>
+              <Button type="primary" style="margin-left: 10px" @click="addTitle('IMAGE')">娣诲姞鍥剧墖鏍囬</Button>
+            </FormItem>
+          </Col>
+
+          <Col :span="24">
+
+            <!-- 宸叉坊鍔犵殑鏍囬鍒楄〃 -->
+            <div v-for="(title, index) in form.titles" :key="index" class="title-item">
+              <card class="title-text">
+                <Input v-model="title.templateTitle" placeholder="璇疯緭鍏ユ枃鏈爣棰�" style="width: 300px; margin-top: 8px;"/>
+                <Button  class="delete-btn" type="text" @click="removeTitle(index)" style="color: #ff4d4f;">鍒犻櫎</Button>
+              </card>
+<!--              <card v-if="title.contentType === 'IMAGE'" class="title-image">-->
+<!--                <Upload-->
+<!--                  v-if="!title.imgTempUrl"-->
+<!--                  :before-upload="(file) => handleBeforeUpload(file,title)"-->
+<!--                  :format="['jpg','jpeg','png','gif']"-->
+<!--                  :max-size="20480"-->
+<!--                  action=""-->
+<!--                  accept="image/*"-->
+<!--                >-->
+<!--                  <Button icon="ios-cloud-upload-outline">涓婁紶灏侀潰鍥剧墖</Button>-->
+<!--                </Upload>-->
+<!--                <div v-else class="upload-file-info">-->
+
+<!--                  <Col :span="24">-->
+<!--                    <img :src="title.imgTempUrl" alt="娲诲姩鍥剧墖" class="preview-image-limit">-->
+<!--                  </Col>-->
+<!--                  <Button type="text" @click="handleRemoveImage(title)">鍒犻櫎</Button>-->
+<!--                </div>-->
+
+<!--                <Button class="delete-btn" type="text" @click="removeTitle(index)" style="color: #ff4d4f;">鍒犻櫎</Button>-->
+<!--              </card>-->
+            </div>
+          </Col>
+          <Col :span="24">
+            <FormItem  label="妯℃澘鍥剧墖" >
+              <div style="display: flex; flex-wrap: wrap;">
+                <vuedraggable :animation="200" :list="showListImages">
+                  <div v-for="(item, __index) in showListImages" :key="__index"
+                       class="demo-upload-list">
+                    <template>
+                      <img :src="item"/>
+                      <div class="demo-upload-list-cover">
+                        <div>
+                          <Icon size="30" type="md-search" @click.native="handleViewGoodsPicture(item)"></Icon>
+                          <Icon size="30" type="md-trash" @click.native="handleRemoveGoodsPicture(item)"></Icon>
+                        </div>
+                      </div>
+                    </template>
+                  </div>
+                </vuedraggable>
+              </div>
+              <div style="width: 100%;display: flex;justify-content: start;margin-top: 10px;">
+                <Button @click="handleCLickImg('goodsGalleryFiles')" type="primary">涓婁紶鍥剧墖</Button>
+              </div>
+              <Modal v-model="goodsPictureVisible" title="View Image">
+                <img v-if="goodsPictureVisible" :src="previewGoodsPicture" style="width: 100%"/>
+              </Modal>
+            </FormItem>
+          </Col>
+
+        </Row>
+
+
+      </Form>
+      <div slot="footer">
+        <Button type="text" @click="showModal = false">鍏抽棴</Button>
+        <Button type="primary" :loading="submitLoading" @click="saveOrUpdate">纭</Button>
+      </div>
+    </Modal>
+
+    <Modal v-model="picModelFlag" width="1200px">
+      <div class="demo-upload-list" v-for="(item,index) in showListImages">
+        <template>
+          <img :src="item">
+          <div class="demo-upload-list-cover">
+            <Icon type="ios-eye-outline" @click.native="handleView(item)"></Icon>
+            <Icon type="ios-trash-outline" @click.native="handleRemove(null,index)"></Icon>
+          </div>
+        </template>
+      </div>
+      <div class="demo-upload-list">
+        <Upload
+          :before-upload="upLoadImg"
+          accept="image/*"
+          action="-"
+          type="drag"
+          style=" display: inline-block;width: 58px"
+        >
+          <div style="width: 58px;height:58px;line-height: 58px;">
+            <Icon type="ios-camera" size="20"></Icon>
+          </div>
+        </Upload>
+      </div>
+    </Modal>
+    <Modal v-model="visible" title="棰勮鍥剧墖">
+      <img v-if="visible" :src="previewPicture" style="width: 100%">
+    </Modal>
+
+  </div>
+</template>
+<script>
+
+import { getPage,edit,add,del,changeStatus,detail } from "@/api/goods-customeize-template.js"
+import vuedraggable from "vuedraggable";
+import COS from "cos-js-sdk-v5";
+import {getFileKey} from "@/utils/file.js";
+import {getFilePreview, getSts} from "@/api/file";
+
+export default {
+  name: 'GoodsCustomizeTemplate',
+  components: {vuedraggable},
+  data() {
+    return {
+      // 棰勮鍥剧墖璺緞
+      previewPicture: "",
+      visible:false,
+      picModelFlag: false, // 鍥剧墖閫夋嫨鍣�
+      goodsPictureVisible: false,
+      showListImages: [],
+      listImages: [],
+      previewGoodsPicture: "",
+      selectedFormBtnName: "", // 鐐瑰嚮鍥剧墖缁戝畾form
+
+      //鏂板
+      modalTitle:"",
+      showModal:false,
+
+      list: [],
+      total: 0,
+      listLoading: false,
+      submitLoading: false,
+
+      listQuery: {
+        pageNumber: 1,
+        pageSize: 10,
+        templateName:"",
+        status:"",
+      },
+      form:{
+        titles: []
+      },
+      // 琛ㄥ崟楠岃瘉瑙勫垯
+      formRules: {
+        templateName:[
+          {required:true,message:"妯℃澘鍚嶄笉鑳戒负绌�",trigger:"blur"}
+        ]
+      },
+
+      // 琛ㄥご閰嶇疆
+      tableColumns: [
+        {
+          type: 'selection',
+          width: 60,
+          align: 'center'
+        },
+        {
+          title: '妯℃澘鍚嶇О',
+          key: 'templateName',
+          align: 'center',
+          ellipsis: true,
+          tooltip: true
+        },
+        {
+          title: '鍚敤鐘舵��',
+          key: 'status',
+          width: 120,
+          align: 'center',
+          render: (h, params) => {
+            const status = params.row.status;
+            const color = status === 'ENABLE' ? 'success' : status === 'DISABLE' ? 'default' : 'warning';
+            const text = status === 'ENABLE' ? '鍚敤' : status === 'DISABLE' ? '鏈惎鐢�' : '鏈煡';
+
+            return h('Tag', {
+              props: {
+                color: color
+              }
+            }, text);
+          }
+        },
+        {
+          title: '鎿嶄綔',
+          slot: 'action',
+          width: 200,
+          align: 'center'
+        },
+      ]
+    }
+  },
+  methods: {
+    changeStatus(row){
+      changeStatus(row.id).then(res =>{
+        if (res.code ===200 ){
+          this.$Message.success(res.msg)
+        }
+        this.getList()
+      })
+
+    },
+    async updateTemplate(row){
+      this.showModal = true;
+      this.modalTitle = "淇敼妯℃澘";
+
+      await detail(row.id).then(res =>{
+        if (res.code === 200){
+          this.form = {
+            id: res.data.id,
+            templateName:  res.data.templateName || '', // 纭繚涓嶄负undefined
+            titles:  res.data.titles || [] // 鏍囬鍒楄〃
+          };
+          this.form.titles.map((i) =>{
+            i.id = null;
+            return i
+          })
+          this.listImages = res.data.listImages.map((i) => {
+            return i.imgUrl
+          });
+
+        }
+      })
+      const stsInfo = await getSts();
+      const endpoint = stsInfo.data.endpoint;
+      console.log()
+      this.showListImages = this.listImages.map((i) => {
+        if (i!=null&&i.indexOf('http')===-1)
+          return endpoint+'/'+i;
+        else return i;
+      })
+
+    },
+    delTemplate(row){
+      del(row.id).then(res =>{
+        if (res.code === 200){
+          this.$Message.success(res.msg)
+        }
+        this.getList()
+      })
+    },
+    async upLoadImg(file) {
+      console.log("鎵撳嵃涓婁紶1")
+      if (this.listImages.length >= 10) {
+        this.$Message.error("鍥剧墖涓婁紶涓嶈兘瓒呰繃10涓�");
+        return;
+      }
+      console.log("鎵撳嵃涓婁紶2")
+      try {
+        // 鑾峰彇鏂囦欢涓婁紶涓存椂瀵嗛挜
+        const sts = await getSts();
+        const cos = new COS({
+          getAuthorization: async function (options, callback) {
+            callback({
+              TmpSecretId: sts.data.tmpSecretId,
+              TmpSecretKey: sts.data.tmpSecretKey,
+              SecurityToken: sts.data.sessionToken,
+              // 寤鸿杩斿洖鏈嶅姟鍣ㄦ椂闂翠綔涓虹鍚嶇殑寮�濮嬫椂闂达紝閬垮厤瀹㈡埛绔湰鍦版椂闂村亸宸繃澶у鑷寸鍚嶉敊璇�
+              StartTime: sts.data.stsStartTime, // 鏃堕棿鎴筹紝鍗曚綅绉掞紝濡傦細1580000000
+              ExpiredTime: sts.data.stsEndTime,// 鏃堕棿鎴筹紝鍗曚綅绉掞紝濡傦細1580000000
+              ScopeLimit: true, // 缁嗙矑搴︽帶鍒舵潈闄愰渶瑕佽涓� true锛屼細闄愬埗瀵嗛挜鍙湪鐩稿悓璇锋眰鏃堕噸澶嶄娇鐢�
+            });
+          }
+        })
+        const fileKey = getFileKey(file.name)
+        const upData = await cos.uploadFile({
+          Bucket: sts.data.bucket,
+          Region: sts.data.region,
+          Key: fileKey,
+          Body: file, // 瑕佷笂浼犵殑鏂囦欢瀵硅薄銆�
+          SliceSize: 1024 * 1024 * 5,
+          onProgress: function (progressData) {
+            console.log('涓婁紶杩涘害锛�', progressData);
+          },
+        });
+        console.log("涓婁紶鎴愬姛", upData)
+        this.$nextTick(() => {
+          this.listImages.push(fileKey);
+          this.showListImages.push(sts.data.endpoint + "/" + fileKey);
+
+        })
+
+      } catch (e) {
+        console.log("涓婁紶澶辫触", upData)
+        return false;
+      } finally {
+
+      }
+      return false;
+    },
+    handleView(url) {
+      this.previewPicture = url;
+      this.visible = true;
+    },
+    handleRemove(item, index) {
+      if (!item) {
+        this.listImages.splice(index, 1);
+        this.showListImages.splice(index, 1);
+
+      } else {
+        console.log('绉婚櫎娴嬭瘯',item, index);
+        item.splice(index, 1)
+      }
+      this.previewPicture = "";
+    },
+
+    handleCLickImg(val, index) {
+      this.picModelFlag = true;
+      this.selectedFormBtnName = val;
+    },
+    handleViewGoodsPicture(url) {
+      this.previewGoodsPicture = url;
+      this.goodsPictureVisible = true;
+    },
+    // 绉婚櫎鍟嗗搧鍥剧墖
+    handleRemoveGoodsPicture(file) {
+      // this.baseInfoForm.goodsGalleryFiles =
+      //   this.baseInfoForm.goodsGalleryFiles.filter((i) => i !== file);
+    },
+
+    addTitle(type){
+      this.form.titles.push(
+        {
+          contentType: type,
+          templateTitle: '',
+          imgTempUrl:null,//鍥剧墖涓存椂鍦板潃
+          file:null,
+        });
+    },
+    // 绉婚櫎鏍囬
+    removeTitle(index) {
+      this.form.titles.splice(index, 1);
+    },
+
+    saveOrUpdate() {
+      const submitData = {
+        ...this.form,
+        listImages: this.listImages,
+      };
+
+      console.log(submitData)
+
+      const isTitlesValid = (titles) => {
+        console.log("杩涘叆鍒ゆ柇")
+        // 鏁扮粍鏈韩涓簄ull/undefined鎴栫┖鏁扮粍
+        if (!titles || titles.length === 0) return false;
+        // 妫�鏌ユ瘡涓厓绱犵殑鏈夋晥鎬�
+        return titles.every(title => {
+          // 鍩虹鏍¢獙锛氬繀椤绘槸瀵硅薄涓斿寘鍚玞ontentType
+          if (!title || typeof title !== 'object' || !title.contentType) return false;
+
+            return title.templateTitle !== null
+              && title.templateTitle !== undefined
+              && title.templateTitle.trim() !== '';
+          // 鏈煡绫诲瀷
+          return false;
+        });
+      };
+
+      const isImagesValid = (images) => {
+        if (!images || images.length === 0) return false;
+        return images.some(img => img && (typeof img === 'string' ? img.trim() !== '' : true));
+      };
+
+      this.$refs.form.validate(async (valid) => {
+        if (valid) {
+          try {
+            // 鍏堝鐞嗘墍鏈夊浘鐗囦笂浼狅紙绛夊緟寮傛瀹屾垚锛�
+            this.submitLoading = true;
+
+            // 涓婁紶瀹屾垚鍚庡啀楠岃瘉
+            if (!isTitlesValid(submitData.titles)) {
+              this.$Message.error('璇风‘淇濇墍鏈夋爣棰橀兘濉啓瀹屾暣锛堟枃鏈爣棰樹笉鑳戒负绌猴紝鍥剧墖鏍囬闇�涓婁紶鎴愬姛锛�');
+              this.submitLoading = false;
+              return;
+            }
+
+            if (!isImagesValid(submitData.listImages)) {
+              this.$Message.error('璇锋坊鍔犳湁鏁堢殑鍥剧墖锛堣嚦灏戜竴寮犲浘鐗囷級');
+              this.submitLoading = false;
+              return;
+            }
+
+            // 楠岃瘉閫氳繃锛屾彁浜ゆ暟鎹�
+            const api = this.form.id ? edit : add;
+            const res = await api(submitData);
+
+            if (res.code === 200) {
+              this.$Message.success(res.msg);
+              this.modelShow = false;
+              this.getList();
+            } else {
+              this.$Message.error(res.msg);
+            }
+          } catch (error) {
+            this.$Message.error('鎻愪氦澶辫触锛岃閲嶈瘯');
+            console.error(error);
+          } finally {
+            this.submitLoading = false;
+            this.showModal = false;
+          }
+        }
+      });
+    },
+    addModal(){
+      this.form = {
+        titles: []
+      };
+      this.showModal = true;
+      this.modalTitle = "鏂板妯℃澘";
+      this.showListImages =[];
+      this.listImages =[];
+    },
+    detail(row){
+      console.log(row)
+    },
+
+    // 鑾峰彇鍒楄〃鏁版嵁
+    getList() {
+      this.listLoading = true;
+      // 妯℃嫙API璋冪敤
+      getPage(this.listQuery).then(res =>{
+        this.listLoading = false;
+        this.list = res.data;
+        this.total = res.total;
+      })
+    },
+
+    // 閲嶇疆鎼滅储鏉′欢
+    handleResetSearch() {
+      this.listQuery = {
+        pageNumber: 1,
+        pageSize: 10,
+        storeName: "",
+        couponName: "",
+        generateStatus: "",
+        status: ""
+      };
+      this.getList();
+    },
+
+    // 鍒嗛〉澶у皬鏀瑰彉
+    handleSizeChange(size) {
+      this.listQuery.pageSize = size;
+      this.listQuery.pageNumber = 1;
+      this.getList();
+    },
+
+    // 褰撳墠椤电爜鏀瑰彉
+    handleCurrentChange(pageNumber) {
+      this.listQuery.pageNumber = pageNumber;
+      this.getList();
+    },
+
+    // 鎻愪氦琛ㄥ崟
+    handleSubmit() {
+      this.$refs.dataForm.validate((valid) => {
+        if (valid) {
+        }
+      });
+    }
+  },
+  mounted() {
+    this.getList();
+  }
+}
+</script>
+
+<style scoped lang="less">
+.preview-image-limit{
+  max-width: 100%;
+  max-height: 200px;
+  object-fit: contain;
+  border-radius: 4px;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  display: block;
+}
+.delete-btn {
+  position: absolute; /* 缁濆瀹氫綅 */
+  top: 12px; /* 璺濈椤堕儴鐨勮窛绂� */
+  right: 12px; /* 璺濈鍙充晶鐨勮窛绂� */
+  padding: 4px 8px; /* 璋冩暣鎸夐挳澶у皬 */
+  line-height: 1; /* 璋冩暣琛岄珮 */
+}
+.title-item {
+  margin-bottom: 12px;
+  padding-bottom: 12px;
+  border-bottom: 1px dashed #e9e9e9;
+}
+
+.title-type-selector {
+  display: flex;
+  justify-content: center;
+  padding: 20px 0;
+}
+
+.type-option {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  margin: 0 20px;
+  cursor: pointer;
+  padding: 15px;
+  border-radius: 4px;
+  transition: all 0.3s;
+
+  &:hover {
+    background-color: #f5f7fa;
+  }
+
+  span {
+    margin-top: 10px;
+  }
+}
+.coupon-management {
+  padding: 16px;
+  background: #f5f7f9;
+  min-height: 100vh;
+}
+
+.filter-container {
+  margin-bottom: 16px;
+
+  .filter-header {
+    display: flex;
+    align-items: center;
+    margin-bottom: 16px;
+
+    .filter-title {
+      margin-left: 8px;
+      font-weight: 600;
+      font-size: 16px;
+    }
+
+    .filter-actions {
+      margin-left: auto;
+    }
+  }
+
+  .filter-content {
+    .search-form {
+      /deep/ .ivu-form-item {
+        margin-bottom: 16px;
+        margin-right: 16px;
+      }
+    }
+  }
+}
+
+.operation-container {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16px;
+  padding: 12px 16px;
+  background: #fff;
+  border-radius: 4px;
+  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
+
+  .operation-info {
+    color: #999;
+    font-size: 14px;
+  }
+}
+
+.table-container {
+  margin-bottom: 16px;
+
+  .coupon-table {
+    /deep/ .ivu-table-cell {
+      padding: 8px 12px;
+    }
+
+    .action-btns {
+      display: flex;
+      justify-content: center;
+
+      button {
+        margin: 0 2px;
+      }
+    }
+  }
+}
+
+.pagination-container {
+  display: flex;
+  justify-content: flex-end;
+  background: #fff;
+  padding: 12px 16px;
+  border-radius: 4px;
+  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
+}
+
+// 鍝嶅簲寮忚皟鏁�
+@media (max-width: 768px) {
+  .coupon-management {
+    padding: 8px;
+  }
+
+  .filter-content .search-form {
+    /deep/ .ivu-form-item {
+      width: 100%;
+      margin-right: 0;
+
+      .ivu-form-item-content {
+        width: 100%;
+
+        .ivu-input, .ivu-select {
+          width: 100% !important;
+        }
+      }
+    }
+  }
+
+  .operation-container {
+    flex-direction: column;
+    align-items: flex-start;
+
+    .add-btn {
+      margin-bottom: 8px;
+    }
+  }
+
+  .action-btns {
+    flex-direction: column;
+
+    button {
+      margin: 2px 0 !important;
+      width: 100%;
+    }
+  }
+}
+.demo-upload-list {
+  display: inline-block;
+  width: 60px;
+  height: 60px;
+  text-align: center;
+  line-height: 60px;
+  border: 1px solid transparent;
+  border-radius: 4px;
+  overflow: hidden;
+  background: #fff;
+  position: relative;
+  box-shadow: 0 1px 1px rgba(0, 0, 0, .2);
+  margin-right: 4px;
+}
+
+.demo-upload-list img {
+  width: 100%;
+  height: 100%;
+}
+
+.demo-upload-list-cover {
+  display: none;
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background: rgba(0, 0, 0, .6);
+}
+
+.demo-upload-list:hover .demo-upload-list-cover {
+  display: block;
+}
+
+.demo-upload-list-cover i {
+  color: #fff;
+  font-size: 20px;
+  cursor: pointer;
+  margin: 0 2px;
+}
+</style>

--
Gitblit v1.8.0