Ver código fonte

- 区域站点、服务列表基本完成
- 完善平台划分
- 门店新增部分大部分完成

Huanyi 1 mês atrás
pai
commit
9b19c4a2de

+ 8 - 0
.vite/deps/_metadata.json

@@ -0,0 +1,8 @@
+{
+  "hash": "ec735b4d",
+  "configHash": "0fcda39c",
+  "lockfileHash": "cec954eb",
+  "browserHash": "a62000ce",
+  "optimized": {},
+  "chunks": {}
+}

+ 3 - 0
.vite/deps/package.json

@@ -0,0 +1,3 @@
+{
+  "type": "module"
+}

+ 20 - 4
package-lock.json

@@ -1,12 +1,12 @@
 {
-  "name": "ruoyi-vue-plus",
-  "version": "5.5.3-2.5.3",
+  "name": "pet-system-admin",
+  "version": "0.0.1",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
-      "name": "ruoyi-vue-plus",
-      "version": "5.5.3-2.5.3",
+      "name": "pet-system-admin",
+      "version": "0.0.1",
       "license": "MIT",
       "dependencies": {
         "@element-plus/icons-vue": "2.3.2",
@@ -18,6 +18,7 @@
         "axios": "1.13.1",
         "crypto-js": "4.2.0",
         "echarts": "5.6.0",
+        "element-china-area-data": "^6.1.0",
         "element-plus": "2.11.7",
         "file-saver": "2.0.5",
         "highlight.js": "11.11.1",
@@ -4146,6 +4147,12 @@
         "node": ">= 16"
       }
     },
+    "node_modules/china-division": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/china-division/-/china-division-2.7.0.tgz",
+      "integrity": "sha512-4uUPAT+1WfqDh5jytq7omdCmHNk3j+k76zEG/2IqaGcYB90c2SwcixttcypdsZ3T/9tN1TTpBDoeZn+Yw/qBEA==",
+      "license": "MIT"
+    },
     "node_modules/chokidar": {
       "version": "4.0.3",
       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
@@ -4605,6 +4612,15 @@
       "dev": true,
       "license": "ISC"
     },
+    "node_modules/element-china-area-data": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/element-china-area-data/-/element-china-area-data-6.1.0.tgz",
+      "integrity": "sha512-IkpcjwQv2A/2AxFiSoaISZ+oMw1rZCPUSOg5sOCwT5jKc96TaawmKZeY81xfxXsO0QbKxU5LLc6AirhG52hUmg==",
+      "license": "MIT",
+      "dependencies": {
+        "china-division": "^2.7.0"
+      }
+    },
     "node_modules/element-plus": {
       "version": "2.11.7",
       "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.11.7.tgz",

+ 1 - 0
package.json

@@ -26,6 +26,7 @@
     "axios": "1.13.1",
     "crypto-js": "4.2.0",
     "echarts": "5.6.0",
+    "element-china-area-data": "^6.1.0",
     "element-plus": "2.11.7",
     "file-saver": "2.0.5",
     "highlight.js": "11.11.1",

+ 74 - 0
src/api/service/list/index.ts

@@ -0,0 +1,74 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { ServiceVO, ServiceForm, ServiceQuery, ServiceOnStoreVo } from '@/api/service/list/types';
+
+/**
+ * 查询服务项目列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listService = (query?: ServiceQuery): AxiosPromise<ServiceVO[]> => {
+  return request({
+    url: '/service/list/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询服务项目详细
+ * @param id
+ */
+export const getService = (id: string | number): AxiosPromise<ServiceVO> => {
+  return request({
+    url: '/service/list/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增服务项目
+ * @param data
+ */
+export const addService = (data: ServiceForm) => {
+  return request({
+    url: '/service/list',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改服务项目
+ * @param data
+ */
+export const updateService = (data: ServiceForm) => {
+  return request({
+    url: '/service/list',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除服务项目
+ * @param id
+ */
+export const delService = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/service/list/' + id,
+    method: 'delete'
+  });
+};
+
+/**
+ * 获取服务项目列表
+ * @Author Huanyi
+ */
+export const listOnStore = (): AxiosPromise<ServiceOnStoreVo[]> => {
+  return request({
+    url: '/service/list/listOnStore',
+    method: 'GET'
+  });
+};

+ 90 - 0
src/api/service/list/types.ts

@@ -0,0 +1,90 @@
+export interface ServiceVO {
+  /**
+   * 序号
+   */
+  id: string | number;
+
+  /**
+   * 服务名称
+   */
+  name: string;
+
+  /**
+   * 服务图标
+   */
+  icon: number;
+
+  /**
+   * 服务图标Url
+   */
+  iconUrl: string;
+  /**
+   * 服务模式
+   */
+  mode: number;
+
+  /**
+   * 排序权重
+   */
+  sort: number;
+
+  /**
+   * 备注说明
+   */
+  remark: string;
+
+  /**
+   * 创建时间
+   */
+  createTime: string;
+
+}
+
+export interface ServiceForm extends BaseEntity {
+  /**
+   * 序号
+   */
+  id?: string | number;
+
+  /**
+   * 服务名称
+   */
+  name?: string;
+
+  /**
+   * 服务图标
+   */
+  icon?: number;
+
+  /**
+   * 服务模式
+   */
+  mode?: number;
+
+  /**
+   * 排序权重
+   */
+  sort?: number;
+
+  /**
+   * 备注说明
+   */
+  remark?: string;
+
+}
+
+export interface ServiceQuery extends PageQuery {
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}
+
+export interface ServiceOnStoreVo {
+  id?: number | string;
+  name?: string;
+}
+
+
+

+ 11 - 0
src/api/service/mode/index.ts

@@ -0,0 +1,11 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { SysServiceModeVo } from '@/api/service/mode/types';
+
+export const list = (): AxiosPromise<SysServiceModeVo[]> => {
+  return request({
+    url: '/service/mode/list',
+    method: 'GET'
+  });
+};
+

+ 4 - 0
src/api/service/mode/types.ts

@@ -0,0 +1,4 @@
+export interface SysServiceModeVo {
+  value: number;
+  label: string;
+}

+ 85 - 0
src/api/system/areaStation/index.ts

@@ -0,0 +1,85 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { AreaStationVO, AreaStationForm, AreaStationQuery, SysAreaStationTypeVo, SysAreaStationOnStoreVo } from '@/api/system/areaStation/types';
+
+/**
+ * 查询区域站点列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listAreaStation = (query?: AreaStationQuery): AxiosPromise<AreaStationVO[]> => {
+  return request({
+    url: '/system/areaStation/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询区域站点详细
+ * @param id
+ */
+export const getAreaStation = (id: string | number): AxiosPromise<AreaStationVO> => {
+  return request({
+    url: '/system/areaStation/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增区域站点
+ * @param data
+ */
+export const addAreaStation = (data: AreaStationForm) => {
+  return request({
+    url: '/system/areaStation',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改区域站点
+ * @param data
+ */
+export const updateAreaStation = (data: AreaStationForm) => {
+  return request({
+    url: '/system/areaStation',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除区域站点
+ * @param id
+ */
+export const delAreaStation = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/system/areaStation/' + id,
+    method: 'delete'
+  });
+};
+
+/**
+ * 获取区域类型
+ * @Author Huanyi
+ */
+export const listType = (): AxiosPromise<SysAreaStationTypeVo[]> => {
+  return request({
+    url: '/system/areaStation/listType',
+    method: 'GET'
+  });
+};
+
+/**
+ * 获取区域列表
+ * @Author Huanyi
+ */
+export const listOnStore = (): AxiosPromise<SysAreaStationOnStoreVo[]> => {
+  return request({
+    url: '/system/areaStation/listOnStore',
+    method: 'GET'
+  });
+};

+ 145 - 0
src/api/system/areaStation/types.ts

@@ -0,0 +1,145 @@
+export interface AreaStationVO {
+  /**
+   * 序号
+   */
+  id: string | number;
+
+  /**
+   * 区域名称
+   */
+  name: string;
+
+  /**
+   * 省市编码
+   */
+  code: string;
+
+  /**
+   * 排序权重
+   */
+  sort: number;
+
+  /**
+   * 详细地址
+   */
+  address: string;
+
+  /**
+   * 站长姓名
+   */
+  leaderName: string;
+
+  /**
+   * 联系电话
+   */
+  contactPhone: string;
+
+  /**
+   * 经度
+   */
+  longitude: number;
+
+  /**
+   * 纬度
+   */
+  latitude: number;
+
+  /**
+   * 类型
+   */
+  type: number;
+
+  /**
+   * 状态
+   */
+  status: number;
+
+    /**
+     * 子对象
+     */
+    children: AreaStationVO[];
+}
+
+export interface AreaStationForm extends BaseEntity {
+  /**
+   * 序号
+   */
+  id?: string | number;
+
+  /**
+   * 区域名称
+   */
+  name?: string;
+
+  /**
+   * 父级ID
+   */
+  parentId?: string | number;
+
+  /**
+   * 省市编码
+   */
+  code?: string;
+
+  /**
+   * 排序权重
+   */
+  sort?: number;
+
+  /**
+   * 详细地址
+   */
+  address?: string;
+
+  /**
+   * 站长姓名
+   */
+  leaderName?: string;
+
+  /**
+   * 联系电话
+   */
+  contactPhone?: string;
+
+  /**
+   * 经度
+   */
+  longitude?: number;
+
+  /**
+   * 纬度
+   */
+  latitude?: number;
+
+  /**
+   * 类型
+   */
+  type?: number;
+
+  /**
+   * 状态
+   */
+  status?: number;
+
+}
+
+export interface AreaStationQuery {
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}
+
+export interface SysAreaStationTypeVo {
+  value?: number;
+  label?: string;
+  style?: string;
+}
+
+export interface SysAreaStationOnStoreVo {
+  id?: number | string;
+  name?: string;
+  type?: number;
+  parentId?: number | string;
+}

+ 9 - 0
src/api/system/tenant/index.ts

@@ -107,3 +107,12 @@ export function syncTenantConfig() {
     method: 'get'
   });
 }
+
+// 查询门店品牌列表
+export function listOnStore(query?: any) {
+  return request({
+    url: '/system/tenant/listOnStore',
+    method: 'get',
+    params: query
+  });
+}

+ 13 - 1
src/api/system/tenantCategories/index.ts

@@ -1,6 +1,6 @@
 import request from '@/utils/request';
 import { AxiosPromise } from 'axios';
-import { TenantCategoriesVO, TenantCategoriesForm, TenantCategoriesQuery } from '@/api/system/tenantCategories/types';
+import { TenantCategoriesVO, TenantCategoriesForm, TenantCategoriesQuery, SysTenantCategoriesOnStoreVo } from '@/api/system/tenantCategories/types';
 
 /**
  * 查询商户分类列表
@@ -61,3 +61,15 @@ export const delTenantCategories = (id: string | number | Array<string | number>
     method: 'delete'
   });
 };
+
+/**
+ * 获取商户分类的列表
+ * @Author Huanyi
+ */
+export const listOnStore = (query: PageQuery): AxiosPromise<SysTenantCategoriesOnStoreVo[]> => {
+  return request({
+    url: '/system/tenantCategories/listOnStore',
+    method: 'GET',
+    params: query
+  });
+};

+ 4 - 1
src/api/system/tenantCategories/types.ts

@@ -106,5 +106,8 @@ export interface TenantCategoriesQuery extends PageQuery {
   params?: any;
 }
 
-
+export interface SysTenantCategoriesOnStoreVo {
+  id?: number | string;
+  name?: string;
+}
 

+ 190 - 0
src/components/PageSelect/index.vue

@@ -0,0 +1,190 @@
+<template>
+  <div class="page-select">
+    <el-select
+      v-bind="$attrs"
+      v-model="localValue"
+      :filterable="true"
+      :remote="false"
+      @visible-change="handleVisibleChange"
+      @input="handleInput"
+    >
+      <el-option
+        v-for="item in options"
+        :key="item.value"
+        :label="item.label"
+        :value="item.value"
+      />
+      <!-- 分页组件 -->
+      <div v-if="total > 0" class="brand-pagination">
+        <div class="custom-pagination">
+          <span
+            class="page-arrow"
+            :class="{ disabled: currentPage <= 1 }"
+            @click="currentPage > 1 && handlePageChange(currentPage - 1)"
+          >
+            &lt;
+          </span>
+          <template v-for="page in pageNumbers" :key="page">
+            <span
+              class="page-number"
+              :class="{ active: page === currentPage }"
+              @click="handlePageChange(page)"
+            >
+              {{ page }}
+            </span>
+          </template>
+          <span
+            class="page-arrow"
+            :class="{ disabled: currentPage >= totalPages }"
+            @click="currentPage < totalPages && handlePageChange(currentPage + 1)"
+          >
+            &gt;
+          </span>
+          <span class="total-text">共{{ total }}条</span>
+        </div>
+      </div>
+    </el-select>
+  </div>
+</template>
+
+<script setup name="PageSelect" lang="ts">
+import { ref, computed, watch } from 'vue';
+
+const props = defineProps({
+  modelValue: {
+    type: [String, Number],
+    default: undefined
+  },
+  options: {
+    type: Array,
+    default: () => []
+  },
+  total: {
+    type: Number,
+    default: 0
+  },
+  pageSize: {
+    type: Number,
+    default: 10
+  }
+});
+
+const emit = defineEmits(['update:modelValue', 'page-change', 'visible-change']);
+
+const localValue = ref(props.modelValue);
+const currentPage = ref(1);
+
+// 监听modelValue变化
+watch(
+  () => props.modelValue,
+  (newValue) => {
+    localValue.value = newValue;
+  }
+);
+
+// 计算属性:总页数
+const totalPages = computed(() => {
+  return Math.ceil(props.total / props.pageSize);
+});
+
+// 计算属性:页码数组
+const pageNumbers = computed(() => {
+  const pages = [];
+  const total = totalPages.value;
+  const current = currentPage.value;
+  
+  // 生成页码,最多显示5个页码
+  let start = Math.max(1, current - 2);
+  let end = Math.min(total, start + 4);
+  
+  // 调整起始页码,确保显示5个页码
+  if (end - start < 4) {
+    start = Math.max(1, end - 4);
+  }
+  
+  for (let i = start; i <= end; i++) {
+    pages.push(i);
+  }
+  
+  return pages;
+});
+
+// 处理输入变化
+const handleInput = (value: any) => {
+  emit('update:modelValue', value);
+};
+
+// 处理可见性变化
+const handleVisibleChange = (visible: boolean) => {
+  if (visible) {
+    currentPage.value = 1;
+  }
+  emit('visible-change', visible);
+};
+
+// 处理页面切换
+const handlePageChange = (page: number) => {
+  currentPage.value = page;
+  emit('page-change', page);
+};
+</script>
+
+<style scoped>
+.page-select {
+  width: 100%;
+}
+
+.brand-pagination {
+  margin-top: 10px;
+  padding-top: 10px;
+  border-top: 1px solid #ebeef5;
+  text-align: center;
+}
+
+.custom-pagination {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 10px;
+  font-size: 14px;
+}
+
+.page-arrow {
+  cursor: pointer;
+  color: #606266;
+  user-select: none;
+  padding: 2px 8px;
+  transition: color 0.3s;
+}
+
+.page-arrow:hover:not(.disabled) {
+  color: #409eff;
+}
+
+.page-arrow.disabled {
+  color: #c0c4cc;
+  cursor: not-allowed;
+}
+
+.page-number {
+  cursor: pointer;
+  color: #606266;
+  padding: 2px 8px;
+  transition: all 0.3s;
+}
+
+.page-number:hover {
+  color: #409eff;
+}
+
+.page-number.active {
+  color: #409eff;
+  font-weight: bold;
+}
+
+.total-text {
+  margin-left: 15px;
+  font-size: 12px;
+  color: #909399;
+}
+</style>

+ 5 - 0
src/types/components.d.ts

@@ -19,7 +19,9 @@ declare module 'vue' {
     ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
     ElButton: typeof import('element-plus/es')['ElButton']
     ElCard: typeof import('element-plus/es')['ElCard']
+    ElCascader: typeof import('element-plus/es')['ElCascader']
     ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
+    ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
     ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
@@ -58,6 +60,7 @@ declare module 'vue' {
     ElTabPane: typeof import('element-plus/es')['ElTabPane']
     ElTabs: typeof import('element-plus/es')['ElTabs']
     ElTag: typeof import('element-plus/es')['ElTag']
+    ElTextArea: typeof import('element-plus/es')['ElTextArea']
     ElTooltip: typeof import('element-plus/es')['ElTooltip']
     ElTree: typeof import('element-plus/es')['ElTree']
     ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
@@ -75,6 +78,8 @@ declare module 'vue' {
     ImageUpload: typeof import('./../components/ImageUpload/index.vue')['default']
     LangSelect: typeof import('./../components/LangSelect/index.vue')['default']
     MessageType: typeof import('./../components/Process/MessageType.vue')['default']
+    PageSelect: typeof import('./../components/PageSelect/index.vue')['default']
+    PageSelector: typeof import('./../components/PageSelector/index.vue')['default']
     Pagination: typeof import('./../components/Pagination/index.vue')['default']
     ParentView: typeof import('./../components/ParentView/index.vue')['default']
     ProcessMeddle: typeof import('./../components/Process/processMeddle.vue')['default']

+ 297 - 0
src/views/service/list/index.vue

@@ -0,0 +1,297 @@
+<template>
+  <div class="p-2">
+    <!--    <transition :enter-active-class="proxy?.animate.searchAnimate.enter"-->
+    <!--      :leave-active-class="proxy?.animate.searchAnimate.leave">-->
+    <!--      <div v-show="showSearch" class="mb-[10px]">-->
+    <!--        <el-card shadow="hover">-->
+    <!--          <el-form ref="queryFormRef" :model="queryParams" :inline="true">-->
+    <!--            <el-form-item>-->
+    <!--              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>-->
+    <!--              <el-button icon="Refresh" @click="resetQuery">重置</el-button>-->
+    <!--            </el-form-item>-->
+    <!--          </el-form>-->
+    <!--        </el-card>-->
+    <!--      </div>-->
+    <!--    </transition>-->
+
+    <el-card shadow="never">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['list:list:add']">新增</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"
+              v-hasPermi="['list:list:edit']">修改</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()"
+              v-hasPermi="['list:list:remove']">删除</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="warning" plain icon="Download" @click="handleExport"
+              v-hasPermi="['list:list: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="serviceList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="排序" align="center" prop="sort" width="100" />
+        <el-table-column label="服务名称" align="center" prop="name" width="200" />
+        <el-table-column label="服务图标" align="center" prop="iconUrl" width="100">
+          <template #default="scope">
+            <image-preview :src="scope.row.iconUrl" :width="50" :height="50" />
+          </template>
+        </el-table-column>
+        <el-table-column label="服务模式" align="center" prop="mode" width="200">
+          <template #default="scope">
+            {{ getModeLabel(scope.row.mode) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="备注说明" align="center" prop="remark" width="700" />
+        <el-table-column label="创建时间" align="center" prop="createTime" width="180">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{m}:{s}') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-tooltip content="修改" placement="top">
+              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
+                v-hasPermi="['list:list:edit']"></el-button>
+            </el-tooltip>
+            <el-tooltip content="删除" placement="top">
+              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
+                v-hasPermi="['list:list:remove']"></el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <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="500px" append-to-body>
+      <el-form ref="serviceFormRef" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="服务名称" prop="name">
+          <el-input v-model="form.name" placeholder="请输入服务名称" />
+        </el-form-item>
+        <el-form-item label="服务图标" prop="icon">
+          <image-upload v-model="form.icon" :limit="1" />
+        </el-form-item>
+        <el-form-item label="服务模式" prop="mode">
+          <el-radio-group v-model="form.mode">
+            <el-radio v-for="mode in modeList" :key="mode.id" :label="mode.value">
+              {{ mode.label }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="所属品牌" prop="tenantId">
+          <PageSelect v-model="form.tenantId" :options="brandOptions" :total="brandTotal" :pageSize="10"
+            placeholder="请选择所属品牌" @page-change="handleBrandPageChange"
+            @visible-change="handleBrandSelectVisibleChange" />
+        </el-form-item>
+        <el-form-item label="排序权重" prop="sort">
+          <el-input v-model="form.sort" placeholder="请输入排序权重" />
+        </el-form-item>
+        <el-form-item label="备注说明" prop="remark">
+          <el-input v-model="form.remark" placeholder="请输入备注说明" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="Service" lang="ts">
+import { listService, getService, delService, addService, updateService } from '@/api/service/list';
+import { ServiceVO, ServiceQuery, ServiceForm } from '@/api/service/list/types';
+import { list as listMode } from '@/api/service/mode';
+import { SysServiceModeVo } from '@/api/service/mode/types';
+import PageSelect from '@/components/PageSelect/index.vue';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const serviceList = ref<ServiceVO[]>([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const modeList = ref<SysServiceModeVo[]>([]); // 服务模式列表
+const brandOptions = ref<any[]>([]); // 品牌选项
+const brandTotal = ref(0); // 品牌总数
+
+const queryFormRef = ref<ElFormInstance>();
+const serviceFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: ServiceForm = {
+  id: undefined,
+  name: undefined,
+  icon: undefined,
+  mode: undefined,
+  sort: undefined,
+  remark: undefined,
+  tenantId: undefined
+};
+const data = reactive<PageData<ServiceForm, ServiceQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    params: {}
+  },
+  rules: {
+    id: [{ required: true, message: '序号不能为空', trigger: 'blur' }],
+    name: [{ required: true, message: '服务名称不能为空', trigger: 'blur' }],
+    icon: [{ required: true, message: '服务图标不能为空', trigger: 'blur' }],
+    mode: [{ required: true, message: '服务模式不能为空', trigger: 'change' }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询服务项目列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listService(queryParams.value);
+  serviceList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+
+/** 获取服务模式列表 */
+const getModeList = async () => {
+  try {
+    const res = await listMode();
+    modeList.value = res.data || res;
+  } catch (error) {
+    console.error('获取服务模式列表失败:', error);
+  }
+};
+
+/** 根据服务模式value获取label */
+const getModeLabel = (value: any): string => {
+  const mode = modeList.value.find((item) => item.value === value);
+  return mode ? mode.label : value;
+};
+
+/** 处理品牌分页 */
+const handleBrandPageChange = (page: number) => {
+  // 这里可以调用API获取对应页的数据
+  console.log('品牌分页切换到:', page);
+};
+
+/** 处理品牌选择框可见性变化 */
+const handleBrandSelectVisibleChange = (visible: boolean) => {
+  console.log('品牌选择框可见性变化:', visible);
+  if (visible) {
+    // 这里可以调用API获取初始数据
+  }
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  serviceFormRef.value?.resetFields();
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: ServiceVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = '添加服务项目';
+};
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: ServiceVO) => {
+  reset();
+  const _id = row?.id || ids.value[0];
+  const res = await getService(_id);
+  Object.assign(form.value, res.data);
+  dialog.visible = true;
+  dialog.title = '修改服务项目';
+};
+
+/** 提交按钮 */
+const submitForm = () => {
+  serviceFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.id) {
+        await updateService(form.value).finally(() => (buttonLoading.value = false));
+      } else {
+        await addService(form.value).finally(() => (buttonLoading.value = false));
+      }
+      proxy?.$modal.msgSuccess('操作成功');
+      dialog.visible = false;
+      await getList();
+    }
+  });
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: ServiceVO) => {
+  const _ids = row?.id || ids.value;
+  await proxy?.$modal.confirm('是否确认删除服务项目编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
+  await delService(_ids);
+  proxy?.$modal.msgSuccess('删除成功');
+  await getList();
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'service/service/export',
+    {
+      ...queryParams.value
+    },
+    `service_${new Date().getTime()}.xlsx`
+  );
+};
+
+onMounted(() => {
+  getList();
+  getModeList();
+});
+</script>

+ 315 - 0
src/views/system/areaStation/index.vue

@@ -0,0 +1,315 @@
+<template>
+  <div class="p-2">
+    <!--    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">-->
+    <!--      <div v-show="showSearch" class="mb-[10px]">-->
+    <!--        <el-card shadow="hover">-->
+    <!--          <el-form ref="queryFormRef" :model="queryParams" :inline="true">-->
+    <!--            <el-form-item>-->
+    <!--              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>-->
+    <!--              <el-button icon="Refresh" @click="resetQuery">重置</el-button>-->
+    <!--            </el-form-item>-->
+    <!--          </el-form>-->
+    <!--        </el-card>-->
+    <!--      </div>-->
+    <!--    </transition>-->
+
+    <el-card shadow="never">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Plus" @click="handleAdd()"
+              v-hasPermi="['system:areaStation:add']">新增</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
+          </el-col>
+          <!--          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>-->
+        </el-row>
+      </template>
+      <el-table ref="areaStationTableRef" v-loading="loading" :data="areaStationList" row-key="id" border
+        :default-expand-all="isExpandAll" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }">
+        <el-table-column label="区域名称" prop="name" width="300" />
+        <el-table-column label="类型" align="center" prop="type">
+          <template #default="scope">
+            <el-tag :type="getTypeStyle(scope.row.type)">
+              {{ getTypeLabel(scope.row.type) }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="省市编码" align="center" prop="code" width="200" />
+        <!--        <el-table-column label="排序权重" align="center" prop="sort" />-->
+        <el-table-column label="详细地址" align="center" prop="address" width="400" />
+        <el-table-column label="站长姓名" align="center" prop="leaderName" />
+        <el-table-column label="联系电话" align="center" prop="contactPhone" />
+        <!--        <el-table-column label="经度" align="center" prop="longitude" />-->
+        <!--        <el-table-column label="纬度" align="center" prop="latitude" />-->
+        <!--        <el-table-column label="状态" align="center" prop="status" width="100" />-->
+        <el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-tooltip content="修改" placement="top">
+              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
+                v-hasPermi="['system:areaStation:edit']" />
+            </el-tooltip>
+            <el-tooltip content="新增" placement="top">
+              <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)"
+                v-hasPermi="['system:areaStation:add']" />
+            </el-tooltip>
+            <el-tooltip content="删除" placement="top">
+              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
+                v-hasPermi="['system:areaStation:remove']" />
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+    <!-- 添加或修改区域站点对话框 -->
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
+      <el-form ref="areaStationFormRef" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="区域名称" prop="name">
+          <el-input v-model="form.name" placeholder="请输入区域名称" />
+        </el-form-item>
+        <el-form-item label="区域类型" prop="type">
+          <el-radio-group v-model="form.type">
+            <el-radio v-for="type in typeList" :key="type.value" :label="type.value">
+              <el-tag :type="type.style">
+                {{ type.label }}
+              </el-tag>
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <!--        <el-form-item v-if="form.type === 0 || form.type === 1 || form.type === 2" label="父级ID" prop="parentId">-->
+        <!--          <el-tree-select-->
+        <!--            v-model="form.parentId"-->
+        <!--            :data="areaStationOptions"-->
+        <!--            :props="{ value: 'id', label: 'name', children: 'children' }"-->
+        <!--            value-key="id"-->
+        <!--            placeholder="请选择父级ID"-->
+        <!--            check-strictly-->
+        <!--          />-->
+        <!--        </el-form-item>-->
+        <el-form-item label="省市编码" prop="code">
+          <el-input v-model="form.code" placeholder="请输入省市编码" />
+        </el-form-item>
+        <el-form-item v-if="form.type === 2" label="详细地址" prop="address">
+          <el-input v-model="form.address" placeholder="请输入详细地址" />
+        </el-form-item>
+        <el-form-item v-if="form.type === 2" label="联系人" prop="leaderName">
+          <el-input v-model="form.leaderName" placeholder="请输入联系人" />
+        </el-form-item>
+        <el-form-item v-if="form.type === 2" label="联系电话" prop="contactPhone">
+          <el-input v-model="form.contactPhone" placeholder="请输入联系电话" />
+        </el-form-item>
+        <el-form-item v-if="form.type === 0 || form.type === 1" label="排序权重" prop="sort">
+          <el-input v-model="form.sort" placeholder="请输入排序权重" />
+        </el-form-item>
+        <el-form-item v-if="form.type === 0 || form.type === 1" label="经度" prop="longitude">
+          <el-input v-model="form.longitude" placeholder="请输入经度" />
+        </el-form-item>
+        <el-form-item v-if="form.type === 0 || form.type === 1" label="纬度" prop="latitude">
+          <el-input v-model="form.latitude" placeholder="请输入纬度" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="AreaStation" lang="ts">
+import { listAreaStation, getAreaStation, delAreaStation, addAreaStation, updateAreaStation, listType } from '@/api/system/areaStation';
+import { AreaStationVO, AreaStationQuery, AreaStationForm, SysAreaStationTypeVo } from '@/api/system/areaStation/types';
+
+type AreaStationOption = {
+  id: number;
+  name: string;
+  children?: AreaStationOption[];
+};
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const areaStationList = ref<AreaStationVO[]>([]);
+const areaStationOptions = ref<AreaStationOption[]>([]);
+const typeList = ref<SysAreaStationTypeVo[]>([]); // 区域类型列表
+const buttonLoading = ref(false);
+const showSearch = ref(true);
+const isExpandAll = ref(true);
+const loading = ref(false);
+
+const queryFormRef = ref<ElFormInstance>();
+const areaStationFormRef = ref<ElFormInstance>();
+const areaStationTableRef = ref<ElTableInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: AreaStationForm = {
+  id: undefined,
+  name: undefined,
+  parentId: undefined,
+  code: undefined,
+  sort: undefined,
+  address: undefined,
+  leaderName: undefined,
+  contactPhone: undefined,
+  longitude: undefined,
+  latitude: undefined,
+  type: undefined,
+  status: undefined
+};
+
+const data = reactive<PageData<AreaStationForm, AreaStationQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    params: {}
+  },
+  rules: {
+    id: [{ required: true, message: '序号不能为空', trigger: 'blur' }],
+    name: [{ required: true, message: '区域名称不能为空', trigger: 'blur' }],
+    type: [{ required: true, message: '类型不能为空', trigger: 'change' }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询区域站点列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listAreaStation(queryParams.value);
+  const data = proxy?.handleTree<AreaStationVO>(res.data, 'id', 'parentId');
+  if (data) {
+    areaStationList.value = data;
+    loading.value = false;
+  }
+};
+
+/** 查询区域站点下拉树结构 */
+const getTreeselect = async () => {
+  const res = await listAreaStation();
+  areaStationOptions.value = [];
+  const data: AreaStationOption = { id: 0, name: '顶级节点', children: [] };
+  data.children = proxy?.handleTree<AreaStationOption>(res.data, 'id', 'parentId');
+  areaStationOptions.value.push(data);
+};
+
+/** 获取区域类型列表 */
+const getTypeList = async () => {
+  try {
+    const res = await listType();
+    typeList.value = res.data || res;
+  } catch (error) {
+    console.error('获取区域类型列表失败:', error);
+  }
+};
+
+/** 根据类型值获取标签文本 */
+const getTypeLabel = (value: any): string => {
+  const type = typeList.value.find((item) => item.value === value);
+  return type ? type.label : value;
+};
+
+/** 根据类型值获取标签样式 */
+const getTypeStyle = (value: any): string => {
+  const type = typeList.value.find((item) => item.value === value);
+  return type ? type.style : 'default';
+};
+
+// 取消按钮
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+// 表单重置
+const reset = () => {
+  form.value = { ...initFormData };
+  areaStationFormRef.value?.resetFields();
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+};
+
+/** 新增按钮操作 */
+const handleAdd = (row?: AreaStationVO) => {
+  reset();
+  getTreeselect();
+  if (row != null && row.id) {
+    form.value.parentId = row.id;
+  } else {
+    form.value.parentId = 0;
+  }
+  dialog.visible = true;
+  dialog.title = '添加区域站点';
+};
+
+/** 展开/折叠操作 */
+const handleToggleExpandAll = () => {
+  isExpandAll.value = !isExpandAll.value;
+  toggleExpandAll(areaStationList.value, isExpandAll.value);
+};
+
+/** 展开/折叠操作 */
+const toggleExpandAll = (data: AreaStationVO[], status: boolean) => {
+  data.forEach((item) => {
+    areaStationTableRef.value?.toggleRowExpansion(item, status);
+    if (item.children && item.children.length > 0) toggleExpandAll(item.children, status);
+  });
+};
+
+/** 修改按钮操作 */
+const handleUpdate = async (row: AreaStationVO) => {
+  reset();
+  await getTreeselect();
+  if (row != null) {
+    form.value.parentId = row.parentId;
+  }
+  const res = await getAreaStation(row.id);
+  Object.assign(form.value, res.data);
+  dialog.visible = true;
+  dialog.title = '修改区域站点';
+};
+
+/** 提交按钮 */
+const submitForm = () => {
+  areaStationFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.id) {
+        await updateAreaStation(form.value).finally(() => (buttonLoading.value = false));
+      } else {
+        await addAreaStation(form.value).finally(() => (buttonLoading.value = false));
+      }
+      proxy?.$modal.msgSuccess('操作成功');
+      dialog.visible = false;
+      getList();
+    }
+  });
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row: AreaStationVO) => {
+  await proxy?.$modal.confirm('是否确认删除区域站点编号为"' + row.id + '"的数据项?');
+  loading.value = true;
+  await delAreaStation(row.id).finally(() => (loading.value = false));
+  await getList();
+  proxy?.$modal.msgSuccess('删除成功');
+};
+
+onMounted(() => {
+  getList();
+  getTypeList();
+});
+</script>

+ 375 - 52
src/views/system/store/index.vue

@@ -1,6 +1,7 @@
 <template>
   <div class="p-2">
-    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter"
+      :leave-active-class="proxy?.animate.searchAnimate.leave">
       <div v-show="showSearch" class="mb-[10px]">
         <el-card shadow="hover">
           <el-form ref="queryFormRef" :model="queryParams" :inline="true">
@@ -11,12 +12,8 @@
               <el-input v-model="queryParams.contact" placeholder="请输入联系人" clearable @keyup.enter="handleQuery" />
             </el-form-item>
             <el-form-item label="有效期至" prop="validity">
-              <el-date-picker clearable
-                v-model="queryParams.validity"
-                type="date"
-                value-format="YYYY-MM-DD"
-                placeholder="请选择有效期至"
-              />
+              <el-date-picker clearable v-model="queryParams.validity" type="date" value-format="YYYY-MM-DD"
+                placeholder="请选择有效期至" />
             </el-form-item>
             <el-form-item>
               <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@@ -31,16 +28,20 @@
       <template #header>
         <el-row :gutter="10" class="mb8">
           <el-col :span="1.5">
-            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:store:add']">新增</el-button>
+            <el-button type="primary" plain icon="Plus" @click="handleAdd"
+              v-hasPermi="['system:store:add']">新增</el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:store:edit']">修改</el-button>
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"
+              v-hasPermi="['system:store:edit']">修改</el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:store:remove']">删除</el-button>
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()"
+              v-hasPermi="['system:store:remove']">删除</el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:store:export']">导出</el-button>
+            <el-button type="warning" plain icon="Download" @click="handleExport"
+              v-hasPermi="['system:store:export']">导出</el-button>
           </el-col>
           <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
         </el-row>
@@ -51,12 +52,12 @@
         <el-table-column label="序号" align="center" prop="id" v-if="true" />
         <el-table-column label="LOGO" align="center" prop="logoUrl" width="100">
           <template #default="scope">
-            <image-preview :src="scope.row.logoUrl" :width="50" :height="50"/>
+            <image-preview :src="scope.row.logoUrl" :width="50" :height="50" />
           </template>
         </el-table-column>
         <el-table-column label="营业执照" align="center" prop="businessLicenseUrl" width="100">
           <template #default="scope">
-            <image-preview :src="scope.row.businessLicenseUrl" :width="50" :height="50"/>
+            <image-preview :src="scope.row.businessLicenseUrl" :width="50" :height="50" />
           </template>
         </el-table-column>
         <el-table-column label="门店名称" align="center" prop="name" />
@@ -84,44 +85,67 @@
         <el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
           <template #default="scope">
             <el-tooltip content="修改" placement="top">
-              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:store:edit']"></el-button>
+              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
+                v-hasPermi="['system:store:edit']"></el-button>
             </el-tooltip>
             <el-tooltip content="删除" placement="top">
-              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:store:remove']"></el-button>
+              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
+                v-hasPermi="['system:store:remove']"></el-button>
             </el-tooltip>
           </template>
         </el-table-column>
       </el-table>
 
-      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+      <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="500px" append-to-body>
-      <el-form ref="storeFormRef" :model="form" :rules="rules" label-width="80px">
-        <el-form-item label="LOGO" prop="logo">
-          <image-upload v-model="form.logo"/>
+    <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="门店Logo" prop="logo">
+          <image-upload v-model="form.logo" :limit="1" />
         </el-form-item>
         <el-form-item label="营业执照" prop="businessLicense">
-          <image-upload v-model="form.businessLicense"/>
+          <image-upload v-model="form.businessLicense" :limit="1" />
         </el-form-item>
         <el-form-item label="门店名称" prop="name">
           <el-input v-model="form.name" placeholder="请输入门店名称" />
         </el-form-item>
-        <el-form-item label="开始营业时间" prop="startBusinessTime">
-          <el-date-picker clearable
-            v-model="form.startBusinessTime"
-            type="datetime"
-            value-format="YYYY-MM-DD HH:mm:ss"
-            placeholder="请选择开始营业时间">
-          </el-date-picker>
+        <el-form-item label="服务项目" prop="services">
+          <el-checkbox-group v-model="form.services">
+            <el-checkbox v-for="service in serviceList" :key="service.id" :label="service.id" border>
+              {{ service.name }}
+            </el-checkbox>
+          </el-checkbox-group>
         </el-form-item>
-        <el-form-item label="结束营业时间" prop="endBusinessTime">
-          <el-date-picker clearable
-            v-model="form.endBusinessTime"
-            type="datetime"
-            value-format="YYYY-MM-DD HH:mm:ss"
-            placeholder="请选择结束营业时间">
-          </el-date-picker>
+        <el-form-item label="商户分类" prop="tenantCatergories">
+          <PageSelect v-model="form.tenantCatergories"
+            :options="tenantCategoriesList.map(item => ({ value: item.id, label: item.name }))"
+            :total="tenantCategoriesTotal" :pageSize="10" placeholder="请选择商户分类"
+            @page-change="handleTenantCategoriesPageChange" @visible-change="handleTenantCategoriesVisibleChange" />
+        </el-form-item>
+        <el-form-item label="所属品牌" prop="tenantId">
+          <PageSelect v-model="form.tenantId"
+            :options="brandList.map(item => ({ value: item.tenantId, label: item.name }))" :total="brandTotal"
+            :pageSize="10" placeholder="请选择所属品牌" @page-change="handleBrandPageChange"
+            @visible-change="handleBrandSelectVisibleChange" />
+        </el-form-item>
+        <el-form-item label="营业时间" prop="startBusinessTime">
+          <el-row :gutter="10">
+            <el-col :span="10">
+              <el-date-picker clearable v-model="form.startBusinessTime" type="datetime"
+                value-format="YYYY-MM-DD HH:mm:ss" placeholder="开始时间" style="width: 100%">
+              </el-date-picker>
+            </el-col>
+            <el-col :span="4" style="text-align: center; line-height: 40px">
+              至
+            </el-col>
+            <el-col :span="10">
+              <el-date-picker clearable v-model="form.endBusinessTime" type="datetime"
+                value-format="YYYY-MM-DD HH:mm:ss" placeholder="结束时间" style="width: 100%">
+              </el-date-picker>
+            </el-col>
+          </el-row>
         </el-form-item>
         <el-form-item label="联系人" prop="contact">
           <el-input v-model="form.contact" placeholder="请输入联系人" />
@@ -130,22 +154,49 @@
           <el-input v-model="form.contactNumber" placeholder="请输入联系电话" />
         </el-form-item>
         <el-form-item label="有效期至" prop="validity">
-          <el-date-picker clearable
-            v-model="form.validity"
-            type="datetime"
-            value-format="YYYY-MM-DD HH:mm:ss"
-            placeholder="请选择有效期至">
+          <el-date-picker clearable v-model="form.validity" type="datetime" value-format="YYYY-MM-DD HH:mm:ss"
+            placeholder="请选择有效期至" style="width: 100%">
           </el-date-picker>
         </el-form-item>
-        <el-form-item label="详细地址" prop="detailAddress">
-          <el-input v-model="form.detailAddress" placeholder="请输入详细地址" />
-        </el-form-item>
-        <el-form-item label="经度" prop="longitude">
-          <el-input v-model="form.longitude" placeholder="请输入经度" />
+        <el-row :gutter="10">
+          <el-col :span="12">
+            <el-form-item label="所在区域" prop="regionId">
+              <el-cascader v-model="regionValue" :options="areaOptions" placeholder="选择区域" style="width: 100%"
+                @change="handleAreaChange" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="归属站点" prop="site">
+              <el-select v-model="form.site" placeholder="选择站点" :disabled="!form.regionId">
+                <el-option v-for="site in siteOptions" :key="site.value" :label="site.label" :value="site.value" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-form-item label="详细地址">
+          <el-row :gutter="10" style="margin-bottom: 10px">
+            <el-col :span="24">
+              <el-cascader v-model="addressCascaderValue" :options="regionData" placeholder="选择省市区"
+                style="width: 100%" />
+            </el-col>
+          </el-row>
+          <el-input v-model="form.detailAddress" type="textarea" placeholder="输入详细地址" rows="3" style="width: 100%" />
         </el-form-item>
-        <el-form-item label="维度" prop="latitude">
-          <el-input v-model="form.latitude" placeholder="请输入维度" />
+        <el-form-item>
+          <el-button type="primary" style="width: 100%" @click="getGeolocation">获取经纬度</el-button>
         </el-form-item>
+        <el-row :gutter="10">
+          <el-col :span="12">
+            <el-form-item label="经度" prop="longitude">
+              <el-input v-model="form.longitude" placeholder="请获取/输入位置经度" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="纬度" prop="latitude">
+              <el-input v-model="form.latitude" placeholder="请获取/输入位置纬度" />
+            </el-form-item>
+          </el-col>
+        </el-row>
       </el-form>
       <template #footer>
         <div class="dialog-footer">
@@ -160,6 +211,13 @@
 <script setup name="Store" lang="ts">
 import { listStore, getStore, delStore, addStore, updateStore } from '@/api/system/store';
 import { StoreVO, StoreQuery, StoreForm } from '@/api/system/store/types';
+import { listOnStore } from '@/api/system/tenant';
+import { listOnStore as listTenantCategoriesOnStore } from '@/api/system/tenantCategories';
+import { listOnStore as listServiceOnStore } from '@/api/service/list';
+import { listOnStore as listAreaStationOnStore } from '@/api/system/areaStation';
+import { SysAreaStationOnStoreVo } from '@/api/system/areaStation/types';
+import { regionData, CodeToText, TextToCode } from 'element-china-area-data';
+import PageSelect from '@/components/PageSelect/index.vue';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
@@ -174,6 +232,28 @@ const total = ref(0);
 
 const queryFormRef = ref<ElFormInstance>();
 const storeFormRef = ref<ElFormInstance>();
+const brandSelectRef = ref<any>(null);
+
+// 新增的响应式变量
+const regionValue = ref<any[]>([]);
+const province = ref('');
+const city = ref('');
+const district = ref('');
+const addressCascaderValue = ref<any[]>([]); // 省市区级联选择器值
+const brandList = ref<any[]>([]); // 品牌列表
+const brandLoading = ref(false); // 品牌加载状态
+const currentPage = ref(1); // 当前页码
+const brandKeyword = ref(''); // 搜索关键词
+const brandSelectVisible = ref(false); // 品牌选择框可见状态
+const brandTotal = ref(0); // 品牌总数
+const serviceList = ref<any[]>([]); // 服务项目列表
+const tenantCategoriesList = ref<any[]>([]); // 商户分类列表
+const tenantCategoriesTotal = ref(0); // 商户分类总数
+const areaStationList = ref<SysAreaStationOnStoreVo[]>([]); // 区域站点列表
+const areaOptions = ref<any[]>([]); // 所在区域树形选项
+const siteOptions = ref<any[]>([]); // 归属站点选项
+
+
 
 const dialog = reactive<DialogOption>({
   visible: false,
@@ -197,12 +277,14 @@ const initFormData: StoreForm = {
   longitude: undefined,
   latitude: undefined,
   tenantId: undefined,
+  services: [],
+  regionId: undefined,
 }
 const data = reactive<PageData<StoreForm, StoreQuery>>({
-  form: {...initFormData},
+  form: { ...initFormData },
   queryParams: {
     pageNum: 1,
-    pageSize: 10,
+    pageSize: 1,
     businessLicense: undefined,
     name: undefined,
     tenantCatergories: undefined,
@@ -244,6 +326,12 @@ const data = reactive<PageData<StoreForm, StoreQuery>>({
     tenantId: [
       { required: true, message: "租户编号不能为空", trigger: "change" }
     ],
+    regionId: [
+      { required: true, message: "所在区域不能为空", trigger: "change" }
+    ],
+    site: [
+      { required: true, message: "归属站点不能为空", trigger: "change" }
+    ],
   }
 });
 
@@ -266,7 +354,13 @@ const cancel = () => {
 
 /** 表单重置 */
 const reset = () => {
-  form.value = {...initFormData};
+  form.value = { ...initFormData };
+  // 重置新增的变量
+  regionValue.value = [];
+  province.value = '';
+  city.value = '';
+  district.value = '';
+  addressCascaderValue.value = [];
   storeFormRef.value?.resetFields();
 }
 
@@ -312,9 +406,9 @@ const submitForm = () => {
     if (valid) {
       buttonLoading.value = true;
       if (form.value.id) {
-        await updateStore(form.value).finally(() =>  buttonLoading.value = false);
+        await updateStore(form.value).finally(() => buttonLoading.value = false);
       } else {
-        await addStore(form.value).finally(() =>  buttonLoading.value = false);
+        await addStore(form.value).finally(() => buttonLoading.value = false);
       }
       proxy?.$modal.msgSuccess("操作成功");
       dialog.visible = false;
@@ -339,7 +433,236 @@ const handleExport = () => {
   }, `store_${new Date().getTime()}.xlsx`)
 }
 
+/** 获取经纬度 */
+const getGeolocation = () => {
+  if ('geolocation' in navigator) {
+    navigator.geolocation.getCurrentPosition(
+      (position) => {
+        form.value.longitude = position.coords.longitude.toFixed(6);
+        form.value.latitude = position.coords.latitude.toFixed(6);
+        proxy?.$modal.msgSuccess('获取经纬度成功');
+      },
+      (error) => {
+        let errorMessage = '获取位置失败';
+        switch (error.code) {
+          case error.PERMISSION_DENIED:
+            errorMessage = '用户拒绝了地理定位请求';
+            break;
+          case error.POSITION_UNAVAILABLE:
+            errorMessage = '位置信息不可用';
+            break;
+          case error.TIMEOUT:
+            errorMessage = '获取位置超时';
+            break;
+          case error.UNKNOWN_ERROR:
+            errorMessage = '未知错误';
+            break;
+        }
+        proxy?.$modal.msgError(errorMessage);
+      }
+    );
+  } else {
+    proxy?.$modal.msgError('您的浏览器不支持地理定位');
+  }
+};
+
+/** 获取品牌列表 */
+const getBrandList = async (pageNum = 1, keyword = '', append = false) => {
+  brandLoading.value = true;
+  // 确保参数格式正确,直接传递数字类型的pageNum
+  const res = await listOnStore({ pageNum: pageNum, pageSize: 10 });
+  if (res.code === 200) {
+    if (append) {
+      // 追加模式,用于分页加载
+      brandList.value = [...brandList.value, ...res.rows];
+    } else {
+      // 替换模式,用于初始加载或搜索
+      brandList.value = res.rows;
+    }
+    // 存储总数
+    brandTotal.value = res.total || 0;
+    console.log('总数', brandTotal.value);
+  }
+  brandLoading.value = false;
+};
+
+/** 获取服务项目列表 */
+const getServiceList = async () => {
+  try {
+    const res = await listServiceOnStore();
+    // 转换数据格式,适配checkbox组件
+    serviceList.value = res.data || res;
+  } catch (error) {
+    console.error('获取服务项目列表失败:', error);
+  }
+};
+
+/** 获取区域站点列表 */
+const getAreaStationList = async () => {
+  try {
+    const res = await listAreaStationOnStore();
+    const data = res.data || res;
+    areaStationList.value = data;
+
+    // 分离所在区域数据(type为0或1)
+    const areaData = data.filter((item: any) => item.type === 0 || item.type === 1);
+    // 构建树形结构
+    areaOptions.value = buildTree(areaData, 0);
+
+    // 初始化站点数据为空
+    siteOptions.value = [];
+  } catch (error) {
+    console.error('获取区域站点列表失败:', error);
+  }
+};
+
+/** 构建树形结构 */
+const buildTree = (data: any[], parentId: any): any[] => {
+  return data
+    .filter(item => String(item.parentId) === String(parentId))
+    .map(item => ({
+      value: item.id,
+      label: item.name,
+      children: buildTree(data, item.id)
+    }));
+};
+
+/** 处理所在区域选择变化 */
+const handleAreaChange = (value: any[]) => {
+  // 清空归属站点选择
+  form.value.site = undefined;
+
+  if (value && value.length > 0) {
+    // 获取最后一级的id
+    const areaId = value[value.length - 1];
+    // 更新regionId
+    form.value.regionId = areaId;
+    // 过滤出parentId等于areaId的站点
+    siteOptions.value = areaStationList.value
+      .filter((item: any) => item.type === 2 && String(item.parentId) === String(areaId))
+      .map((item: any) => ({
+        value: item.id,
+        label: item.name
+      }));
+  } else {
+    // 如果没有选择区域,清空站点选项和regionId
+    form.value.regionId = undefined;
+    siteOptions.value = [];
+  }
+};
+
+/** 获取商户分类列表 */
+const getTenantCategoriesList = async (pageNum = 1) => {
+  try {
+    const res = await listTenantCategoriesOnStore({ pageNum, pageSize: 10 });
+    if (res.code === 200) {
+      tenantCategoriesList.value = res.rows;
+      tenantCategoriesTotal.value = res.total || 0;
+    }
+  } catch (error) {
+    console.error('获取商户分类列表失败:', error);
+  }
+};
+
+/** 处理品牌页面切换 */
+const handleBrandPageChange = (page: number) => {
+  // 确保page是数字类型
+  const pageNum = Number(page);
+  currentPage.value = pageNum;
+  getBrandList(pageNum, brandKeyword.value, false);
+};
+
+/** 处理商户分类分页 */
+const handleTenantCategoriesPageChange = (page: number) => {
+  // 确保page是数字类型
+  const pageNum = Number(page);
+  getTenantCategoriesList(pageNum);
+};
+
+/** 处理商户分类选择框可见性变化 */
+const handleTenantCategoriesVisibleChange = (visible: boolean) => {
+  if (visible) {
+    getTenantCategoriesList(1);
+  }
+};
+
+/** 远程搜索方法 */
+const remoteMethod = (query: string) => {
+  brandKeyword.value = query;
+  currentPage.value = 1;
+  getBrandList(1, query, false);
+};
+
+/** 处理品牌选择框显示状态变化 */
+const handleBrandSelectVisibleChange = (visible: boolean) => {
+  brandSelectVisible.value = visible;
+  if (visible) {
+    // 选择框显示时,重置页码并重新加载数据
+    currentPage.value = 1;
+    getBrandList(1, brandKeyword.value, false);
+  }
+};
+
 onMounted(() => {
   getList();
+  getBrandList();
+  getServiceList();
+  getAreaStationList();
 });
 </script>
+
+<style scoped>
+.brand-pagination {
+  margin-top: 10px;
+  padding-top: 10px;
+  border-top: 1px solid #ebeef5;
+  text-align: center;
+}
+
+.custom-pagination {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 10px;
+  font-size: 14px;
+}
+
+.page-arrow {
+  cursor: pointer;
+  color: #606266;
+  user-select: none;
+  padding: 2px 8px;
+  transition: color 0.3s;
+}
+
+.page-arrow:hover:not(.disabled) {
+  color: #409eff;
+}
+
+.page-arrow.disabled {
+  color: #c0c4cc;
+  cursor: not-allowed;
+}
+
+.page-number {
+  cursor: pointer;
+  color: #606266;
+  padding: 2px 8px;
+  transition: all 0.3s;
+}
+
+.page-number:hover {
+  color: #409eff;
+}
+
+.page-number.active {
+  color: #409eff;
+  font-weight: bold;
+}
+
+.total-text {
+  margin-left: 15px;
+  font-size: 12px;
+  color: #909399;
+}
+</style>

+ 52 - 39
src/views/system/tenantCategories/index.vue

@@ -1,7 +1,6 @@
 <template>
   <div class="p-2">
-    <transition :enter-active-class="proxy?.animate.searchAnimate.enter"
-      :leave-active-class="proxy?.animate.searchAnimate.leave">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
       <div v-show="showSearch" class="mb-[10px]">
         <el-card shadow="hover">
           <el-form ref="queryFormRef" :model="queryParams" :inline="true">
@@ -9,14 +8,26 @@
               <el-input v-model="queryParams.name" placeholder="请输入分类名称" clearable @keyup.enter="handleQuery" />
             </el-form-item>
             <el-form-item label="创建时间" style="width: 308px">
-              <el-date-picker v-model="dateRangeCreateTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
-                range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
-                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" />
+              <el-date-picker
+                v-model="dateRangeCreateTime"
+                value-format="YYYY-MM-DD HH:mm:ss"
+                type="daterange"
+                range-separator="-"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
+              />
             </el-form-item>
             <el-form-item label="更新时间" style="width: 308px">
-              <el-date-picker v-model="dateRangeUpdateTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
-                range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
-                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" />
+              <el-date-picker
+                v-model="dateRangeUpdateTime"
+                value-format="YYYY-MM-DD HH:mm:ss"
+                type="daterange"
+                range-separator="-"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
+              />
             </el-form-item>
             <el-form-item>
               <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@@ -31,20 +42,20 @@
       <template #header>
         <el-row :gutter="10" class="mb8">
           <el-col :span="1.5">
-            <el-button type="primary" plain icon="Plus" @click="handleAdd"
-              v-hasPermi="['system:tenantCategories:add']">新增</el-button>
+            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:tenantCategories:add']">新增</el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"
-              v-hasPermi="['system:tenantCategories:edit']">修改</el-button>
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:tenantCategories:edit']"
+              >修改</el-button
+            >
           </el-col>
           <el-col :span="1.5">
-            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()"
-              v-hasPermi="['system:tenantCategories:remove']">删除</el-button>
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:tenantCategories:remove']"
+              >删除</el-button
+            >
           </el-col>
           <el-col :span="1.5">
-            <el-button type="warning" plain icon="Download" @click="handleExport"
-              v-hasPermi="['system:tenantCategories:export']">导出</el-button>
+            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:tenantCategories:export']">导出</el-button>
           </el-col>
           <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
         </el-row>
@@ -52,49 +63,51 @@
 
       <el-table v-loading="loading" border :data="tenantCategoriesList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
-        <el-table-column label="序号" align="center" prop="id" v-if="true" width="200" />
-        <el-table-column label="分类名称" align="center" prop="name" />
-        <el-table-column label="排序" align="center" prop="sort" />
+        <el-table-column label="分类名称"  prop="name" width="1000" />
         <el-table-column label="图标" align="center" prop="iconUrl" width="100">
           <template #default="scope">
             <image-preview :src="scope.row.iconUrl" :width="50" :height="50" />
           </template>
         </el-table-column>
-        <el-table-column label="状态" align="center" prop="status">
+        <el-table-column label="排序" align="center" prop="sort" width="100" />
+        <el-table-column label="状态" align="center" prop="status" width="100">
           <template #default="scope">
             <el-tag :type="scope.row.status ? 'success' : 'danger'">
               {{ scope.row.status ? '启用' : '禁用' }}
             </el-tag>
           </template>
         </el-table-column>
-        <el-table-column label="创建者" align="center" prop="createBy" />
-        <el-table-column label="创建时间" align="center" prop="createTime" width="180">
-          <template #default="scope">
-            <span>{{ parseTime(scope.row.createTime) }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column label="更新者" align="center" prop="updateBy" />
-        <el-table-column label="更新时间" align="center" prop="updateTime" width="180">
-          <template #default="scope">
-            <span>{{ parseTime(scope.row.updateTime) }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
+        <!--        <el-table-column label="创建者" align="center" prop="createBy" />-->
+        <!--        <el-table-column label="创建时间" align="center" prop="createTime" width="180">-->
+        <!--          <template #default="scope">-->
+        <!--            <span>{{ parseTime(scope.row.createTime) }}</span>-->
+        <!--          </template>-->
+        <!--        </el-table-column>-->
+        <!--        <el-table-column label="更新者" align="center" prop="updateBy" />-->
+        <!--        <el-table-column label="更新时间" align="center" prop="updateTime" width="180">-->
+        <!--          <template #default="scope">-->
+        <!--            <span>{{ parseTime(scope.row.updateTime) }}</span>-->
+        <!--          </template>-->
+        <!--        </el-table-column>-->
+        <el-table-column label="操作" align="center" fixed="right" class-name="small-padding">
           <template #default="scope">
             <el-tooltip content="修改" placement="top">
-              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
-                v-hasPermi="['system:tenantCategories:edit']"></el-button>
+              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:tenantCategories:edit']"></el-button>
             </el-tooltip>
             <el-tooltip content="删除" placement="top">
-              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
-                v-hasPermi="['system:tenantCategories:remove']"></el-button>
+              <el-button
+                link
+                type="primary"
+                icon="Delete"
+                @click="handleDelete(scope.row)"
+                v-hasPermi="['system:tenantCategories:remove']"
+              ></el-button>
             </el-tooltip>
           </template>
         </el-table-column>
       </el-table>
 
-      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
-        v-model:limit="queryParams.pageSize" @pagination="getList" />
+      <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="500px" append-to-body>