Quellcode durchsuchen

feat(store): 优化门店管理界面并集成Leaflet地图

- 移除门店编号查询字段和导出按钮
- 重命名营业时间字段为兑换时间并调整时间格式
- 替换高德地图为Leaflet地图实现位置选择功能
- 添加门店主页图标、宣传图和详细信息字段
- 新增经纬度存储和联系方式验证规则
- 重构门店表单布局并添加地理位置选择功能
- 集成OpenStreetMap搜索和定位服务
fugui001 vor 2 Tagen
Ursprung
Commit
df5369e91b
3 geänderte Dateien mit 384 neuen und 340 gelöschten Zeilen
  1. 3 1
      package.json
  2. 18 0
      src/api/system/physical/store/types.ts
  3. 363 339
      src/views/system/physical/store/index.vue

+ 3 - 1
package.json

@@ -28,7 +28,7 @@
     "@vueuse/core": "13.1.0",
     "animate.css": "4.1.1",
     "await-to-js": "3.0.0",
-    "axios": "1.8.4",
+    "axios": "^1.8.4",
     "crypto-js": "4.2.0",
     "dompurify": "^3.2.6",
     "echarts": "5.6.0",
@@ -38,6 +38,8 @@
     "image-conversion": "2.1.1",
     "js-cookie": "3.0.5",
     "jsencrypt": "3.3.2",
+    "leaflet": "^1.9.4",
+    "leaflet-geosearch": "^4.4.0",
     "nprogress": "0.2.0",
     "pinia": "3.0.2",
     "screenfull": "6.0.2",

+ 18 - 0
src/api/system/physical/store/types.ts

@@ -70,6 +70,11 @@ export interface StoreVO {
   deletedAt: string;
 
   customTags: string[]; // 初始化为空数组
+
+  mainTitle?: string;
+  storeDescription?: string;
+  storePromotionImage?: string;
+  storeAppIds?: string;
 }
 
 export interface StoreForm extends BaseEntity {
@@ -144,6 +149,15 @@ export interface StoreForm extends BaseEntity {
    */
   deletedAt?: string;
   customTags: string[]; // 初始化为空数组
+
+  latitude?: number;
+  longitude?: number;
+
+  mainTitle?: string;
+  storeDescription?: string;
+  storeAppIds?: string;
+  storePromotionImage?: string[];
+  storePromotionImageOsId?: string | string[];
 }
 
 export interface StoreQuery extends PageQuery {
@@ -216,4 +230,8 @@ export interface StoreQuery extends PageQuery {
    * 日期范围参数
    */
   params?: any;
+  mainTitle?: string;
+  storeDescription?: string;
+  storePromotionImage?: string;
+  storeAppIds?: string;
 }

+ 363 - 339
src/views/system/physical/store/index.vue

@@ -4,9 +4,6 @@
       <div v-show="showSearch" class="mb-[10px]">
         <el-card shadow="hover">
           <el-form ref="queryFormRef" :model="queryParams" :inline="true">
-            <el-form-item label="门店编号" prop="storeCode">
-              <el-input v-model="queryParams.storeCode" placeholder="请输入门店编号" clearable @keyup.enter="handleQuery" />
-            </el-form-item>
             <el-form-item label="门店名称" prop="name">
               <el-input v-model="queryParams.name" placeholder="请输入门店名称" clearable @keyup.enter="handleQuery" />
             </el-form-item>
@@ -38,48 +35,30 @@
               >删除</el-button
             >
           </el-col>
-          <el-col :span="1.5">
-            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['physical:store:export']">导出</el-button>
-          </el-col>
           <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
         </el-row>
       </template>
 
       <el-table v-loading="loading" border :data="storeList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
-        <el-table-column label="编号" align="center" prop="id" v-if="true" />
-        <el-table-column label="门店编号" align="center" prop="storeCode" />
-        <el-table-column label="门店名称" align="center" prop="name" />
-        <el-table-column label="门店类型" align="center" prop="storeTypeName" />
-        <el-table-column label="所属赛区" align="center" prop="zoneId" />
-        <el-table-column label="详细地址" align="center" prop="address" />
-        <el-table-column label="门店图标" align="center" prop="iconUrl" width="120">
+        <el-table-column label="门店 ID" align="center" prop="id" v-if="true" />
+        <el-table-column label="主页图标" align="center" prop="iconUrl" width="120">
           <template #default="scope">
             <img v-if="scope.row.iconUrl" :src="scope.row.iconUrl" alt="" style="width: 40px; height: 40px; object-fit: cover; border-radius: 4px" />
             <span v-else>无</span>
           </template>
         </el-table-column>
-
-        <el-table-column label="门店背景图" align="center" prop="backgroundUrl" width="120">
-          <template #default="scope">
-            <img
-              v-if="scope.row.backgroundUrl"
-              :src="scope.row.backgroundUrl"
-              alt=""
-              style="width: 40px; height: 40px; object-fit: cover; border-radius: 4px"
-            />
-            <span v-else>无</span>
-          </template>
-        </el-table-column>
-        <el-table-column label="营业开始时间" align="center" prop="businessStartTime" width="180">
+        <el-table-column label="门店名称" align="center" prop="name" />
+        <el-table-column label="门店类型" align="center" prop="storeTypeName" />
+        <el-table-column label="详细地址" align="center" prop="address" />
+        <el-table-column label="兑换开始时间" align="center" prop="businessStartTime" width="180">
           <template #default="scope">
-            <span>{{ parseTime(scope.row.businessStartTime, '{h}:{i}:{s}') }}</span>
+            <span>{{ parseTime(scope.row.businessStartTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
           </template>
         </el-table-column>
-
-        <el-table-column label="营业结束时间" align="center" prop="businessEndTime" width="180">
+        <el-table-column label="兑换结束时间" align="center" prop="businessEndTime" width="180">
           <template #default="scope">
-            <span>{{ parseTime(scope.row.businessEndTime, '{h}:{i}:{s}') }}</span>
+            <span>{{ parseTime(scope.row.businessEndTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
           </template>
         </el-table-column>
         <el-table-column label="状态" align="center" prop="status">
@@ -104,29 +83,26 @@
 
       <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
     </el-card>
+
     <!-- 添加或修改门店信息对话框 -->
     <el-dialog :title="dialog.title" v-model="dialog.visible" width="600px" append-to-body>
       <el-form ref="storeFormRef" :model="form" :rules="rules" label-width="120px">
-        <el-form-item label="门店编号" prop="storeCode">
-          <el-input v-model="form.storeCode" placeholder="请输入门店编号" />
-        </el-form-item>
         <el-form-item label="门店名称" prop="name">
-          <el-input v-model="form.name" placeholder="请输入门店名称,最多20字" />
-        </el-form-item>
-        <el-form-item label="门店类型" prop="storeTypeId">
-          <el-select v-model="form.storeTypeId" placeholder="请选择门店类型" style="width: 100%">
-            <el-option v-for="item in tagOptions" :key="item.id" :label="item.serviceName" :value="item.id" />
-          </el-select>
-        </el-form-item>
-        <el-form-item label="所属赛区" prop="zoneId">
-          <el-input v-model="form.zoneId" placeholder="请输入所属赛区" />
+          <el-input v-model="form.name" placeholder="请输入门店名称,最多 20 字" />
         </el-form-item>
         <el-form-item label="详细地址" prop="address">
           <el-input v-model="form.address" type="textarea" placeholder="请输入内容" />
-          <!-- 添加地图选择按钮 -->
-          <el-button type="primary" @click="openMapSelector">选择地图位置</el-button>
+          <el-button type="primary" @click="openMapSelector" style="margin-top: 8px">选择地图位置</el-button>
         </el-form-item>
-        <el-form-item label="门店图标" prop="iconUrl">
+        <el-form-item label="联系方式" prop="phone">
+          <el-input v-model="form.phone" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+        <el-form-item label="服务类型" prop="storeTypeId">
+          <el-select v-model="form.storeTypeId" placeholder="请选择服务类型" style="width: 100%">
+            <el-option v-for="item in tagOptions" :key="item.id" :label="item.serviceName" :value="item.id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="主页图标" prop="iconUrl">
           <div class="upload-container">
             <el-upload
               class="upload-icon"
@@ -142,7 +118,6 @@
                 <el-button type="primary">点击选择图标</el-button>
               </template>
 
-              <!-- 预览图区域 -->
               <template #default>
                 <div class="preview-area" @click="handlePreviewClick">
                   <img
@@ -163,93 +138,37 @@
             </el-upload>
           </div>
         </el-form-item>
-
-        <el-form-item label="门店背景" prop="backgroundUrl">
-          <div class="upload-container">
-            <el-upload
-              class="upload-icon"
-              action="#"
-              :on-change="handleIconChange2"
-              :on-remove="handleIconRemove2"
-              :file-list="fileList2"
-              :auto-upload="false"
-              :limit="1"
-              accept="image/*"
-            >
-              <template #trigger>
-                <el-button type="primary">点击选择背景</el-button>
-              </template>
-
-              <!-- 预览图区域 -->
-              <template #default>
-                <div class="preview-area" @click="handlePreviewClick2">
-                  <img
-                    v-if="iconPreviewUrl2 || form.backgroundUrl"
-                    :src="iconPreviewUrl2 || form.backgroundUrl"
-                    alt="预览图"
-                    style="max-width: 100px; max-height: 100px; margin-top: 10px; cursor: pointer"
-                  />
-                  <div v-else style="margin-top: 10px; color: #999">无</div>
-                </div>
-              </template>
-
-              <template #tip>
-                <div class="el-upload__tip">
-                  <span v-if="fileList2.length > 0">当前已选文件:{{ fileList2[0].name }}</span>
-                </div>
-              </template>
-            </el-upload>
-          </div>
+        <el-form-item label="主标题" prop="mainTitle">
+          <el-input v-model="form.mainTitle" type="textarea" placeholder="请输入内容" />
         </el-form-item>
-
-        <el-form-item label="门店标签" prop="customTags">
-          <div class="tag-container" style="display: flex; align-items: center; gap: 8px">
-            <!-- 已选标签 -->
-            <el-tag
-              v-for="(tag, index) in customTags"
-              :key="index"
-              closable
-              @close="handleTagClose(index)"
-              style="margin-right: 8px; margin-bottom: 8px"
-            >
-              {{ tag }}
-            </el-tag>
-
-            <!-- 添加新标签按钮 -->
-            <el-button type="primary" size="small" plain @click="openAddTagDialog" style="margin-top: 4px"> + 添加标签 </el-button>
-          </div>
+        <el-form-item label="门店简介" prop="storeDescription">
+          <el-input v-model="form.storeDescription" type="textarea" placeholder="请输入内容" />
         </el-form-item>
-
-        <el-form-item label="营业时间" prop="businessStartTime">
-          <el-time-picker
+        <el-form-item label="门店appId">
+          <el-input v-model="form.storeAppIds" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+        <el-form-item label="门店宣传图" prop="storePromotionImage">
+          <imageUpload v-model="form.storePromotionImageOsId" :limit="5" />
+        </el-form-item>
+        <el-form-item label="开始兑换时间" prop="businessStartTime">
+          <el-date-picker
+            clearable
             v-model="form.businessStartTime"
-            format="HH:mm:ss"
-            value-format="HH:mm:ss"
-            placeholder="请选择营业开始时间"
-            :picker-options="{
-              start: '00:00',
-              end: '23:59',
-              step: '00:30'
-            }"
-          />
+            type="datetime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            placeholder="请选择开始兑换时间"
+          >
+          </el-date-picker>
         </el-form-item>
-
-        <el-form-item label="闭店时间" prop="businessEndTime">
-          <el-time-picker
+        <el-form-item label="结束兑换时间" prop="businessEndTime">
+          <el-date-picker
+            clearable
             v-model="form.businessEndTime"
-            format="HH:mm:ss"
-            value-format="HH:mm:ss"
-            placeholder="请选择营业结束时间"
-            :picker-options="{
-              start: '00:00',
-              end: '23:59',
-              step: '00:30'
-            }"
-          />
-        </el-form-item>
-
-        <el-form-item label="联系方式" prop="backgroundUrl">
-          <el-input v-model="form.phone" type="textarea" placeholder="请输入内容" />
+            type="datetime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            placeholder="请选择结束兑换时间"
+          >
+          </el-date-picker>
         </el-form-item>
         <el-form-item label="状态" prop="status">
           <el-radio-group v-model="form.status">
@@ -269,27 +188,26 @@
     <!-- 地图选择对话框 -->
     <el-dialog title="选择地理位置" v-model="mapDialogVisible" width="800px" append-to-body>
       <div style="height: 500px">
-        <!-- 搜索栏 -->
-        <div style="margin-bottom: 10px; display: flex">
-          <el-input v-model="searchKeyword" placeholder="请输入地址关键字搜索" style="flex: 1" @keyup.enter="searchAddress">
+        <div style="margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center">
+          <div style="display: flex; gap: 8px">
+            <el-button type="primary" icon="Location" @click="getCurrentLocation">定位当前位置</el-button>
+          </div>
+          <!--          <el-input v-model="searchKeyword" placeholder="请输入地址关键字搜索" style="flex: 1; max-width: 400px" @keyup.enter="searchAddress">
             <template #append>
               <el-button @click="searchAddress">搜索</el-button>
             </template>
           </el-input>
-          <el-button @click="clearSearch" style="margin-left: 10px">清空</el-button>
+          <el-button @click="clearSearch" style="margin-left: 10px">清空</el-button>-->
         </div>
 
-        <!-- 地图容器 -->
-        <div id="amap-container" style="width: 100%; height: 400px"></div>
+        <div id="leaflet-map-container" style="width: 100%; height: 400px; background: #f0f0f0"></div>
 
-        <!-- 选中地址显示 -->
         <div style="margin-top: 10px">
           <el-input v-model="selectedAddress" placeholder="点击地图选择位置或搜索地址" readonly>
             <template #prepend>选中地址:</template>
           </el-input>
         </div>
       </div>
-
       <template #footer>
         <div class="dialog-footer">
           <el-button @click="cancelMapSelection">取 消</el-button>
@@ -297,23 +215,11 @@
         </div>
       </template>
     </el-dialog>
+
     <!-- 图片预览弹窗 -->
     <el-dialog v-model="dialogVisible" title="图片预览" width="50%">
       <img :src="previewSrc || iconPreviewUrl || form.iconUrl" alt="预览图片" style="max-width: 100%; max-height: 80vh" />
     </el-dialog>
-
-    <el-dialog v-model="dialogVisible2" title="图片预览" width="50%">
-      <img :src="previewSrc2 || iconPreviewUrl2 || form.backgroundUrl" alt="预览图片" style="max-width: 100%; max-height: 80vh" />
-    </el-dialog>
-
-    <!-- 添加标签弹窗 -->
-    <el-dialog title="添加门店标签" v-model="addTagDialogVisible" width="400px">
-      <el-input v-model="newTagName" placeholder="请输入标签名称" clearable @keyup.enter="confirmAddTag" style="width: 100%; margin-bottom: 16px" />
-      <template #footer>
-        <el-button @click="addTagDialogVisible = false">取消</el-button>
-        <el-button type="primary" @click="confirmAddTag">确定</el-button>
-      </template>
-    </el-dialog>
   </div>
 </template>
 
@@ -322,10 +228,12 @@ import { listStore, getStore, delStore, addStore, updateStore } from '@/api/syst
 import { StoreVO, StoreQuery, StoreForm } from '@/api/system/physical/store/types';
 import { uploadTournament } from '@/api/system/business/tournaments';
 import { selectAllPhysicalTagsSelList } from '@/api/system/physical/tag';
-
-// 引入高德地图加载器
-import AMapLoader from '@amap/amap-jsapi-loader';
 import { TagVO } from '@/api/system/physical/tag/types';
+import { ElMessage } from 'element-plus';
+import L from 'leaflet';
+import 'leaflet/dist/leaflet.css';
+import { listByIds, delOss } from '@/api/system/oss';
+
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
 const storeList = ref<StoreVO[]>([]);
@@ -358,11 +266,15 @@ const initFormData: StoreForm = {
   businessEndTime: undefined,
   status: 1,
   phone: '',
-  customTags: [], // 初始化为空数组
+  customTags: [],
   createdAt: undefined,
   updatedAt: undefined,
-  deletedAt: undefined
+  deletedAt: undefined,
+  latitude: undefined,
+  longitude: undefined,
+  storePromotionImageOsId: []
 };
+
 const data = reactive<PageData<StoreForm, StoreQuery>>({
   form: { ...initFormData },
   queryParams: {
@@ -384,21 +296,48 @@ const data = reactive<PageData<StoreForm, StoreQuery>>({
     params: {}
   },
   rules: {
-    id: [{ required: true, message: '主键ID不能为空', trigger: 'blur' }],
-    storeCode: [{ required: true, message: '门店编号,如M0001不能为空', trigger: 'blur' }],
-    name: [{ required: true, message: '门店名称,最多20字不能为空', trigger: 'blur' }],
-    storeTypeId: [{ required: true, message: '门店类型ID,关联service_type表不能为空', trigger: 'blur' }],
-    zoneId: [{ required: true, message: '所属赛区分组ID,关联zone表不能为空', trigger: 'blur' }],
+    id: [{ required: true, message: '主键 ID 不能为空', trigger: 'blur' }],
+    name: [{ required: true, message: '门店名称,最多 20 字不能为空', trigger: 'blur' }],
+    storeTypeId: [{ required: true, message: '门店类型 ID 不能为空', trigger: 'blur' }],
     address: [{ required: true, message: '详细地址不能为空', trigger: 'blur' }],
-    businessStartTime: [{ required: true, message: '营业开始时间不能为空', trigger: 'blur' }],
-    businessEndTime: [{ required: true, message: '营业结束时间不能为空', trigger: 'blur' }],
-    status: [{ required: true, message: '状态:1=开业中,2=闭店中不能为空', trigger: 'change' }]
+    status: [{ required: true, message: '状态不能为空', trigger: 'change' }],
+    mainTitle: [{ required: true, message: '主标题不能为空', trigger: 'change' }],
+    storeDescription: [{ required: true, message: '门店简介不能为空', trigger: 'change' }],
+    phone: [
+      { required: true, message: '联系方式不能为空', trigger: 'blur' },
+      { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
+    ],
+    businessStartTime: [
+      { required: true, message: '营业开始时间不能为空', trigger: 'blur' },
+      {
+        validator: (rule, value, callback) => {
+          if (value && form.value.businessEndTime && value > form.value.businessEndTime) {
+            callback(new Error('开始时间不能大于结束时间'));
+          } else {
+            callback();
+          }
+        },
+        trigger: 'change'
+      }
+    ],
+    businessEndTime: [
+      { required: true, message: '营业结束时间不能为空', trigger: 'blur' },
+      {
+        validator: (rule, value, callback) => {
+          if (value && form.value.businessStartTime && value < form.value.businessStartTime) {
+            callback(new Error('结束时间不能小于开始时间'));
+          } else {
+            callback();
+          }
+        },
+        trigger: 'change'
+      }
+    ],
   }
 });
 
 const { queryParams, form, rules } = toRefs(data);
 
-/** 查询门店信息列表 */
 const getList = async () => {
   loading.value = true;
   const res = await listStore(queryParams.value);
@@ -407,91 +346,125 @@ const getList = async () => {
   loading.value = false;
 };
 
-/** 取消按钮 */
 const cancel = () => {
   reset();
   dialog.visible = false;
 };
 
-/** 表单重置 */
 const reset = () => {
   form.value = { ...initFormData };
   storeFormRef.value?.resetFields();
-
-  // 显式重置 customTags
   customTags.value = [];
-
-  // 清空上传相关状态
   fileList.value = [];
   fileList2.value = [];
   iconPreviewUrl.value = '';
   iconPreviewUrl2.value = '';
-  form.value.iconUrl = ''; // 清除表单中的URL
-  form.value.backgroundUrl = ''; // 清除表单中的URL
+  form.value.iconUrl = '';
+  form.value.backgroundUrl = '';
 };
-/** 搜索按钮操作 */
+
 const handleQuery = () => {
   queryParams.value.pageNum = 1;
   getList();
 };
 
-/** 重置按钮操作 */
 const resetQuery = () => {
   queryFormRef.value?.resetFields();
   handleQuery();
 };
 
-/** 多选框选中数据 */
 const handleSelectionChange = (selection: StoreVO[]) => {
   ids.value = selection.map((item) => item.id);
   single.value = selection.length != 1;
   multiple.value = !selection.length;
 };
 
-/** 新增按钮操作 */
 const handleAdd = () => {
   reset();
   dialog.visible = true;
   dialog.title = '添加门店信息';
-  // 清空上传相关状态
   fileList.value = [];
   fileList2.value = [];
   iconPreviewUrl.value = '';
   iconPreviewUrl2.value = '';
-  form.value.iconUrl = ''; // 清除表单中的URL
-  form.value.backgroundUrl = ''; // 清除表单中的URL
-  // 确保 customTags 被清空
+  form.value.iconUrl = '';
+  form.value.backgroundUrl = '';
   customTags.value = [];
 };
 
-/** 修改按钮操作 */
 const handleUpdate = async (row?: StoreVO) => {
   reset();
   const _id = row?.id || ids.value[0];
   const res = await getStore(_id);
   Object.assign(form.value, res.data);
 
-  // 保留已有的图片URL,但清除上传组件的状态
   if (form.value.iconUrl) {
-    iconPreviewUrl.value = form.value.iconUrl; // 设置预览图
+    iconPreviewUrl.value = form.value.iconUrl;
   }
   if (form.value.backgroundUrl) {
-    iconPreviewUrl2.value = form.value.backgroundUrl; // 设置预览图
+    iconPreviewUrl2.value = form.value.backgroundUrl;
   }
-  // 从后端获取的标签数据
   if (res.data.customTags && Array.isArray(res.data.customTags)) {
     customTags.value = [...res.data.customTags];
   } else {
     customTags.value = [];
   }
+  const processImages = async (images: any[]) => {
+    const imageUrls = await Promise.all(
+      images.map(async (image) => {
+        try {
+          const res = await getIds(image.osId);
+          const firstItem = Array.isArray(res) ? res[0] : res.data?.[0] || res[0];
+          return {
+            url: firstItem?.url || firstItem?.picUrl,
+            ossId: image.osId,
+            name: image.osId
+          };
+        } catch (error) {
+          console.error('获取图片URL失败:', error);
+          return { url: '', ossId: '' };
+        }
+      })
+    );
+    return imageUrls
+      .filter((item) => item.url !== '')
+      .map((item) => item.ossId)
+      .join(',');
+  };
+  if (res.data.storePromotionImage && Array.isArray(res.data.storePromotionImage)) {
+    form.value.storePromotionImageOsId = await processImages(res.data.storePromotionImage);
+  } else {
+    form.value.storePromotionImageOsId = [];
+  }
   dialog.visible = true;
   dialog.title = '修改门店信息';
 };
-/** 提交按钮 */
+const getIds = async (ids: string | number) => {
+  return await listByIds(ids);
+};
 const submitForm = () => {
   storeFormRef.value?.validate(async (valid: boolean) => {
     if (valid) {
       buttonLoading.value = true;
+
+      const processOsIds = async (osIdString: any, imageType: string) => {
+        if (!osIdString) return [];
+        const osIds = osIdString
+          .toString()
+          .split(',')
+          .map((id: string) => id.trim());
+        const imageList = [];
+        for (const osId of osIds) {
+          const res = await listByIds(osId);
+          const firstItem = Array.isArray(res) ? res[0] : res.data?.[0] || res[0];
+          const picUrl = firstItem?.url || firstItem?.picUrl;
+          imageList.push({ osId, imageUrl: picUrl, imageType });
+        }
+        return imageList;
+      };
+
+      form.value.storePromotionImage = await processOsIds(form.value.storePromotionImageOsId, 'STORE');
+
       if (form.value.id) {
         await updateStore(form.value).finally(() => (buttonLoading.value = false));
       } else {
@@ -503,32 +476,22 @@ const submitForm = () => {
     }
   });
 };
+// ... existing code ...
+
 
-/** 删除按钮操作 */
 const handleDelete = async (row?: StoreVO) => {
   const _ids = row?.id || ids.value;
-  await proxy?.$modal.confirm('是否确认删除门店信息编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
+  await proxy?.$modal.confirm('是否确认删除门店信息编号为"' + _ids + '"的数据项?');
   await delStore(_ids);
   proxy?.$modal.msgSuccess('删除成功');
   await getList();
 };
 
-/** 导出按钮操作 */
-const handleExport = () => {
-  proxy?.download(
-    'system/store/export',
-    {
-      ...queryParams.value
-    },
-    `store_${new Date().getTime()}.xlsx`
-  );
-};
-
 onMounted(() => {
   getList();
-  loadTagOptions(); // 加载门店类型选项
+  loadTagOptions();
 });
-// 响应式变量
+
 const iconPreviewUrl = ref('');
 const iconPreviewUrl2 = ref('');
 const fileList = ref([]);
@@ -538,7 +501,6 @@ const dialogVisible2 = ref(false);
 const previewSrc = ref('');
 const previewSrc2 = ref('');
 
-/** 处理图标上传 */
 const handleIconChange = async (file) => {
   const index = fileList.value.findIndex((f) => f.uid === file.uid);
   fileList.value = [];
@@ -548,7 +510,6 @@ const handleIconChange = async (file) => {
   }
 
   if (index === -1) {
-    // 如果文件不在列表中,则添加进去
     fileList.value.push(file);
   }
 
@@ -556,7 +517,6 @@ const handleIconChange = async (file) => {
     const rawFile = file.raw;
     const res = await uploadTournament(rawFile);
     if (res.code === 200) {
-      // 更新文件状态为成功
       fileList.value[index] = {
         ...file,
         status: 'success',
@@ -569,7 +529,6 @@ const handleIconChange = async (file) => {
       throw new Error(res.msg);
     }
   } catch (error) {
-    // 更新文件状态为失败
     fileList.value[index] = {
       ...file,
       status: 'fail',
@@ -579,15 +538,12 @@ const handleIconChange = async (file) => {
   }
 };
 
-/** 处理图标删除 */
 const handleIconRemove = (file, updatedFileList) => {
   fileList.value = updatedFileList;
-  // 清除预览图和临时链接
   iconPreviewUrl.value = '';
-  form.value.iconUrl = ''; // 清除表单中的URL
+  form.value.iconUrl = '';
 };
 
-/** 点击预览图触发放大 */
 const handlePreviewClick = () => {
   const currentSrc = iconPreviewUrl.value || form.value.iconUrl;
   if (currentSrc) {
@@ -595,7 +551,6 @@ const handlePreviewClick = () => {
   }
 };
 
-/** 处理背景图上传 */
 const handleIconChange2 = async (file) => {
   const index = fileList2.value.findIndex((f) => f.uid === file.uid);
   fileList2.value = [];
@@ -605,7 +560,6 @@ const handleIconChange2 = async (file) => {
   }
 
   if (index === -1) {
-    // 如果文件不在列表中,则添加进去
     fileList2.value.push(file);
   }
 
@@ -613,7 +567,6 @@ const handleIconChange2 = async (file) => {
     const rawFile = file.raw;
     const res = await uploadTournament(rawFile);
     if (res.code === 200) {
-      // 更新文件状态为成功
       fileList2.value[index] = {
         ...file,
         status: 'success',
@@ -626,7 +579,6 @@ const handleIconChange2 = async (file) => {
       throw new Error(res.msg);
     }
   } catch (error) {
-    // 更新文件状态为失败
     fileList2.value[index] = {
       ...file,
       status: 'fail',
@@ -636,24 +588,20 @@ const handleIconChange2 = async (file) => {
   }
 };
 
-/** 处理背景图删除 */
 const handleIconRemove2 = (file, updatedFileList) => {
   fileList2.value = updatedFileList;
-  // 清除预览图和临时链接
   iconPreviewUrl2.value = '';
-  form.value.backgroundUrl = ''; // 清除表单中的URL
+  form.value.backgroundUrl = '';
 };
 
-/** 点击预览图触发放大 */
 const handlePreviewClick2 = () => {
   const currentSrc = iconPreviewUrl2.value || form.value.backgroundUrl;
   if (currentSrc) {
     dialogVisible2.value = true;
   }
 };
-// 响应式变量
+
 const tagOptions = ref<TagVO[]>([]);
-// 加载门店类型选项
 const loadTagOptions = async () => {
   try {
     const res = await selectAllPhysicalTagsSelList();
@@ -667,22 +615,20 @@ const loadTagOptions = async () => {
     ElMessage.error('请求失败,请检查网络');
   }
 };
-// 自定义标签相关状态
+
 const customTags = ref<string[]>([]);
 const addTagDialogVisible = ref(false);
 const newTagName = ref('');
-// 添加标签
+
 const confirmAddTag = () => {
   if (!newTagName.value.trim()) {
     ElMessage.warning('标签名称不能为空');
     return;
   }
-  // 检查是否已达到最大数量限制
   if (customTags.value.length >= 3) {
-    ElMessage.warning('最多只能添加3个标签');
+    ElMessage.warning('最多只能添加 3 个标签');
     return;
   }
-  // 检查是否已存在
   if (customTags.value.includes(newTagName.value)) {
     ElMessage.warning('该标签已存在');
     return;
@@ -693,16 +639,13 @@ const confirmAddTag = () => {
   addTagDialogVisible.value = false;
 };
 
-// 删除标签
 const handleTagClose = (index: number) => {
   customTags.value.splice(index, 1);
 };
 
-// 表单提交时,将标签数组赋值给 form.customTags
 watch(
   customTags,
   () => {
-    // 确保赋值的是一个数组
     if (Array.isArray(customTags.value)) {
       form.value.customTags = customTags.value;
     }
@@ -713,153 +656,234 @@ const openAddTagDialog = () => {
   newTagName.value = '';
   addTagDialogVisible.value = true;
 };
-// 添加响应式变量
+
 const mapDialogVisible = ref(false);
 const selectedAddress = ref('');
 const selectedLocation = ref({ lng: 0, lat: 0 });
 const searchKeyword = ref('');
-let amapInstance: any = null;
-let marker: any = null;
+let mapInstance: L.Map | null = null;
+let markerInstance: L.Marker | null = null;
 
-/** 打开地图选择器 */
 const openMapSelector = () => {
   mapDialogVisible.value = true;
   nextTick(() => {
-    initAMap(); // 等待 el-dialog 渲染完成再初始化
+    setTimeout(() => {
+      initLeafletMap();
+    }, 100);
   });
 };
 
-/** 初始化高德地图 */
-const initAMap = () => {
-  AMapLoader.load({
-    key: 'ed2e29e4cbcc0f96905acb2106e82b42', // 请替换为您的高德地图key
-    version: '2.0',
-    plugins: ['AMap.Geocoder', 'AMap.PlaceSearch', 'AMap.AutoComplete', 'AMap.Marker', 'AMap.ToolBar', 'AMap.Scale', 'AMap.Geolocation']
-  })
-    .then((AMap) => {
-      amapInstance = AMap;
-
-      // 确保容器存在后再创建地图
-      const container = document.getElementById('amap-container');
-      if (!container) return;
-
-      const map = new AMap.Map(container, {
-        resizeEnable: true,
-        zoom: 12,
-        center: [116.397428, 39.90923] // 默认中心点
-      });
-
-      // 添加定位功能
-      map.addControl(
-        new AMap.Geolocation({
-          zoomToCurrent: true,
-          position: 'rtb'
-        })
-      );
-
-      // 添加点击事件
-      map.on('click', function (e) {
-        const lnglat = e.lnglat;
-        setSelectedLocation(lnglat, AMap);
-      });
-
-      // 添加地图控件
-      map.addControl(new AMap.ToolBar());
-      map.addControl(new AMap.Scale());
-
-      // 初始化标记
-      marker = new AMap.Marker({
-        map: map,
-        draggable: true,
-        cursor: 'move'
-      });
-
-      // 标记拖拽结束事件
-      marker.on('dragend', function (e) {
-        setSelectedLocation(e.lnglat, AMap);
-      });
-
-      // 将map实例保存到DOM元素上
-      (container as any).__map__ = map;
-    })
-    .catch((e) => {
-      console.error(e);
-      proxy?.$modal.msgError('地图加载失败: ' + e.message);
-    });
+const initLeafletMap = () => {
+  const container = document.getElementById('leaflet-map-container');
+  if (!container) return;
+
+  if (mapInstance) {
+    mapInstance.remove();
+    mapInstance = null;
+    markerInstance = null;
+  }
+
+  mapInstance = L.map('leaflet-map-container', {
+    center: [35.8617, 104.1954],
+    zoom: 5,
+    zoomControl: true,
+    preferCanvas: true,
+    fadeAnimation: false,
+    zoomAnimation: false
+  });
+
+  L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png', {
+    attribution:
+      '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
+    subdomains: ['a', 'b', 'c', 'd'],
+    maxZoom: 20
+  }).addTo(mapInstance);
+
+  mapInstance.on('click', function (e: L.LeafletMouseEvent) {
+    setSelectedLocation(e.latlng);
+  });
+
+  markerInstance = L.marker([35.8617, 104.1954], {
+    draggable: true,
+    riseOnHover: true
+  }).addTo(mapInstance);
+
+  markerInstance.on('dragend', function (e) {
+    const marker = e.target;
+    const position = marker.getLatLng();
+    setSelectedLocation(position);
+  });
+
+  setTimeout(() => {
+    if (mapInstance) {
+      mapInstance.invalidateSize();
+    }
+  }, 300);
 };
 
-/** 设置选中位置 */
-const setSelectedLocation = (lnglat: any, AMap: any) => {
+const setSelectedLocation = async (latlng: L.LatLng) => {
   selectedLocation.value = {
-    lng: lnglat.getLng(),
-    lat: lnglat.getLat()
+    lng: latlng.lng,
+    lat: latlng.lat
   };
+  if (markerInstance) {
+    markerInstance.setLatLng(latlng);
+  }
 
-  marker.setPosition(lnglat);
+  const coordsText = `纬度:${latlng.lat.toFixed(6)}, 经度:${latlng.lng.toFixed(6)}`;
 
-  // 逆地理编码获取地址
-  const geocoder = new AMap.Geocoder();
-  geocoder.getAddress(lnglat, function (status: string, result: any) {
-    debugger;
-    if (status === 'complete' && result.regeocode) {
-      selectedAddress.value = result.regeocode.formattedAddress;
+  try {
+    const response = await fetch(
+      `https://api.opencagedata.com/geocode/v1/json?q=${latlng.lat}+${latlng.lng}&key=82827937f9614a6eb17c772efa63d75d&language=zh&no_annotations=1`
+    );
+
+    if (response.ok) {
+      const data = await response.json();
+      if (data && data.results && data.results.length > 0) {
+        const address = data.results[0].formatted;
+        const cleanedAddress = address.replace(/^\d{5,}\s*/, '').replace(/,/g, '');
+        selectedAddress.value = cleanedAddress;
+      } else {
+        selectedAddress.value = coordsText;
+      }
+    } else {
+      selectedAddress.value = coordsText;
     }
-  });
+  } catch (error) {
+    console.error('地址解析失败:', error);
+    selectedAddress.value = coordsText;
+  }
 };
+const searchAddress = async () => {
+  if (!searchKeyword.value.trim()) {
+    proxy?.$modal.msgWarning('请输入搜索关键字');
+    return;
+  }
 
-/** 搜索地址 */
-const searchAddress = () => {
-  if (!searchKeyword.value || !amapInstance) return;
-
-  amapInstance.plugin('AMap.PlaceSearch', () => {
-    const placeSearch = new amapInstance.PlaceSearch({
-      pageSize: 5,
-      pageIndex: 1,
-      city: '全国', // 搜索范围
-      map: document.getElementById('amap-container')?.__map__ // 使用正确的map引用
-    });
-
-    placeSearch.search(searchKeyword.value, (status: string, result: any) => {
-      if (status === 'complete' && result.info === 'OK') {
-        if (result.poiList && result.poiList.pois.length > 0) {
-          const firstPoi = result.poiList.pois[0];
-          const lnglat = [firstPoi.location.lng, firstPoi.location.lat];
-
-          // 更新地图中心点
-          const mapElement = document.getElementById('amap-container');
-          if (mapElement) {
-            const map = mapElement.__map__;
-            if (map) {
-              map.setCenter(lnglat);
-              setSelectedLocation(new amapInstance.LngLat(firstPoi.location.lng, firstPoi.location.lat), amapInstance);
-            }
-          }
-        }
-      } else {
-        proxy?.$modal.msgWarning('未找到相关地址');
+  try {
+    const response = await fetch(
+      `https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(searchKeyword.value)}&countrycodes=cn&limit=5`
+    );
+
+    if (!response.ok) {
+      throw new Error('搜索失败');
+    }
+
+    const results = await response.json();
+
+    if (results && results.length > 0) {
+      const firstResult = results[0];
+      const latlng = L.latLng(parseFloat(firstResult.lat), parseFloat(firstResult.lon));
+
+      if (mapInstance) {
+        mapInstance.setView(latlng, 15);
+        setSelectedLocation(latlng);
+        selectedAddress.value = firstResult.display_name || searchKeyword.value;
+        proxy?.$modal.msgSuccess('找到地址');
       }
-    });
-  });
+    } else {
+      proxy?.$modal.msgWarning('未找到相关地址');
+    }
+  } catch (error) {
+    console.error('搜索失败:', error);
+    proxy?.$modal.msgError('搜索失败,请重试');
+  }
 };
-
-/** 确认选择地图位置 */
 const confirmMapSelection = () => {
   if (selectedAddress.value) {
     form.value.address = selectedAddress.value;
-    // 如果需要保存经纬度,可以在form中添加相应字段
-    // form.value.longitude = selectedLocation.value.lng;
-    // form.value.latitude = selectedLocation.value.lat;
+    form.value.latitude = selectedLocation.value.lat;
+    form.value.longitude = selectedLocation.value.lng;
   }
   mapDialogVisible.value = false;
 };
 
-/** 取消地图选择 */
 const cancelMapSelection = () => {
   mapDialogVisible.value = false;
 };
 
-/** 清除搜索关键词 */
 const clearSearch = () => {
   searchKeyword.value = '';
 };
+// ... existing code ...
+// ... existing code ...
+const getCurrentLocation = () => {
+  if (!navigator.geolocation) {
+    proxy?.$modal.msgError('您的浏览器不支持地理位置功能');
+    return;
+  }
+
+  proxy?.$modal.loading('正在获取您的位置...');
+
+  navigator.geolocation.getCurrentPosition(
+    async (position) => {
+      const accuracy = position.coords.accuracy;
+      console.log('定位精度:', accuracy, '米');
+
+      if (accuracy > 100) {
+        proxy?.$modal.msgWarning('定位精度较低 (' + Math.round(accuracy) + '米),建议在窗边或室外开阔地重试');
+      }
+
+      const latlng = L.latLng(position.coords.latitude, position.coords.longitude);
+      console.log('定位坐标:', latlng);
+
+      if (mapInstance) {
+        const zoom = accuracy < 50 ? 17 : accuracy < 100 ? 16 : 15;
+        mapInstance.setView(latlng, zoom);
+        await setSelectedLocation(latlng);
+      }
+
+      proxy?.$modal.closeLoading();
+      if (accuracy <= 50) {
+        proxy?.$modal.msgSuccess('位置获取成功 (精度:' + Math.round(accuracy) + '米)');
+      }
+    },
+    (error) => {
+      proxy?.$modal.closeLoading();
+      switch (error.code) {
+        case error.PERMISSION_DENIED:
+          proxy?.$modal.msgError('用户拒绝了地理位置请求');
+          break;
+        case error.POSITION_UNAVAILABLE:
+          proxy?.$modal.msgError('位置信息不可用');
+          break;
+        case error.TIMEOUT:
+          proxy?.$modal.msgError('获取位置超时');
+          break;
+        default:
+          proxy?.$modal.msgError('获取位置失败');
+      }
+    },
+    {
+      enableHighAccuracy: true,
+      timeout: 20000,
+      maximumAge: 0
+    }
+  );
+};
+// ... existing code ...
+
+// ... existing code ...
 </script>
+
+<style scoped>
+.upload-container {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+}
+
+.upload-icon {
+  margin-bottom: 10px;
+}
+
+.preview-area {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.tag-container {
+  flex-wrap: wrap;
+}
+</style>