MrShi
6 天以前 caad7caa1e3409568ffaf8312064731c9b6147b0
门店累表
已添加3个文件
已修改3个文件
1052 ■■■■■ 文件已修改
admin/src/api/business/shopInfo.js 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/api/system/common.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/components/business/OperaShopEditWindow.vue 369 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/components/business/OperaShopInfoWindow.vue 473 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/components/common/uploadImages.vue 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/views/business/storeList.vue 77 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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
    }
  })
}
}
// ä¿®æ”¹é—¨åº—状态
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)
}
admin/src/api/system/common.js
@@ -34,3 +34,8 @@
    autoLogin: false
  })
}
// ä¸Šä¼ æ–‡ä»¶
export function upload (data) {
  return request.post('/web/public/upload', data)
}
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: '请输入身份证正面', 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>
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>
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>
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>
</script>
<style scoped>
.link-name {
  color: #2E68EC;
  text-decoration: underline;
  cursor: pointer;
}
</style>