From caad7caa1e3409568ffaf8312064731c9b6147b0 Mon Sep 17 00:00:00 2001
From: MrShi <1878285526@qq.com>
Date: 星期五, 10 四月 2026 20:03:13 +0800
Subject: [PATCH] 门店累表

---
 admin/src/components/common/uploadImages.vue          |  109 ++++++
 admin/src/components/business/OperaShopInfoWindow.vue |  473 ++++++++++++++++++++++++++
 admin/src/api/system/common.js                        |    5 
 admin/src/api/business/shopInfo.js                    |   19 
 admin/src/views/business/storeList.vue                |   77 +++
 admin/src/components/business/OperaShopEditWindow.vue |  369 ++++++++++++++++++++
 6 files changed, 1,036 insertions(+), 16 deletions(-)

diff --git a/admin/src/api/business/shopInfo.js b/admin/src/api/business/shopInfo.js
index a04141c..d394d7f 100644
--- a/admin/src/api/business/shopInfo.js
+++ b/admin/src/api/business/shopInfo.js
@@ -15,7 +15,7 @@
 }
 
 export function updateById (data) {
-  return request.post('/business/shopInfo/updateById', data)
+  return request.post('/business/shopInfo/updateShop', data)
 }
 
 export function deleteById (id) {
@@ -28,4 +28,19 @@
       ids
     }
   })
-}
\ No newline at end of file
+}
+
+// 淇敼闂ㄥ簵鐘舵��
+export function changeStatus (data) {
+  return request.post('/business/shopInfo/changeStatus', data)
+}
+
+// 闂ㄥ簵璇︽儏
+export function detail (id) {
+  return request.get(`/business/shopInfo/detail/${id}`)
+}
+
+// 閲嶇疆瀵嗙爜
+export function resetPassword (data) {
+  return request.post('/business/shopInfo/resetPassword', data)
+}
diff --git a/admin/src/api/system/common.js b/admin/src/api/system/common.js
index 1f2087f..eceb493 100644
--- a/admin/src/api/system/common.js
+++ b/admin/src/api/system/common.js
@@ -34,3 +34,8 @@
     autoLogin: false
   })
 }
+
+// 涓婁紶鏂囦欢
+export function upload (data) {
+  return request.post('/web/public/upload', data)
+}
\ No newline at end of file
diff --git a/admin/src/components/business/OperaShopEditWindow.vue b/admin/src/components/business/OperaShopEditWindow.vue
new file mode 100644
index 0000000..f9e942c
--- /dev/null
+++ b/admin/src/components/business/OperaShopEditWindow.vue
@@ -0,0 +1,369 @@
+<template>
+  <GlobalWindow
+    title="缂栬緫闂ㄥ簵"
+    :visible.sync="visible"
+    width="60%"
+    :confirm-working="isWorking.save"
+    @confirm="handleConfirm"
+  >
+    <el-form ref="form" :model="form" label-width="120px" :rules="rules" class="inline-form">
+      <div class="form-section">
+        <h4 class="section-title">璐﹀彿淇℃伅</h4>
+        <el-form-item label="娉ㄥ唽鎵嬫満鍙�" prop="telephone">
+          <el-input v-model="form.telephone" disabled></el-input>
+        </el-form-item>
+        <div class="password-tip">榛樿瀵嗙爜榛樿涓猴細銆愭墜鏈哄彿鍚庡叚浣�+@123456"锛屽鍚庡叚浣嶄负981923锛岄粯璁ゅ瘑鐮侊細981923@123456</div>
+      </div>
+
+      <div class="form-section">
+        <h4 class="section-title">鍩烘湰淇℃伅</h4>
+        <el-form-item label="闂ㄥ簵鍚嶇О" prop="name">
+          <el-input v-model="form.name" placeholder="璇疯緭鍏ラ棬搴楀悕绉�"></el-input>
+        </el-form-item>
+        <el-form-item label="闂ㄥ簵绫诲瀷" prop="companyType">
+          <el-radio-group v-model="form.companyType" disabled>
+            <el-radio :label="1">浼佷笟</el-radio>
+            <el-radio :label="0">涓汉</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="鑱旂郴浜�" prop="linkName">
+          <el-input v-model="form.linkName" placeholder="璇疯緭鍏ヨ仈绯讳汉"></el-input>
+        </el-form-item>
+        <el-form-item label="鑱旂郴鐢佃瘽" prop="linkPhone">
+          <el-input v-model="form.linkPhone" placeholder="璇疯緭鍏ヨ仈绯荤數璇�"></el-input>
+        </el-form-item>
+        <el-form-item label="韬唤璇佸彿" prop="idcard">
+          <el-input v-model="form.idcard" placeholder="璇疯緭鍏ヨ韩浠借瘉鍙�"></el-input>
+        </el-form-item>
+        <el-form-item label="鎵�鍦ㄧ渷甯傚尯" prop="areaCode">
+          <el-cascader v-model="form.areaCode" :options="areaOptions" placeholder="璇烽�夋嫨鐪佸競鍖�"></el-cascader>
+        </el-form-item>
+        <el-form-item label="闂ㄥ簵鍦板潃" prop="address">
+          <el-input v-model="form.address" placeholder="璇疯緭鍏ラ棬搴楀湴鍧�"></el-input>
+        </el-form-item>
+        <el-form-item label="缁忕含搴�" prop="longitude">
+          <div class="longitude-latitude">
+            <el-input v-model="form.longitude" placeholder="缁忓害" style="width: 150px;"></el-input>
+            <span class="separator">-</span>
+            <el-input v-model="form.latitude" placeholder="绾害" style="width: 150px;"></el-input>
+            <el-button type="primary" @click="openMapSelector">閫夋嫨</el-button>
+          </div>
+        </el-form-item>
+      </div>
+
+      <div class="form-section">
+        <el-tabs v-model="qualificationTab">
+          <el-tab-pane v-if="form.companyType === 1" label="涓讳綋璧勮川锛堜紒涓氾級" name="enterprise">
+            <el-form-item label="娉曚汉濮撳悕" prop="legalPersonName">
+              <el-input v-model="form.legalPersonName" placeholder="璇疯緭鍏ユ硶浜哄鍚�"></el-input>
+            </el-form-item>
+            <el-form-item label="娉曚汉鎵嬫満鍙�" prop="legalPersonPhone">
+              <el-input v-model="form.legalPersonPhone" placeholder="璇疯緭鍏ユ硶浜烘墜鏈哄彿"></el-input>
+            </el-form-item>
+            <el-form-item label="娉曚汉韬唤璇佸彿" prop="legalPersonCard">
+              <el-input v-model="form.legalPersonCard" placeholder="璇疯緭鍏ユ硶浜鸿韩浠借瘉鍙�"></el-input>
+            </el-form-item>
+            <el-form-item label="娉曚汉韬唤璇佹闈�" prop="idcardImg">
+              <UploadImages
+                :fileList="form.idcardImg"
+                :uploadData="{ folder: 'shop' }"
+                @getFileList="e => form.idcardImg.push(e)"
+                @deleteRow="index => form.idcardImg.splice(index, 1)" />
+            </el-form-item>
+            <el-form-item label="娉曚汉韬唤璇佸弽闈�" prop="idcardImgBack">
+              <UploadImages
+                :fileList="form.idcardImgBack"
+                :uploadData="{ folder: 'shop' }"
+                @getFileList="e => form.idcardImgBack.push(e)"
+                @deleteRow="index => form.idcardImgBack.splice(index, 1)" />
+            </el-form-item>
+            <el-form-item label="钀ヤ笟鎵х収" prop="businessImg">
+              <UploadImages
+                :fileList="form.businessImg"
+                :uploadData="{ folder: 'shop' }"
+                @getFileList="e => form.businessImg.push(e)"
+                @deleteRow="index => form.businessImg.splice(index, 1)" />
+            </el-form-item>
+          </el-tab-pane>
+          <el-tab-pane v-if="form.companyType === 0" label="涓讳綋璧勮川锛堜釜浜猴級" name="personal">
+            <el-form-item label="韬唤璇佹闈�" prop="idcardImg">
+              <UploadImages
+                :fileList="form.idcardImg"
+                :uploadData="{ folder: 'shop' }"
+                @getFileList="e => form.idcardImg.push(e)"
+                @deleteRow="index => form.idcardImg.splice(index, 1)" />
+            </el-form-item>
+            <el-form-item label="韬唤璇佸弽闈�" prop="idcardImgBack">
+              <UploadImages
+                :fileList="form.idcardImgBack"
+                :uploadData="{ folder: 'shop' }"
+                @getFileList="e => form.idcardImgBack.push(e)"
+                @deleteRow="index => form.idcardImgBack.splice(index, 1)" />
+            </el-form-item>
+            <el-form-item label="鏈夋晥鍔冲姩鍚堝悓" prop="laborContractImgs">
+              <UploadImages
+                :fileList="form.laborContractImgs"
+                :uploadData="{ folder: 'shop' }"
+                :maxCount="3"
+                @getFileList="e => form.laborContractImgs.push(e)"
+                @deleteRow="index => form.laborContractImgs.splice(index, 1)" />
+            </el-form-item>
+            <el-form-item label="绀句繚缂寸撼璇佹槑" prop="socialSecurityImgs">
+              <UploadImages
+                :fileList="form.socialSecurityImgs"
+                :uploadData="{ folder: 'shop' }"
+                :maxCount="3"
+                @getFileList="e => form.socialSecurityImgs.push(e)"
+                @deleteRow="index => form.socialSecurityImgs.splice(index, 1)" />
+            </el-form-item>
+          </el-tab-pane>
+          <el-tab-pane label="闂ㄥ簵鐓х墖鍙婂叾浠栨潗鏂�" name="photos">
+            <el-form-item label="闂ㄥ簵闂ㄥご鐓�" prop="storeFrontImgs">
+              <UploadImages
+                :fileList="form.storeFrontImgs"
+                :uploadData="{ folder: 'shop' }"
+                :maxCount="3"
+                @getFileList="e => form.storeFrontImgs.push(e)"
+                @deleteRow="index => form.storeFrontImgs.splice(index, 1)" />
+            </el-form-item>
+            <el-form-item label="闂ㄥ簵鍐呴儴鐓х墖" prop="storeInteriorImgs">
+              <UploadImages
+                :fileList="form.storeInteriorImgs"
+                :uploadData="{ folder: 'shop' }"
+                :maxCount="3"
+                @getFileList="e => form.storeInteriorImgs.push(e)"
+                @deleteRow="index => form.storeInteriorImgs.splice(index, 1)" />
+            </el-form-item>
+            <el-form-item label="鍏跺畠鏉愭枡" prop="otherMaterialImgs">
+              <UploadImages
+                :fileList="form.otherMaterialImgs"
+                :uploadData="{ folder: 'shop' }"
+                :maxCount="3"
+                @getFileList="e => form.otherMaterialImgs.push(e)"
+                @deleteRow="index => form.otherMaterialImgs.splice(index, 1)" />
+            </el-form-item>
+          </el-tab-pane>
+        </el-tabs>
+      </div>
+    </el-form>
+  </GlobalWindow>
+</template>
+
+<script>
+import BaseOpera from '@/components/base/BaseOpera'
+import GlobalWindow from '@/components/common/GlobalWindow'
+import UploadImages from '@/components/common/uploadImages'
+import { detail, updateById } from '@/api/business/shopInfo'
+import { listByParentId } from '@/api/business/areas'
+export default {
+  name: 'OperaShopEditWindow',
+  extends: BaseOpera,
+  components: { GlobalWindow, UploadImages },
+  data () {
+    return {
+      form: {
+        id: null,
+        telephone: '',
+        name: '',
+        companyType: 1,
+        linkName: '',
+        linkPhone: '',
+        idcard: '',
+        areaCode: [],
+        provinceId: '',
+        cityId: '',
+        areaId: '',
+        address: '',
+        longitude: '',
+        latitude: '',
+        legalPersonName: '',
+        legalPersonPhone: '',
+        legalPersonCard: '',
+        idcardImg: '',
+        idcardImgBack: '',
+        businessLicenseImg: '',
+        laborContractImgs: '',
+        socialSecurityImgs: '',
+        storeFrontImgs: '',
+        storeInteriorImgs: '',
+        otherMaterialImgs: ''
+      },
+      rules: {
+        name: [{ required: true, message: '璇疯緭鍏ラ棬搴楀悕绉�', trigger: 'blur' }],
+        companyType: [{ required: true, message: '璇烽�夋嫨闂ㄥ簵绫诲瀷', trigger: 'change' }],
+        linkName: [{ required: true, message: '璇疯緭鍏ヨ仈绯讳汉', trigger: 'blur' }],
+        linkPhone: [{ required: true, message: '璇疯緭鍏ヨ仈绯荤數璇�', trigger: 'blur' }],
+        idcard: [{ required: true, message: '璇疯緭鍏ヨ韩浠借瘉鍙�', trigger: 'blur' }],
+        areaCode: [{ required: true, message: '璇烽�夋嫨鐪佸競鍖�', trigger: 'change' }],
+        address: [{ required: true, message: '璇疯緭鍏ラ棬搴楀湴鍧�', trigger: 'blur' }],
+        longitude: [{ required: true, message: '璇烽�夋嫨缁忕含搴�', trigger: 'blur' }],
+        legalPersonName: [{ required: true, message: '璇疯緭鍏ユ硶浜哄鍚�', trigger: 'blur' }],
+        legalPersonPhone: [{ required: true, message: '璇疯緭鍏ユ硶浜烘墜鏈哄彿', trigger: 'blur' }],
+        legalPersonCard: [{ required: true, message: '璇疯緭鍏ユ硶浜鸿韩浠借瘉鍙�', trigger: 'blur' }],
+        businessImg: [{ required: true, message: '璇疯緭鍏ヨ惀涓氭墽鐓�', trigger: 'blur' }],
+        idcardImg: [{ required: true, message: '璇疯緭鍏ヨ韩浠借瘉姝i潰', trigger: 'blur' }],
+        idcardImgBack: [{ required: true, message: '璇疯緭鍏ヨ韩浠借瘉鍙嶉潰', trigger: 'blur' }],
+        laborContractImgs: [{ required: true, message: '璇疯緭鍏ユ湁鏁堝姵鍔ㄥ悎鍚�', trigger: 'blur' }],
+        socialSecurityImgs: [{ required: true, message: '璇疯緭鍏ョぞ淇濈即绾宠瘉鏄�', trigger: 'blur' }],
+        storeFrontImgs: [{ required: true, message: '璇疯緭鍏ラ棬搴楅棬澶寸収', trigger: 'blur' }],
+        storeInteriorImgs: [{ required: true, message: '璇疯緭鍏ラ棬搴楀唴閮ㄧ収鐗�', trigger: 'blur' }],
+        otherMaterialImgs: [{ required: true, message: '璇疯緭鍏ュ叾瀹冩潗鏂�', trigger: 'blur' }]
+      },
+      qualificationTab: 'enterprise',
+      areaOptions: [],
+      isWorking: {
+        save: false
+      }
+    }
+  },
+  created () {
+    this.config({
+      api: '/business/shopInfo',
+      'field.id': 'id'
+    })
+    this.loadAreaOptions()
+  },
+  methods: {
+    open (title, row) {
+      this.isWorking.save = false
+      detail(row.id)
+        .then(res => {
+          this.form = {
+            id: res.id,
+            telephone: res.telephone || '',
+            name: res.name || '',
+            companyType: res.companyType ?? 1,
+            linkName: res.linkName || '',
+            linkPhone: res.linkPhone || '',
+            idcard: res.idcard || '',
+            areaCode: res.provinceId ? [res.provinceId, res.cityId, res.areaId] : [],
+            provinceId: res.provinceId || '',
+            cityId: res.cityId || '',
+            areaId: res.areaId || '',
+            address: res.address || '',
+            longitude: res.longitude || '',
+            latitude: res.latitude || '',
+            legalPersonName: res.legalPersonName || '',
+            legalPersonPhone: res.legalPersonPhone || '',
+            legalPersonCard: res.legalPersonCard || '',
+            businessImg: res.companyType === 1 ? [{ fileurl: res.businessImg, url: res.imgPrefix + res.businessImg }] : [],
+            idcardImg: [{ fileurl: res.idcardImg, url: res.imgPrefix + res.idcardImg }],
+            idcardImgBack: [{ fileurl: res.idcardImgBack, url: res.imgPrefix + res.idcardImgBack }],
+            laborContractImgs: res.laborContractImgs.map(item => ({ fileurl: item, url: res.imgPrefix + item })),
+            socialSecurityImgs: res.socialSecurityImgs.map(item => ({ fileurl: item, url: res.imgPrefix + item })),
+            storeFrontImgs: res.storeFrontImgs.map(item => ({ fileurl: item, url: res.imgPrefix + item })),
+            storeInteriorImgs: res.storeInteriorImgs.map(item => ({ fileurl: item, url: res.imgPrefix + item })),
+            otherMaterialImgs: res.otherMaterialImgs ? res.otherMaterialImgs.map(item => ({ fileurl: item, url: res.imgPrefix + item })) : []
+          }
+          this.qualificationTab = res.companyType === 1 ? 'enterprise' : 'personal'
+          this.title = title
+          this.visible = true
+        })
+        .catch(e => {
+          this.$tip.apiFailed(e)
+        })
+    },
+    loadAreaOptions () {
+      listByParentId({ })
+        .then(data => {
+          this.areaOptions = this.formatAreaData(data)
+          console.log(this.areaOptions)
+        })
+        .catch(e => {
+          this.$tip.apiFailed(e)
+        })
+    },
+    formatAreaData (data) {
+      const map = {}
+      const result = []
+      data.forEach(item => {
+        map[item.id] = { value: item.id, label: item.name, children: [] }
+      })
+      data.forEach(item => {
+        if (item.type === 0) {
+          result.push(map[item.id])
+        } else if (item.type === 1 && item.parentId && map[item.parentId]) {
+          map[item.parentId].children.push(map[item.id])
+        } else if (item.type === 2 && item.parentId && map[item.parentId]) {
+          map[item.parentId].children.push(map[item.id])
+        }
+      })
+      const clearEmptyChildren = (nodes) => {
+        nodes.forEach(node => {
+          if (node.children.length === 0) {
+            node.children = null
+          } else {
+            clearEmptyChildren(node.children)
+          }
+        })
+      }
+      clearEmptyChildren(result)
+      return result
+    },
+    openMapSelector () {
+      window.open('https://lbs.qq.com/getPoint/', '_blank')
+    },
+    handleConfirm () {
+      this.$refs.form.validate(valid => {
+        if (!valid) return
+        this.isWorking.save = true
+        const imageFields = ['businessImg', 'idcardImg', 'idcardImgBack', 'laborContractImgs', 'socialSecurityImgs', 'storeFrontImgs', 'storeInteriorImgs', 'otherMaterialImgs']
+        const data = { ...this.form }
+        imageFields.forEach(field => {
+          if (data[field]) {
+            const list = Array.isArray(data[field]) ? data[field] : [data[field]]
+            data[field] = list.map(item => typeof item === 'object' ? item.fileurl : item).join(',')
+          }
+        })
+        data.provinceId = this.form.areaCode[0] || ''
+        data.cityId = this.form.areaCode[1] || ''
+        data.areaId = this.form.areaCode[2] || ''
+        delete data.areaCode
+        updateById(data)
+          .then(res => {
+            this.$tip.apiSuccess(res || '淇濆瓨鎴愬姛')
+            this.visible = false
+            this.$emit('success')
+          })
+          .catch(e => {
+            this.$tip.apiFailed(e)
+          })
+          .finally(() => {
+            this.isWorking.save = false
+          })
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.form-section {
+  margin-bottom: 20px;
+}
+.section-title {
+  font-size: 16px;
+  font-weight: bold;
+  color: #303133;
+  margin-bottom: 15px;
+  padding-left: 10px;
+  border-left: 4px solid #2E68EC;
+}
+.password-tip {
+  color: #909399;
+  font-size: 12px;
+  margin: -10px 0 15px 120px;
+}
+.longitude-latitude {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+.longitude-latitude .separator {
+  color: #606266;
+}
+.inline-form /deep/ .el-form-item {
+  display: block;
+  margin-right: 0;
+}
+</style>
diff --git a/admin/src/components/business/OperaShopInfoWindow.vue b/admin/src/components/business/OperaShopInfoWindow.vue
new file mode 100644
index 0000000..55322fc
--- /dev/null
+++ b/admin/src/components/business/OperaShopInfoWindow.vue
@@ -0,0 +1,473 @@
+<template>
+  <GlobalWindow
+    :title="title"
+    :visible.sync="visible"
+    width="80%"
+  >
+    <div class="store-header" v-if="storeInfo">
+      <div class="store-header-left">
+        <!-- <el-image :src="storeInfo.headImage || defaultAvatar" fit="cover" class="store-avatar">
+          <div slot="error" class="image-slot">
+            <i class="el-icon-picture-outline"></i>
+          </div>
+        </el-image> -->
+      </div>
+      <div class="store-header-right">
+        <div class="store-name">{{ storeInfo.name }}</div>
+        <div class="store-info-row">
+          <span class="info-item">
+            <span class="label">闂ㄥ簵绫诲瀷锛�</span>
+            <span class="value">{{ storeInfo.companyType === 1 ? '浼佷笟' : '涓汉' }}</span>
+          </span>
+          <span class="info-item">
+            <span class="label">鑱旂郴浜猴細</span>
+            <span class="value">{{ storeInfo.linkName }}</span>
+          </span>
+          <span class="info-item">
+            <span class="label">鑱旂郴鐢佃瘽锛�</span>
+            <span class="value">{{ storeInfo.linkPhone }}</span>
+          </span>
+          <span class="info-item">
+            <span class="label">韬唤璇佸彿锛�</span>
+            <span class="value">{{ storeInfo.idcard }}</span>
+          </span>
+        </div>
+      </div>
+    </div>
+
+    <el-tabs v-model="activeTab" class="store-tabs">
+      <el-tab-pane label="闂ㄥ簵涓氱哗" name="performance">
+        <el-form ref="searchForm" :model="searchForm" inline>
+          <el-form-item label="浜ゆ槗鍙�" prop="transactionId">
+            <el-input v-model="searchForm.transactionId" clearable placeholder="璇疯緭鍏ヤ氦鏄撳彿"></el-input>
+          </el-form-item>
+          <el-form-item label="鏀舵敮绫诲瀷" prop="type">
+            <el-select v-model="searchForm.type" clearable placeholder="璇烽�夋嫨绫诲瀷">
+              <el-option label="鍏ㄩ儴" :value="0"></el-option>
+              <el-option label="鏀跺叆" :value="1"></el-option>
+              <el-option label="鏀嚭" :value="2"></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="鏀舵敮鏃堕棿" prop="createTime">
+            <el-date-picker type="daterange" v-model="searchForm.createTime" clearable value-format="yyyy-MM-dd HH:mm:ss"
+              range-separator="-" start-placeholder="寮�濮嬫棩鏈�" end-placeholder="缁撴潫鏃ユ湡" />
+          </el-form-item>
+          <section>
+            <el-button type="primary" @click="search">鏌ヨ</el-button>
+            <el-button @click="reset">閲嶇疆</el-button>
+            <el-button :loading="isWorking.export" @click="exportExcel">瀵煎嚭</el-button>
+          </section>
+        </el-form>
+
+        <el-table :data="tableData.list" border stripe v-loading="isWorking.search" class="performance-table">
+          <el-table-column label="鏀跺叆/鏀嚭" min-width="100px">
+            <template slot-scope="{row}">
+              <span :class="row.type === 1 ? 'income' : 'expense'">{{ row.type === 1 ? '鏀跺叆' : '鏀嚭' }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="amount" label="閲戦锛堝厓锛�" min-width="100px">
+            <template slot-scope="{row}">
+              <span :class="row.type === 1 ? 'income' : 'expense'">{{ row.amount }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="createTime" label="鏀舵敮鏃堕棿" min-width="160px"></el-table-column>
+          <el-table-column prop="businessType" label="涓氬姟绫诲瀷" min-width="100px"></el-table-column>
+          <el-table-column prop="transactionId" label="浜ゆ槗鍙�" min-width="180px"></el-table-column>
+          <el-table-column label="鐘舵��" min-width="100px">
+            <template slot-scope="{row}">
+              <span :class="row.status === 1 ? 'status-success' : 'status-pending'">
+                {{ row.status === 1 ? '宸插埌璐�' : '鎻愮幇涓�' }}
+              </span>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <pagination
+          @size-change="handleSizeChange"
+          @current-change="handlePageChange"
+          :pagination="tableData.pagination"
+        ></pagination>
+      </el-tab-pane>
+
+      <el-tab-pane label="闂ㄥ簵璧勮川" name="qualification">
+        <div class="qualification-content" v-if="storeInfo">
+          <div class="qualification-section">
+            <h4 class="section-title">鍩烘湰淇℃伅</h4>
+            <div class="info-grid">
+              <div class="info-row">
+                <span class="label">鎵�鍦ㄧ渷甯傚尯锛�</span>
+                <span class="value">{{ storeInfo.provinceName || '' }} {{ storeInfo.cityName || '' }} {{ storeInfo.areaName || '' }}</span>
+              </div>
+              <div class="info-row">
+                <span class="label">闂ㄥ簵鍦板潃锛�</span>
+                <span class="value">{{ storeInfo.address }}</span>
+              </div>
+              <div class="info-row">
+                <span class="label">闂ㄥ簵鐘舵�侊細</span>
+                <span class="value">{{ storeInfo.auditStatus === 0 ? '寰呭鎵�' : storeInfo.auditStatus === 1 ? '瀹℃壒閫氳繃' : '瀹℃壒鏈�氳繃' }}</span>
+              </div>
+              <div class="info-row">
+                <span class="label">閰嶉�佽寖鍥达細</span>
+                <span class="value">{{ storeInfo.deliveryRange || '鏆傛棤' }}</span>
+              </div>
+            </div>
+          </div>
+
+          <div class="qualification-section">
+            <h4 class="section-title">涓讳綋璧勮川</h4>
+            <template v-if="storeInfo.companyType === 1">
+              <div class="info-grid">
+                <div class="info-row">
+                  <span class="label">娉曚汉濮撳悕锛�</span>
+                  <span class="value">{{ storeInfo.legalPersonName }}</span>
+                </div>
+                <div class="info-row">
+                  <span class="label">娉曚汉鎵嬫満鍙凤細</span>
+                  <span class="value">{{ storeInfo.legalPersonPhone }}</span>
+                </div>
+                <div class="info-row">
+                  <span class="label">娉曚汉韬唤璇佸彿鐮侊細</span>
+                  <span class="value">{{ storeInfo.legalPersonCard }}</span>
+                </div>
+              </div>
+              <div class="image-section">
+                <div class="image-item">
+                  <span class="label">娉曚汉韬唤璇佹闈細</span>
+                  <el-image :src="storeInfo.idcardImg" fit="cover" class="qualification-image" :preview-src-list="[storeInfo.idcardImg]">
+                    <div slot="error" class="image-slot"><i class="el-icon-picture-outline"></i></div>
+                  </el-image>
+                </div>
+                <div class="image-item">
+                  <span class="label">娉曚汉韬唤璇佸弽闈細</span>
+                  <el-image :src="storeInfo.idcardImgBack" fit="cover" class="qualification-image" :preview-src-list="[storeInfo.idcardImgBack]">
+                    <div slot="error" class="image-slot"><i class="el-icon-picture-outline"></i></div>
+                  </el-image>
+                </div>
+                <div class="image-item">
+                  <span class="label">钀ヤ笟鎵х収锛�</span>
+                  <el-image :src="storeInfo.businessImg" fit="cover" class="qualification-image" :preview-src-list="[storeInfo.businessImg]">
+                    <div slot="error" class="image-slot"><i class="el-icon-picture-outline"></i></div>
+                  </el-image>
+                </div>
+              </div>
+            </template>
+            <template v-else>
+              <div class="image-item-row">
+                <span class="label">韬唤璇佹闈細</span>
+                <el-image :src="storeInfo.idcardImg" fit="cover" class="qualification-image" :preview-src-list="[storeInfo.idcardImg]">
+                  <div slot="error" class="image-slot"><i class="el-icon-picture-outline"></i></div>
+                </el-image>
+              </div>
+              <div class="image-item-row">
+                <span class="label">韬唤璇佸弽闈細</span>
+                <el-image :src="storeInfo.idcardImgBack" fit="cover" class="qualification-image" :preview-src-list="[storeInfo.idcardImgBack]">
+                  <div slot="error" class="image-slot"><i class="el-icon-picture-outline"></i></div>
+                </el-image>
+              </div>
+              <div class="image-item-row">
+                <span class="label">鏈夋晥鍔冲姩鍚堝悓锛�</span>
+                <div class="image-list">
+                  <el-image v-for="(img, index) in storeInfo.laborContractImgs" :key="index" :src="img" fit="cover" class="qualification-image" :preview-src-list="storeInfo.laborContractImgs">
+                    <div slot="error" class="image-slot"><i class="el-icon-picture-outline"></i></div>
+                  </el-image>
+                </div>
+              </div>
+              <div class="image-item-row">
+                <span class="label">绀句繚缂寸撼璇佹槑锛�</span>
+                <div class="image-list">
+                  <el-image v-for="(img, index) in storeInfo.socialSecurityImgs" :key="index" :src="img" fit="cover" class="qualification-image" :preview-src-list="storeInfo.socialSecurityImgs">
+                    <div slot="error" class="image-slot"><i class="el-icon-picture-outline"></i></div>
+                  </el-image>
+                </div>
+              </div>
+            </template>
+          </div>
+
+          <div class="qualification-section" v-if="storeInfo.companyType === 1">
+            <h4 class="section-title">闂ㄥ簵鐓х墖鍙婂叾浠栨潗鏂�</h4>
+            <div class="image-item-row">
+              <span class="label">闂ㄥ簵闂ㄥご鐓э細</span>
+              <div class="image-list">
+                <el-image v-for="(img, index) in storeInfo.storeFrontImgs" :key="index" :src="img" fit="cover" class="qualification-image" :preview-src-list="storeInfo.storeFrontImgs">
+                  <div slot="error" class="image-slot"><i class="el-icon-picture-outline"></i></div>
+                </el-image>
+              </div>
+            </div>
+            <div class="image-item-row">
+              <span class="label">闂ㄥ簵鍐呴儴鐓х墖锛�</span>
+              <div class="image-list">
+                <el-image v-for="(img, index) in storeInfo.storeInteriorImgs" :key="index" :src="img" fit="cover" class="qualification-image" :preview-src-list="storeInfo.storeInteriorImgs">
+                  <div slot="error" class="image-slot"><i class="el-icon-picture-outline"></i></div>
+                </el-image>
+              </div>
+            </div>
+            <div class="image-item-row">
+              <span class="label">鍏跺畠鏉愭枡锛�</span>
+              <div class="image-list">
+                <el-image v-for="(img, index) in storeInfo.otherMaterialImgs" :key="index" :src="img" fit="cover" class="qualification-image" :preview-src-list="storeInfo.otherMaterialImgs">
+                  <div slot="error" class="image-slot"><i class="el-icon-picture-outline"></i></div>
+                </el-image>
+              </div>
+            </div>
+          </div>
+        </div>
+      </el-tab-pane>
+    </el-tabs>
+  </GlobalWindow>
+</template>
+
+<script>
+import BaseOpera from '@/components/base/BaseOpera'
+import GlobalWindow from '@/components/common/GlobalWindow'
+import Pagination from '@/components/common/Pagination'
+import { detail } from '@/api/business/shopInfo'
+export default {
+  name: 'OperaShopInfoWindow',
+  extends: BaseOpera,
+  components: { GlobalWindow, Pagination },
+  data () {
+    return {
+      activeTab: 'performance',
+      storeInfo: null,
+      searchForm: {
+        transactionId: '',
+        type: 0,
+        createTime: ''
+      },
+      tableData: {
+        list: [],
+        pagination: {
+          pageIndex: 1,
+          pageSize: 10,
+          total: 0
+        }
+      },
+      isWorking: {
+        search: false,
+        export: false
+      },
+      defaultAvatar: 'https://cube.elemecdn.com/3/c7/9d47156420e4e9c6e2c1f6d6e6e6e6e6.jpeg'
+    }
+  },
+  created () {
+    this.config({
+      api: '/business/shopInfo',
+      'field.id': 'id'
+    })
+  },
+  methods: {
+    open (title, row) {
+      detail(row.id)
+        .then(res => {
+          this.storeInfo = res
+          this.activeTab = 'performance'
+          this.searchForm = {
+            transactionId: '',
+            type: 0,
+            createTime: ''
+          }
+          this.title = title
+          this.visible = true
+          this.search()
+        })
+        .catch(e => {
+          this.$tip.apiFailed(e)
+        })
+    },
+    search () {
+      this.isWorking.search = true
+      const data = {
+        pageIndex: this.tableData.pagination.pageIndex,
+        pageSize: this.tableData.pagination.pageSize,
+        shopId: this.storeInfo?.id,
+        transactionId: this.searchForm.transactionId,
+        type: this.searchForm.type,
+        startTime: this.searchForm.createTime?.[0] || '',
+        endTime: this.searchForm.createTime?.[1] || ''
+      }
+      this.api.fetchPerformance(data)
+        .then(res => {
+          this.tableData.list = res.list || []
+          this.tableData.pagination.total = res.total || 0
+        })
+        .catch(e => {
+          this.$tip.apiFailed(e)
+        })
+        .finally(() => {
+          this.isWorking.search = false
+        })
+    },
+    reset () {
+      this.searchForm = {
+        transactionId: '',
+        type: 0,
+        createTime: ''
+      }
+      this.search()
+    },
+    handleSizeChange (pageSize) {
+      this.tableData.pagination.pageSize = pageSize
+      this.search()
+    },
+    handlePageChange (pageIndex) {
+      this.tableData.pagination.pageIndex = pageIndex
+      this.search()
+    },
+    exportExcel () {
+      this.isWorking.export = true
+      const data = {
+        shopId: this.storeInfo?.id,
+        transactionId: this.searchForm.transactionId,
+        type: this.searchForm.type,
+        startTime: this.searchForm.createTime?.[0] || '',
+        endTime: this.searchForm.createTime?.[1] || ''
+      }
+      this.api.exportPerformance(data)
+        .then(res => {
+          this.$tip.apiSuccess('瀵煎嚭鎴愬姛')
+        })
+        .catch(e => {
+          this.$tip.apiFailed(e)
+        })
+        .finally(() => {
+          this.isWorking.export = false
+        })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.store-header {
+  display: flex;
+  background: #f5f7fa;
+  border-radius: 8px;
+  padding: 20px;
+  margin-bottom: 20px;
+}
+.store-header-left {
+  margin-right: 20px;
+}
+.store-avatar {
+  width: 80px;
+  height: 80px;
+  border-radius: 50%;
+}
+.store-header-right {
+  flex: 1;
+}
+.store-name {
+  font-size: 18px;
+  font-weight: bold;
+  color: #303133;
+  margin-bottom: 10px;
+}
+.store-info-row {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20px;
+}
+.info-item {
+  font-size: 14px;
+}
+.info-item .label {
+  color: #909399;
+}
+.info-item .value {
+  color: #606266;
+}
+.store-tabs {
+  margin-top: 10px;
+}
+.income {
+  color: #67c23a;
+}
+.expense {
+  color: #f56c6c;
+}
+.status-success {
+  color: #67c23a;
+}
+.status-pending {
+  color: #e6a23c;
+}
+.performance-table {
+  margin: 15px 0;
+}
+.qualification-content {
+  padding: 20px;
+}
+.qualification-section {
+  margin-bottom: 30px;
+}
+.section-title {
+  font-size: 16px;
+  font-weight: bold;
+  color: #303133;
+  margin-bottom: 15px;
+  padding-left: 10px;
+  border-left: 4px solid #2E68EC;
+}
+.info-grid {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 15px;
+  margin-bottom: 20px;
+}
+.info-row {
+  display: flex;
+  font-size: 14px;
+}
+.info-row .label {
+  color: #909399;
+  min-width: 100px;
+}
+.info-row .value {
+  color: #606266;
+}
+.image-section {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 30px;
+}
+.image-item {
+  display: flex;
+  flex-direction: column;
+}
+.image-item-row {
+  display: flex;
+  align-items: flex-start;
+  margin-bottom: 20px;
+}
+.image-item-row .label {
+  color: #909399;
+  font-size: 14px;
+  min-width: 120px;
+}
+.image-list {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 10px;
+}
+.image-item .label {
+  color: #909399;
+  font-size: 14px;
+  margin-bottom: 8px;
+}
+.qualification-image {
+  width: 150px;
+  height: 100px;
+  border-radius: 4px;
+  border: 1px solid #eee;
+}
+.image-slot {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 100%;
+  height: 100%;
+  background: #f5f7fa;
+  color: #909399;
+  font-size: 20px;
+}
+</style>
\ No newline at end of file
diff --git a/admin/src/components/common/uploadImages.vue b/admin/src/components/common/uploadImages.vue
new file mode 100644
index 0000000..c62504e
--- /dev/null
+++ b/admin/src/components/common/uploadImages.vue
@@ -0,0 +1,109 @@
+<template>
+  <div class="upload">
+    <div class="upload-item" v-for="(item, index) in fileList" :key="index + '_' + item.url">
+      <div class="dele" @click="handleDelete(index)">
+        <i class="el-icon-delete"></i>
+      </div>
+      <el-image
+        fit="widthFix"
+        :preview-src-list="fileList.map(res => res.url)"
+        :src="fileList[index].url"
+      />
+    </div>
+    <div class="upload-item" @click="handleClick" v-if="fileList.length < maxCount">
+      <i class="el-icon-plus"></i>
+    </div>
+    <input type="file" accept="image/*" ref="fileInput" style="opacity: 0; position: fixed; top: -100%; left: -100%;" @change="handleChangeFile">
+  </div>
+</template>
+
+<script>
+import { upload } from '@/api/system/common'
+export default {
+  props: {
+    fileList: {
+      type: Array,
+      default: () => []
+    },
+    uploadData: Object,
+    maxCount: {
+      type: Number,
+      default: 1
+    }
+  },
+  data() {
+    return {
+      uploadImgUrl: process.env.VUE_APP_API_PREFIX + '/web/public/upload'
+    }
+  },
+  methods: {
+    handleDelete (index) {
+      this.$emit('deleteRow', index)
+    },
+    handleClick () {
+      this.$refs.fileInput.click()
+    },
+    handleChangeFile (e) {
+      let file = e.target.files[0]
+      if (!file) {
+        return
+      }
+      let formData = new FormData()
+      formData.append('file', file)
+      formData.append('folder', this.uploadData.folder)
+      upload(formData)
+        .then(res => {
+          this.$emit('getFileList', {
+            fileurl: res.imgaddr,
+            url: res.url
+          })
+        })
+        .catch(e => {
+          this.$tip.apiFailed(e)
+        })
+      }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .upload {
+    display: flex;
+    align-items: center;
+    flex-wrap: wrap;
+    .upload-item {
+      width: 90px;
+      height: 90px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      flex-direction: column;
+      border: 1px solid #D9D9D9;
+      border-radius: 4px;
+      margin-right: 10px;
+      margin-bottom: 10px;
+      cursor: pointer;
+      position: relative;
+      .dele {
+        position: absolute;
+        top: 0;
+        right: 0;
+        z-index: 10;
+        width: 25px;
+        height: 25px;
+        line-height: 25px;
+        text-align: center;
+        background-color: red;
+        cursor: pointer;
+        .el-icon-delete {
+          font-size: 13px;
+          color: #fff;
+        }
+      }
+      .el-icon-plus {
+        font-size: 24px;
+        color: #909399;
+      }
+    }
+  }
+</style>
\ No newline at end of file
diff --git a/admin/src/views/business/storeList.vue b/admin/src/views/business/storeList.vue
index 10a9a3d..5d5dd4e 100644
--- a/admin/src/views/business/storeList.vue
+++ b/admin/src/views/business/storeList.vue
@@ -20,11 +20,11 @@
         <el-input v-model="searchForm.linkPhone" clearable placeholder="璇疯緭鍏ヨ仈绯荤數璇�" @keypress.enter.native="search"></el-input>
       </el-form-item>
       <el-form-item label="娉ㄥ唽鏃堕棿" prop="createTime">
-        <el-date-picker type="daterange" v-model="searchForm.createTime" clearable value-format="yyyy-MM-dd HH:mm:ss"
-                        range-separator="-" start-placeholder="寮�濮嬫棩鏈�" end-placeholder="缁撴潫鏃ユ湡" />
+        <el-date-picker type="daterange" v-model="searchForm.createTime" clearable value-format="yyyy-MM-dd"
+                        range-separator="-" start-placeholder="寮�濮嬫棩鏈�" end-placeholder="缁撴潫鏃ユ湡" @change="handleDateChange" />
       </el-form-item>
-      <el-form-item label="鐘舵��" prop="status">
-        <el-select v-model="searchForm.status" clearable placeholder="璇烽�夋嫨鐘舵��" @change="search">
+      <el-form-item label="鐘舵��" prop="auditStatus">
+        <el-select v-model="searchForm.auditStatus" clearable placeholder="璇烽�夋嫨鐘舵��" @change="search">
           <el-option label="寰呭鎵�" :value="0"></el-option>
           <el-option label="瀹℃壒閫氳繃" :value="1"></el-option>
           <el-option label="瀹℃壒鏈�氳繃" :value="2"></el-option>
@@ -44,21 +44,31 @@
         @selection-change="handleSelectionChange"
       >
         <el-table-column type="selection" width="55"></el-table-column>
-        <el-table-column prop="name" label="闂ㄥ簵鍚嶇О" min-width="120px"></el-table-column>
+        <el-table-column prop="name" label="闂ㄥ簵鍚嶇О" min-width="120px">
+          <template slot-scope="{row}">
+            <span class="link-name" @click="openShopInfo(row)">{{ row.name }}</span>
+          </template>
+        </el-table-column>
         <el-table-column prop="companyType" label="绫诲瀷" min-width="80px">
           <template slot-scope="{row}">
             {{row.companyType == 1 ? '浼佷笟' : '涓汉'}}
           </template>
         </el-table-column>
         <el-table-column prop="address" label="闂ㄥ簵鍦板潃" min-width="200px"></el-table-column>
-        <el-table-column prop="contact" label="鑱旂郴浜�" min-width="100px"></el-table-column>
-        <el-table-column prop="telephone" label="鑱旂郴鐢佃瘽" min-width="120px"></el-table-column>
+        <el-table-column prop="linkName" label="鑱旂郴浜�" min-width="100px"></el-table-column>
+        <el-table-column prop="linkPhone" label="鑱旂郴鐢佃瘽" min-width="120px"></el-table-column>
         <el-table-column prop="createTime" label="娉ㄥ唽鏃ユ湡" min-width="160px"></el-table-column>
         <el-table-column label="璐﹀彿鐘舵��" min-width="100px">
           <template slot-scope="{row}">
             <el-switch @change="changeStatus($event, row)" v-model="row.status" active-color="#13ce66"
             inactive-color="#ff4949" :active-value="0" :inactive-value="1">
             </el-switch>
+          </template>
+        </el-table-column>
+        <el-table-column label="鎿嶄綔" min-width="150" fixed="right">
+          <template slot-scope="{row}">
+            <el-button type="text" @click="handleEdit(row)">缂栬緫</el-button>
+            <el-button type="text" @click="handleResetPwd(row)">閲嶇疆瀵嗙爜</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -69,6 +79,10 @@
       >
       </pagination>
     </template>
+    <!-- 闂ㄥ簵璇︽儏 -->
+    <OperaShopInfoWindow ref="operaShopInfoWindow" />
+    <!-- 缂栬緫闂ㄥ簵 -->
+    <OperaShopEditWindow ref="operaShopEditWindow" @success="search" />
   </TableLayout>
 </template>
 
@@ -76,10 +90,13 @@
 import BaseTable from '@/components/base/BaseTable'
 import TableLayout from '@/layouts/TableLayout'
 import Pagination from '@/components/common/Pagination'
+import OperaShopInfoWindow from '@/components/business/OperaShopInfoWindow'
+import OperaShopEditWindow from '@/components/business/OperaShopEditWindow'
+import { changeStatus, resetPassword } from '@/api/business/shopInfo'
 export default {
   name: 'StoreList',
   extends: BaseTable,
-  components: { TableLayout, Pagination },
+  components: { TableLayout, Pagination, OperaShopInfoWindow, OperaShopEditWindow },
   data () {
     return {
       searchForm: {
@@ -89,7 +106,9 @@
         linkName: '',
         linkPhone: '',
         createTime: '',
-        status: ''
+        createStartTime: '',
+        createEndTime: '',
+        auditStatus: ''
       }
     }
   },
@@ -106,18 +125,22 @@
     reset () {
       this.searchForm = {
         name: '',
-        type: '',
+        companyType: '',
         address: '',
-        contact: '',
-        telephone: '',
+        linkName: '',
+        linkPhone: '',
         createTime: '',
         status: ''
       }
       this.search()
     },
+    handleDateChange (val) {
+      this.searchForm.createStartTime = val ? val[0] : ''
+      this.searchForm.createEndTime = val ? val[1] : ''
+    },
     changeStatus (e, row) {
       this.working = true
-      this.api.updateStatus({ id: row.id, status: e })
+      changeStatus({ id: row.id, status: e })
         .then(res => {
           this.$tip.apiSuccess(res || '鎿嶄綔鎴愬姛')
           this.search()
@@ -128,7 +151,33 @@
         .finally(() => {
           this.working = false
         })
+    },
+    openShopInfo (row) {
+      this.$refs.operaShopInfoWindow.open('闂ㄥ簵淇℃伅', row)
+    },
+    handleEdit (row) {
+      this.$refs.operaShopEditWindow.open('缂栬緫闂ㄥ簵', row)
+    },
+    handleResetPwd (row) {
+      this.$confirm('鏄惁纭閲嶇疆闂ㄥ簵瀵嗙爜锛�', '鎻愮ず')
+        .then(() => {
+          resetPassword({ id: row.id })
+            .then(res => {
+              this.$tip.apiSuccess(res || '閲嶇疆瀵嗙爜鎴愬姛')
+            })
+            .catch(e => {
+              this.$tip.apiFailed(e)
+            })
+        })
+        .catch(() => {})
     }
   }
 }
-</script>
\ No newline at end of file
+</script>
+<style scoped>
+.link-name {
+  color: #2E68EC;
+  text-decoration: underline;
+  cursor: pointer;
+}
+</style>
\ No newline at end of file

--
Gitblit v1.9.3