Procházet zdrojové kódy

菜单前端基本昨晚,品牌管理改造完成

Huanyi před 2 měsíci
rodič
revize
6fc8ffa94a

+ 3 - 0
.env.development

@@ -29,6 +29,9 @@ VITE_APP_RSA_PRIVATE_KEY = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3C
 # 客户端id
 VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'
 
+# 平台号
+VITE_APP_PLATFORM_CODE = '4pwuAzDBzUd6hekvGHHKedT4VX5WHERAXHpeztPFAzRaUsBUrD'
+
 # websocket 开关 默认使用sse推送
 VITE_APP_WEBSOCKET = false
 

+ 3 - 0
.env.production

@@ -32,6 +32,9 @@ VITE_APP_RSA_PRIVATE_KEY = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3C
 # 客户端id
 VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'
 
+# 平台号
+VITE_APP_PLATFORM_CODE = '4pwuAzDBzUd6hekvGHHKedT4VX5WHERAXHpeztPFAzRaUsBUrD'
+
 # websocket 开关 默认使用sse推送
 VITE_APP_WEBSOCKET = false
 

+ 1 - 1
package.json

@@ -2,7 +2,7 @@
   "$schema": "https://json.schemastore.org/package",
   "name": "pet-system-admin",
   "version": "0.0.1",
-  "description": "宠物系统管理",
+  "description": "宠物系统管理后台.",
   "author": "Huanyi",
   "license": "MIT",
   "type": "module",

+ 63 - 0
src/api/system/tenantCategories/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { TenantCategoriesVO, TenantCategoriesForm, TenantCategoriesQuery } from '@/api/system/tenantCategories/types';
+
+/**
+ * 查询商户分类列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listTenantCategories = (query?: TenantCategoriesQuery): AxiosPromise<TenantCategoriesVO[]> => {
+  return request({
+    url: '/system/tenantCategories/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询商户分类详细
+ * @param id
+ */
+export const getTenantCategories = (id: string | number): AxiosPromise<TenantCategoriesVO> => {
+  return request({
+    url: '/system/tenantCategories/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增商户分类
+ * @param data
+ */
+export const addTenantCategories = (data: TenantCategoriesForm) => {
+  return request({
+    url: '/system/tenantCategories',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改商户分类
+ * @param data
+ */
+export const updateTenantCategories = (data: TenantCategoriesForm) => {
+  return request({
+    url: '/system/tenantCategories',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除商户分类
+ * @param id
+ */
+export const delTenantCategories = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/system/tenantCategories/' + id,
+    method: 'delete'
+  });
+};

+ 110 - 0
src/api/system/tenantCategories/types.ts

@@ -0,0 +1,110 @@
+export interface TenantCategoriesVO {
+  /**
+   * 序号
+   */
+  id: string | number;
+
+  /**
+   * 分类名称
+   */
+  name: string;
+
+  /**
+   * 排序
+   */
+  sort: number;
+
+  /**
+   * 图标
+   */
+  icon: number;
+
+  /**
+   * 图标Url
+   */
+  iconUrl: string;
+  /**
+   * 状态
+   */
+  status: number;
+
+  /**
+   * 创建者
+   */
+  createBy: number;
+
+  /**
+   * 创建时间
+   */
+  createTime: string;
+
+  /**
+   * 更新者
+   */
+  updateBy: number;
+
+  /**
+   * 更新时间
+   */
+  updateTime: string;
+
+}
+
+export interface TenantCategoriesForm extends BaseEntity {
+  /**
+   * 序号
+   */
+  id?: string | number;
+
+  /**
+   * 分类名称
+   */
+  name?: string;
+
+  /**
+   * 排序
+   */
+  sort?: number;
+
+  /**
+   * 图标
+   */
+  icon?: number;
+
+  /**
+   * 状态
+   */
+  status?: number;
+
+}
+
+export interface TenantCategoriesQuery extends PageQuery {
+
+  /**
+   * 分类名称
+   */
+  name?: string;
+
+  /**
+   * 状态
+   */
+  status?: number;
+
+  /**
+   * 创建时间
+   */
+  createTime?: string;
+
+  /**
+   * 更新时间
+   */
+  updateTime?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}
+
+
+

+ 1 - 0
src/api/types.ts

@@ -15,6 +15,7 @@ export type RegisterForm = {
  * 登录请求
  */
 export interface LoginData {
+  platformId?: number;
   tenantId?: string;
   username?: string;
   password?: string;

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

@@ -46,6 +46,7 @@ declare module 'vue' {
     ElPagination: typeof import('element-plus/es')['ElPagination']
     ElPopover: typeof import('element-plus/es')['ElPopover']
     ElRadio: typeof import('element-plus/es')['ElRadio']
+    ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
     ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']

+ 1 - 0
src/utils/request.ts

@@ -25,6 +25,7 @@ export const globalHeaders = () => {
 
 axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8';
 axios.defaults.headers['clientid'] = import.meta.env.VITE_APP_CLIENT_ID;
+axios.defaults.headers['X-Platform-Code'] = import.meta.env.VITE_APP_PLATFORM_CODE;
 // 创建 axios 实例
 const service = axios.create({
   baseURL: import.meta.env.VITE_APP_BASE_API,

+ 116 - 135
src/views/index.vue

@@ -1,96 +1,23 @@
 <template>
-  <div class="app-container home">
-    <el-row :gutter="20">
-      <el-col :sm="24" :lg="12" style="padding-left: 20px">
-        <h2>RuoYi-Vue-Plus多租户管理系统</h2>
-        <p>
-          RuoYi-Vue-Plus 是基于 RuoYi-Vue 针对 分布式集群 场景升级(不兼容原框架)
-          <br />
-          * 前端开发框架 Vue3、TS、Element Plus<br />
-          * 后端开发框架 Spring Boot<br />
-          * 容器框架 Undertow 基于 Netty 的高性能容器<br />
-          * 权限认证框架 Sa-Token 支持多终端认证系统<br />
-          * 关系数据库 MySQL 适配 8.X 最低 5.7<br />
-          * 缓存数据库 Redis 适配 6.X 最低 4.X<br />
-          * 数据库框架 Mybatis-Plus 快速 CRUD 增加开发效率<br />
-          * 数据库框架 p6spy 更强劲的 SQL 分析<br />
-          * 多数据源框架 dynamic-datasource 支持主从与多种类数据库异构<br />
-          * 序列化框架 Jackson 统一使用 jackson 高效可靠<br />
-          * Redis客户端 Redisson 性能强劲、API丰富<br />
-          * 分布式限流 Redisson 全局、请求IP、集群ID 多种限流<br />
-          * 分布式锁 Lock4j 注解锁、工具锁 多种多样<br />
-          * 分布式幂等 Lock4j 基于分布式锁实现<br />
-          * 分布式链路追踪 SkyWalking 支持链路追踪、网格分析、度量聚合、可视化<br />
-          * 分布式任务调度 SnailJob 高性能 高可靠 易扩展<br />
-          * 文件存储 Minio 本地存储<br />
-          * 文件存储 七牛、阿里、腾讯 云存储<br />
-          * 监控框架 SpringBoot-Admin 全方位服务监控<br />
-          * 校验框架 Validation 增强接口安全性 严谨性<br />
-          * Excel框架 FastExcel(原Alibaba EasyExcel) 性能优异 扩展性强<br />
-          * 文档框架 SpringDoc、javadoc 无注解零入侵基于java注释<br />
-          * 工具类框架 Hutool、Lombok 减少代码冗余 增加安全性<br />
-          * 代码生成器 适配MP、SpringDoc规范化代码 一键生成前后端代码<br />
-          * 部署方式 Docker 容器编排 一键部署业务集群<br />
-          * 国际化 SpringMessage Spring标准国际化方案<br />
-        </p>
-        <p><b>当前版本:</b> <span>v5.5.3</span></p>
-        <p>
-          <el-tag type="danger">&yen;免费开源</el-tag>
-        </p>
-        <p>
-          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://gitee.com/dromara/RuoYi-Vue-Plus')">访问码云</el-button>
-          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://github.com/dromara/RuoYi-Vue-Plus')">访问GitHub</el-button>
-          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://plus-doc.dromara.org/#/ruoyi-vue-plus/changlog')"
-            >更新日志</el-button
-          >
-        </p>
-      </el-col>
-
-      <el-col :sm="24" :lg="12" style="padding-left: 20px">
-        <h2>RuoYi-Cloud-Plus多租户微服务管理系统</h2>
-        <p>
-          RuoYi-Cloud-Plus 微服务通用权限管理系统 重写 RuoYi-Cloud 全方位升级(不兼容原框架)
-          <br />
-          * 前端开发框架 Vue3、TS、Element UI<br />
-          * 后端开发框架 Spring Boot<br />
-          * 微服务开发框架 Spring Cloud、Spring Cloud Alibaba<br />
-          * 容器框架 Undertow 基于 XNIO 的高性能容器<br />
-          * 权限认证框架 Sa-Token、Jwt 支持多终端认证系统<br />
-          * 关系数据库 MySQL 适配 8.X 最低 5.7<br />
-          * 关系数据库 Oracle 适配 11g 12c<br />
-          * 关系数据库 PostgreSQL 适配 13 14<br />
-          * 关系数据库 SQLServer 适配 2017 2019<br />
-          * 缓存数据库 Redis 适配 6.X 最低 5.X<br />
-          * 分布式注册中心 Alibaba Nacos 采用2.X 基于GRPC通信高性能<br />
-          * 分布式配置中心 Alibaba Nacos 采用2.X 基于GRPC通信高性能<br />
-          * 服务网关 Spring Cloud Gateway 响应式高性能网关<br />
-          * 负载均衡 Spring Cloud Loadbalancer 负载均衡处理<br />
-          * RPC远程调用 Apache Dubbo 原生态使用体验、高性能<br />
-          * 分布式限流熔断 Alibaba Sentinel 无侵入、高扩展<br />
-          * 分布式事务 Alibaba Seata 无侵入、高扩展 支持 四种模式<br />
-          * 分布式消息队列 Apache Kafka 高性能高速度<br />
-          * 分布式消息队列 Apache RocketMQ 高可用功能多样<br />
-          * 分布式消息队列 RabbitMQ 支持各种扩展插件功能多样性<br />
-          * 分布式搜索引擎 ElasticSearch 业界知名<br />
-          * 分布式链路追踪 Apache SkyWalking 链路追踪、网格分析、度量聚合、可视化<br />
-          * 分布式日志中心 ELK 业界成熟解决方案<br />
-          * 分布式监控 Prometheus、Grafana 全方位性能监控<br />
-          * 其余与 Vue 版本一致<br />
-        </p>
-        <p><b>当前版本:</b> <span>v2.5.3</span></p>
-        <p>
-          <el-tag type="danger">&yen;免费开源</el-tag>
-        </p>
-        <p>
-          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://gitee.com/dromara/RuoYi-Cloud-Plus')">访问码云</el-button>
-          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://github.com/dromara/RuoYi-Cloud-Plus')">访问GitHub</el-button>
-          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://plus-doc.dromara.org/#/ruoyi-cloud-plus/changlog')"
-            >更新日志</el-button
-          >
-        </p>
-      </el-col>
-    </el-row>
-    <el-divider />
+  <div class="home">
+    <div class="coming-soon-container">
+      <div class="coming-soon-content">
+        <div class="icon-wrapper">
+          <svg class="icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <path
+              d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z"
+              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
+            <path d="M12 8V12L15 15" stroke="currentColor" stroke-width="2" stroke-linecap="round"
+              stroke-linejoin="round" />
+          </svg>
+        </div>
+        <h2 class="coming-soon-title">待开发</h2>
+        <p class="coming-soon-subtitle">尽情期待</p>
+        <div class="loading-spinner">
+          <div class="spinner"></div>
+        </div>
+      </div>
+    </div>
   </div>
 </template>
 
@@ -102,64 +29,118 @@ const goTarget = (url: string) => {
 
 <style lang="scss" scoped>
 .home {
-  blockquote {
-    padding: 10px 20px;
-    margin: 0 0 20px;
-    font-size: 17.5px;
-    border-left: 5px solid #eee;
-  }
-  hr {
-    margin-top: 20px;
-    margin-bottom: 20px;
-    border: 0;
-    border-top: 1px solid #eee;
+  min-height: 100vh;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
+  font-family: 'open sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+  overflow-x: hidden;
+}
+
+.coming-soon-container {
+  width: 100%;
+  max-width: 600px;
+  padding: 40px;
+}
+
+.coming-soon-content {
+  background: rgba(255, 255, 255, 0.95);
+  border-radius: 16px;
+  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
+  backdrop-filter: blur(10px);
+  -webkit-backdrop-filter: blur(10px);
+  border: 1px solid rgba(255, 255, 255, 0.3);
+  padding: 60px 40px;
+  text-align: center;
+  transition: all 0.3s ease;
+
+  &:hover {
+    transform: translateY(-5px);
+    box-shadow: 0 15px 50px rgba(0, 0, 0, 0.15);
   }
-  .col-item {
-    margin-bottom: 20px;
+}
+
+.icon-wrapper {
+  margin-bottom: 30px;
+  animation: pulse 2s infinite;
+
+  .icon {
+    width: 80px;
+    height: 80px;
+    color: #4a6fa5;
   }
+}
+
+.coming-soon-title {
+  font-size: 36px;
+  font-weight: 600;
+  color: #2c3e50;
+  margin: 0 0 15px 0;
+  letter-spacing: -0.5px;
+}
 
-  ul {
-    padding: 0;
-    margin: 0;
+.coming-soon-subtitle {
+  font-size: 18px;
+  color: #7f8c8d;
+  margin: 0 0 40px 0;
+  font-weight: 400;
+}
+
+.loading-spinner {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin-top: 30px;
+}
+
+.spinner {
+  width: 40px;
+  height: 40px;
+  border: 4px solid rgba(74, 111, 165, 0.2);
+  border-left-color: #4a6fa5;
+  border-radius: 50%;
+  animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+  to {
+    transform: rotate(360deg);
   }
+}
 
-  font-family: 'open sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
-  font-size: 13px;
-  color: #676a6c;
-  overflow-x: hidden;
+@keyframes pulse {
+
+  0%,
+  100% {
+    transform: scale(1);
+  }
 
-  ul {
-    list-style-type: none;
+  50% {
+    transform: scale(1.05);
   }
+}
 
-  h4 {
-    margin-top: 0px;
+@media (max-width: 768px) {
+  .coming-soon-container {
+    padding: 20px;
   }
 
-  h2 {
-    margin-top: 10px;
-    font-size: 26px;
-    font-weight: 100;
+  .coming-soon-content {
+    padding: 40px 20px;
   }
 
-  p {
-    margin-top: 10px;
+  .icon-wrapper .icon {
+    width: 60px;
+    height: 60px;
+  }
 
-    b {
-      font-weight: 700;
-    }
+  .coming-soon-title {
+    font-size: 28px;
   }
 
-  .update-log {
-    ol {
-      display: block;
-      list-style-type: decimal;
-      margin-block-start: 1em;
-      margin-block-end: 1em;
-      margin-inline-start: 0;
-      margin-inline-end: 0;
-      padding-inline-start: 40px;
-    }
+  .coming-soon-subtitle {
+    font-size: 16px;
   }
 }
 </style>

+ 2 - 0
src/views/login.vue

@@ -95,6 +95,7 @@ const router = useRouter();
 const { t } = useI18n();
 
 const loginForm = ref<LoginData>({
+  platformId: 0,
   tenantId: '000000',
   username: 'admin',
   password: 'admin123',
@@ -189,6 +190,7 @@ const getLoginData = () => {
   const password = localStorage.getItem('password');
   const rememberMe = localStorage.getItem('rememberMe');
   loginForm.value = {
+    platformId: 0,
     tenantId: tenantId === null ? String(loginForm.value.tenantId) : tenantId,
     username: username === null ? String(loginForm.value.username) : username,
     password: password === null ? String(loginForm.value.password) : String(password),

+ 190 - 104
src/views/system/menu/index.vue

@@ -1,84 +1,166 @@
 <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 label="菜单名称" prop="menuName">
-              <el-input v-model="queryParams.menuName" placeholder="请输入菜单名称" clearable @keyup.enter="handleQuery" />
-            </el-form-item>
-            <el-form-item label="状态" prop="status">
-              <el-select v-model="queryParams.status" placeholder="菜单状态" clearable>
-                <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
-              </el-select>
-            </el-form-item>
-            <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="hover">
-      <template #header>
-        <el-row :gutter="10">
-          <el-col :span="1.5">
-            <el-button v-hasPermi="['system:menu:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button v-hasPermi="['system:menu:remove']" type="danger" plain icon="Delete" @click="handleCascadeDelete" :loading="deleteLoading">级联删除</el-button>
-          </el-col>
-          <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
-        </el-row>
-      </template>
+      <!-- 顶部选项卡 -->
+      <el-tabs v-model="platformId" @tab-click="handlePlatformChange" type="border-card" class="mb-[15px]">
+        <el-tab-pane label="管理后台" :name="0">
+          <!-- 管理后台内容 -->
+          <div class="tab-content">
+            <el-row :gutter="10" class="mb-[10px]">
+              <el-col :span="1.5">
+                <el-button v-hasPermi="['system:menu:add']" type="primary" plain icon="Plus"
+                  @click="handleAdd()">新增</el-button>
+              </el-col>
+              <el-col :span="1.5">
+                <el-button v-hasPermi="['system:menu:remove']" type="danger" plain icon="Delete"
+                  @click="handleCascadeDelete" :loading="deleteLoading">级联删除</el-button>
+              </el-col>
+              <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
+            </el-row>
+
+            <transition :enter-active-class="proxy?.animate.searchAnimate.enter"
+              :leave-active-class="proxy?.animate.searchAnimate.leave">
+              <div v-show="showSearch" class="mb-[10px]">
+                <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+                  <el-form-item label="菜单名称" prop="menuName">
+                    <el-input v-model="queryParams.menuName" placeholder="请输入菜单名称" clearable
+                      @keyup.enter="handleQuery" />
+                  </el-form-item>
+                  <el-form-item label="状态" prop="status">
+                    <el-select v-model="queryParams.status" placeholder="菜单状态" clearable>
+                      <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label"
+                        :value="dict.value" />
+                    </el-select>
+                  </el-form-item>
+                  <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>
+              </div>
+            </transition>
+
+            <el-table ref="menuTableRef" v-loading="loading" :data="menuList" row-key="menuId" border
+              :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" :default-expand-all="false" lazy
+              :load="getChildrenList" :expand-change="expandMenuHandle">
+              <el-table-column prop="menuName" label="菜单名称" :show-overflow-tooltip="true" width="160"></el-table-column>
+              <el-table-column prop="icon" label="图标" align="center" width="100">
+                <template #default="scope">
+                  <svg-icon :icon-class="scope.row.icon" />
+                </template>
+              </el-table-column>
+              <el-table-column prop="orderNum" label="排序" width="60"></el-table-column>
+              <el-table-column prop="perms" label="权限标识" :show-overflow-tooltip="true"></el-table-column>
+              <el-table-column prop="component" label="组件路径" :show-overflow-tooltip="true"></el-table-column>
+              <el-table-column prop="status" label="状态" width="80">
+                <template #default="scope">
+                  <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
+                </template>
+              </el-table-column>
+              <el-table-column label="创建时间" align="center" prop="createTime">
+                <template #default="scope">
+                  <span>{{ scope.row.createTime }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column fixed="right" label="操作" width="180">
+                <template #default="scope">
+                  <el-tooltip content="修改" placement="top">
+                    <el-button v-hasPermi="['system:menu:edit']" link type="primary" icon="Edit"
+                      @click="handleUpdate(scope.row)" />
+                  </el-tooltip>
+                  <el-tooltip content="新增" placement="top">
+                    <el-button v-hasPermi="['system:menu:add']" link type="primary" icon="Plus"
+                      @click="handleAdd(scope.row)" />
+                  </el-tooltip>
+                  <el-tooltip content="删除" placement="top">
+                    <el-button v-hasPermi="['system:menu:remove']" link type="primary" icon="Delete"
+                      @click="handleDelete(scope.row)" />
+                  </el-tooltip>
+                </template>
+              </el-table-column>
+            </el-table>
+          </div>
+        </el-tab-pane>
+        <el-tab-pane label="商户后台" :name="1">
+          <!-- 商户后台内容 -->
+          <div class="tab-content">
+            <el-row :gutter="10" class="mb-[10px]">
+              <el-col :span="1.5">
+                <el-button v-hasPermi="['system:menu:add']" type="primary" plain icon="Plus"
+                  @click="handleAdd()">新增</el-button>
+              </el-col>
+              <el-col :span="1.5">
+                <el-button v-hasPermi="['system:menu:remove']" type="danger" plain icon="Delete"
+                  @click="handleCascadeDelete" :loading="deleteLoading">级联删除</el-button>
+              </el-col>
+              <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
+            </el-row>
 
-      <el-table
-        ref="menuTableRef"
-        v-loading="loading"
-        :data="menuList"
-        row-key="menuId"
-        border
-        :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
-        :default-expand-all="false"
-        lazy
-        :load="getChildrenList"
-        :expand-change="expandMenuHandle"
-      >
-        <el-table-column prop="menuName" label="菜单名称" :show-overflow-tooltip="true" width="160"></el-table-column>
-        <el-table-column prop="icon" label="图标" align="center" width="100">
-          <template #default="scope">
-            <svg-icon :icon-class="scope.row.icon" />
-          </template>
-        </el-table-column>
-        <el-table-column prop="orderNum" label="排序" width="60"></el-table-column>
-        <el-table-column prop="perms" label="权限标识" :show-overflow-tooltip="true"></el-table-column>
-        <el-table-column prop="component" label="组件路径" :show-overflow-tooltip="true"></el-table-column>
-        <el-table-column prop="status" label="状态" width="80">
-          <template #default="scope">
-            <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
-          </template>
-        </el-table-column>
-        <el-table-column label="创建时间" align="center" prop="createTime">
-          <template #default="scope">
-            <span>{{ scope.row.createTime }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column fixed="right" label="操作" width="180">
-          <template #default="scope">
-            <el-tooltip content="修改" placement="top">
-              <el-button v-hasPermi="['system:menu:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)" />
-            </el-tooltip>
-            <el-tooltip content="新增" placement="top">
-              <el-button v-hasPermi="['system:menu:add']" link type="primary" icon="Plus" @click="handleAdd(scope.row)" />
-            </el-tooltip>
-            <el-tooltip content="删除" placement="top">
-              <el-button v-hasPermi="['system:menu:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)" />
-            </el-tooltip>
-          </template>
-        </el-table-column>
-      </el-table>
+            <transition :enter-active-class="proxy?.animate.searchAnimate.enter"
+              :leave-active-class="proxy?.animate.searchAnimate.leave">
+              <div v-show="showSearch" class="mb-[10px]">
+                <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+                  <el-form-item label="菜单名称" prop="menuName">
+                    <el-input v-model="queryParams.menuName" placeholder="请输入菜单名称" clearable
+                      @keyup.enter="handleQuery" />
+                  </el-form-item>
+                  <el-form-item label="状态" prop="status">
+                    <el-select v-model="queryParams.status" placeholder="菜单状态" clearable>
+                      <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label"
+                        :value="dict.value" />
+                    </el-select>
+                  </el-form-item>
+                  <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>
+              </div>
+            </transition>
+
+            <el-table ref="menuTableRef" v-loading="loading" :data="menuList" row-key="menuId" border
+              :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" :default-expand-all="false" lazy
+              :load="getChildrenList" :expand-change="expandMenuHandle">
+              <el-table-column prop="menuName" label="菜单名称" :show-overflow-tooltip="true" width="160"></el-table-column>
+              <el-table-column prop="icon" label="图标" align="center" width="100">
+                <template #default="scope">
+                  <svg-icon :icon-class="scope.row.icon" />
+                </template>
+              </el-table-column>
+              <el-table-column prop="orderNum" label="排序" width="60"></el-table-column>
+              <el-table-column prop="perms" label="权限标识" :show-overflow-tooltip="true"></el-table-column>
+              <el-table-column prop="component" label="组件路径" :show-overflow-tooltip="true"></el-table-column>
+              <el-table-column prop="status" label="状态" width="80">
+                <template #default="scope">
+                  <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
+                </template>
+              </el-table-column>
+              <el-table-column label="创建时间" align="center" prop="createTime">
+                <template #default="scope">
+                  <span>{{ scope.row.createTime }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column fixed="right" label="操作" width="180">
+                <template #default="scope">
+                  <el-tooltip content="修改" placement="top">
+                    <el-button v-hasPermi="['system:menu:edit']" link type="primary" icon="Edit"
+                      @click="handleUpdate(scope.row)" />
+                  </el-tooltip>
+                  <el-tooltip content="新增" placement="top">
+                    <el-button v-hasPermi="['system:menu:add']" link type="primary" icon="Plus"
+                      @click="handleAdd(scope.row)" />
+                  </el-tooltip>
+                  <el-tooltip content="删除" placement="top">
+                    <el-button v-hasPermi="['system:menu:remove']" link type="primary" icon="Delete"
+                      @click="handleDelete(scope.row)" />
+                  </el-tooltip>
+                </template>
+              </el-table-column>
+            </el-table>
+          </div>
+        </el-tab-pane>
+      </el-tabs>
     </el-card>
 
     <el-dialog v-model="dialog.visible" :title="dialog.title" destroy-on-close append-to-bod width="750px">
@@ -86,14 +168,9 @@
         <el-row>
           <el-col :span="24">
             <el-form-item label="上级菜单">
-              <el-tree-select
-                v-model="form.parentId"
-                :data="menuOptions"
-                :props="{ value: 'menuId', label: 'menuName', children: 'children' } as any"
-                value-key="menuId"
-                placeholder="选择上级菜单"
-                check-strictly
-              />
+              <el-tree-select v-model="form.parentId" :data="menuOptions"
+                :props="{ value: 'menuId', label: 'menuName', children: 'children' } as any" value-key="menuId"
+                placeholder="选择上级菜单" check-strictly />
             </el-form-item>
           </el-col>
           <el-col :span="24">
@@ -128,8 +205,7 @@
                   <el-tooltip content="选择是外链则路由地址需要以`http(s)://`开头" placement="top">
                     <el-icon>
                       <question-filled />
-                    </el-icon> </el-tooltip
-                  >是否外链
+                    </el-icon> </el-tooltip>是否外链
                 </span>
               </template>
               <el-radio-group v-model="form.isFrame">
@@ -229,7 +305,8 @@
                 </span>
               </template>
               <el-radio-group v-model="form.visible">
-                <el-radio v-for="dict in sys_show_hide" :key="dict.value" :value="dict.value">{{ dict.label }} </el-radio>
+                <el-radio v-for="dict in sys_show_hide" :key="dict.value" :value="dict.value">{{ dict.label }}
+                </el-radio>
               </el-radio-group>
             </el-form-item>
           </el-col>
@@ -278,17 +355,9 @@
     </el-dialog>
 
     <el-dialog v-model="deleteDialog.visible" :title="deleteDialog.title" destroy-on-close append-to-bod width="750px">
-      <el-tree
-        ref="menuTreeRef"
-        class="tree-border"
-        :data="menuOptions"
-        show-checkbox
-        node-key="menuId"
-        :check-strictly="false"
-        empty-text="加载中,请稍候"
-        :default-expanded-keys="[0]"
-        :props="{ value: 'menuId', label: 'menuName', children: 'children' } as any"
-      />
+      <el-tree ref="menuTreeRef" class="tree-border" :data="menuOptions" show-checkbox node-key="menuId"
+        :check-strictly="false" empty-text="加载中,请稍候" :default-expanded-keys="[0]"
+        :props="{ value: 'menuId', label: 'menuName', children: 'children' } as any" />
       <template #footer>
         <div class="dialog-footer">
           <el-button type="primary" @click="submitDeleteForm" :loading="deleteLoading">确 定</el-button>
@@ -313,6 +382,7 @@ interface MenuOptionsType {
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { sys_show_hide, sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_show_hide', 'sys_normal_disable'));
 
+const platformId = ref(0); // 默认选中管理后台,使用数字类型
 const menuList = ref<MenuVO[]>([]);
 const menuChildrenListMap = ref({});
 const menuExpandMap = ref({});
@@ -400,7 +470,11 @@ const refreshAllExpandMenuData = () => {
 /** 查询菜单列表 */
 const getList = async () => {
   loading.value = true;
-  const res = await listMenu(queryParams.value);
+  const params = {
+    ...queryParams.value,
+    platformId: platformId.value
+  };
+  const res = await listMenu(params);
 
   const tempMap = {};
   // 存储 父菜单:子菜单列表
@@ -428,7 +502,7 @@ const getList = async () => {
 /** 查询菜单下拉树结构 */
 const getTreeselect = async () => {
   menuOptions.value = [];
-  const response = await listMenu();
+  const response = await listMenu({ platformId: platformId.value });
   const menu: MenuOptionsType = { menuId: 0, menuName: '主类目', children: [] };
   menu.children = proxy?.handleTree<MenuOptionsType>(response.data, 'menuId');
   menuOptions.value.push(menu);
@@ -444,10 +518,18 @@ const reset = () => {
   menuFormRef.value?.resetFields();
 };
 
+/** 平台切换操作 */
+const handlePlatformChange = (tab: any) => {
+  // 确保platformId的值与当前选中的选项卡一致
+  platformId.value = Number(tab.props.name);
+  getList();
+};
+
 /** 搜索按钮操作 */
 const handleQuery = () => {
   getList();
 };
+
 /** 重置按钮操作 */
 const resetQuery = () => {
   queryFormRef.value?.resetFields();
@@ -476,7 +558,11 @@ const handleUpdate = async (row: MenuVO) => {
 const submitForm = () => {
   menuFormRef.value?.validate(async (valid: boolean) => {
     if (valid) {
-      form.value.menuId ? await updateMenu(form.value) : await addMenu(form.value);
+      const menuData = {
+        ...form.value,
+        platformId: platformId.value
+      };
+      form.value.menuId ? await updateMenu(menuData) : await addMenu(menuData);
       proxy?.$modal.msgSuccess('操作成功');
       dialog.visible = false;
       await getList();
@@ -486,7 +572,7 @@ const submitForm = () => {
 /** 删除按钮操作 */
 const handleDelete = async (row: MenuVO) => {
   await proxy?.$modal.confirm('是否确认删除名称为"' + row.menuName + '"的数据项?');
-  await delMenu(row.menuId);
+  await delMenu(row.menuId, platformId.value);
   await getList();
   proxy?.$modal.msgSuccess('删除成功');
 };
@@ -521,7 +607,7 @@ const submitDeleteForm = async () => {
   }
 
   deleteLoading.value = true;
-  await cascadeDelMenu(menuIds).finally(() => (deleteLoading.value = false));
+  await cascadeDelMenu(menuIds, platformId.value).finally(() => (deleteLoading.value = false));
   await getList();
   proxy?.$modal.msgSuccess('删除成功');
   deleteDialog.visible = false;

+ 99 - 79
src/views/system/tenant/index.vue

@@ -1,14 +1,16 @@
 <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">
-            <el-form-item label="租户编号" prop="tenantId">
-              <el-input v-model="queryParams.tenantId" placeholder="请输入租户编号" clearable @keyup.enter="handleQuery" />
+            <el-form-item label="品牌编号" prop="tenantId">
+              <el-input v-model="queryParams.tenantId" placeholder="请输入品牌编号" clearable @keyup.enter="handleQuery" />
             </el-form-item>
             <el-form-item label="联系人" prop="contactUserName">
-              <el-input v-model="queryParams.contactUserName" placeholder="请输入联系人" clearable @keyup.enter="handleQuery" />
+              <el-input v-model="queryParams.contactUserName" placeholder="请输入联系人" clearable
+                @keyup.enter="handleQuery" />
             </el-form-item>
             <el-form-item label="联系电话" prop="contactPhone">
               <el-input v-model="queryParams.contactPhone" placeholder="请输入联系电话" clearable @keyup.enter="handleQuery" />
@@ -29,27 +31,29 @@
       <template #header>
         <el-row :gutter="10" class="mb8">
           <el-col :span="1.5">
-            <el-button v-hasPermi="['system:tenant:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
+            <el-button v-hasPermi="['system:tenant:add']" type="primary" plain icon="Plus"
+              @click="handleAdd">新增</el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button v-hasPermi="['system:tenant:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"
-              >修改</el-button
-            >
+            <el-button v-hasPermi="['system:tenant:edit']" type="success" plain icon="Edit" :disabled="single"
+              @click="handleUpdate()">修改</el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button v-hasPermi="['system:tenant:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
+            <el-button v-hasPermi="['system:tenant:remove']" type="danger" plain icon="Delete" :disabled="multiple"
+              @click="handleDelete()">
               删除
             </el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button v-hasPermi="['system:tenant:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
+            <el-button v-hasPermi="['system:tenant:export']" type="warning" plain icon="Download"
+              @click="handleExport">导出</el-button>
           </el-col>
-<!--          <el-col :span="1.5">-->
-<!--            <el-button v-if="userId === 1" type="success" plain icon="Refresh" @click="handleSyncTenantDict">同步租户字典</el-button>-->
-<!--          </el-col>-->
-<!--          <el-col :span="1.5">-->
-<!--            <el-button v-if="userId === 1" type="success" plain icon="Refresh" @click="handleSyncTenantConfig">同步租户参数配置</el-button>-->
-<!--          </el-col>-->
+          <!--          <el-col :span="1.5">-->
+          <!--            <el-button v-if="userId === 1" type="success" plain icon="Refresh" @click="handleSyncTenantDict">同步品牌字典</el-button>-->
+          <!--          </el-col>-->
+          <!--          <el-col :span="1.5">-->
+          <!--            <el-button v-if="userId === 1" type="success" plain icon="Refresh" @click="handleSyncTenantConfig">同步品牌参数配置</el-button>-->
+          <!--          </el-col>-->
           <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
         </el-row>
       </template>
@@ -57,44 +61,56 @@
       <el-table v-loading="loading" border :data="tenantList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column v-if="false" label="id" align="center" prop="id" />
-        <el-table-column label="租户编号" align="center" prop="tenantId" />
-        <el-table-column label="联系人" align="center" prop="contactUserName" />
-        <el-table-column label="联系电话" align="center" prop="contactPhone" />
-        <el-table-column label="企业名称" align="center" prop="companyName" />
-        <el-table-column label="社会信用代码" align="center" prop="licenseNumber" />
-        <el-table-column label="过期时间" align="center" prop="expireTime" width="180">
+        <!--        <el-table-column label="品牌编号" align="center" prop="tenantId" />-->
+        <el-table-column label="LOGO" align="center" prop="logoUrl" width="100">
           <template #default="scope">
-            <span>{{ proxy.parseTime(scope.row.expireTime, '{y}-{m}-{d}') }}</span>
+            <image-preview :src="scope.row.logoUrl" :width="50" :height="50" />
           </template>
         </el-table-column>
-        <el-table-column label="租户状态" align="center" prop="status">
+        <el-table-column label="品牌名称" align="center" prop="companyName" />
+        <el-table-column label="管理员账号" align="center" prop="username" />
+        <el-table-column label="联系人" align="center" prop="contactUserName" />
+        <el-table-column label="联系电话" align="center" prop="contactPhone" />
+        <el-table-column label="总部地址" align="center" prop="address" />
+        <!--        <el-table-column label="社会信用代码" align="center" prop="licenseNumber" />-->
+        <!--        <el-table-column label="过期时间" align="center" prop="expireTime" width="180">-->
+        <!--          <template #default="scope">-->
+        <!--            <span>{{ proxy.parseTime(scope.row.expireTime, '{y}-{m}-{d}') }}</span>-->
+        <!--          </template>-->
+        <!--        </el-table-column>-->
+        <el-table-column label="状态" align="center" prop="status">
           <template #default="scope">
-            <el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
+            <el-switch v-model="scope.row.status" active-value="0" inactive-value="1"
+              @change="handleStatusChange(scope.row)"></el-switch>
           </template>
         </el-table-column>
+        <el-table-column label="品牌简介" align="center" prop="intro" />
         <el-table-column width="150" label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
           <template #default="scope">
             <el-tooltip content="修改" placement="top">
-              <el-button v-hasPermi="['system:tenant:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
-            </el-tooltip>
-            <el-tooltip content="同步套餐" placement="top">
-              <el-button v-hasPermi="['system:tenant:edit']" link type="primary" icon="Refresh" @click="handleSyncTenantPackage(scope.row)">
-              </el-button>
+              <el-button v-hasPermi="['system:tenant:edit']" link type="primary" icon="Edit"
+                @click="handleUpdate(scope.row)"></el-button>
             </el-tooltip>
+            <!--            <el-tooltip content="同步套餐" placement="top">-->
+            <!--              <el-button v-hasPermi="['system:tenant:edit']" link type="primary" icon="Refresh" @click="handleSyncTenantPackage(scope.row)">-->
+            <!--              </el-button>-->
+            <!--            </el-tooltip>-->
             <el-tooltip content="删除" placement="top">
-              <el-button v-hasPermi="['system:tenant:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
+              <el-button v-hasPermi="['system:tenant:remove']" link type="primary" icon="Delete"
+                @click="handleDelete(scope.row)"></el-button>
             </el-tooltip>
           </template>
         </el-table-column>
       </el-table>
 
-      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
+      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
+        :total="total" @pagination="getList" />
     </el-card>
-    <!-- 添加或修改租户对话框 -->
+    <!-- 添加或修改品牌对话框 -->
     <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
-      <el-form ref="tenantFormRef" :model="form" :rules="rules" label-width="80px">
-        <el-form-item label="企业名称" prop="companyName">
-          <el-input v-model="form.companyName" placeholder="请输入企业名称" />
+      <el-form ref="tenantFormRef" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="品牌名称" prop="companyName">
+          <el-input v-model="form.companyName" placeholder="请输入品牌名称" />
         </el-form-item>
         <el-form-item label="联系人" prop="contactUserName">
           <el-input v-model="form.contactUserName" placeholder="请输入联系人" />
@@ -102,39 +118,42 @@
         <el-form-item label="联系电话" prop="contactPhone">
           <el-input v-model="form.contactPhone" placeholder="请输入联系电话" />
         </el-form-item>
-        <el-form-item v-if="!form.id" label="用户名" prop="username">
-          <el-input v-model="form.username" placeholder="请输入系统用户名" maxlength="30" />
-        </el-form-item>
-        <el-form-item v-if="!form.id" label="用户密码" prop="password">
-          <el-input v-model="form.password" type="password" placeholder="请输入系统用户密码" maxlength="20" />
-        </el-form-item>
-        <el-form-item label="租户套餐" prop="packageId">
-          <el-select v-model="form.packageId" :disabled="!!form.tenantId" placeholder="请选择租户套餐" clearable style="width: 100%">
-            <el-option v-for="item in packageList" :key="item.packageId" :label="item.packageName" :value="item.packageId" />
-          </el-select>
-        </el-form-item>
-        <el-form-item label="过期时间" prop="expireTime">
-          <el-date-picker v-model="form.expireTime" clearable type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择过期时间">
-          </el-date-picker>
-        </el-form-item>
-        <el-form-item label="用户数量" prop="accountCount">
-          <el-input v-model="form.accountCount" placeholder="请输入用户数量" />
-        </el-form-item>
-        <el-form-item label="绑定域名" prop="domain">
-          <el-input v-model="form.domain" placeholder="请输入绑定域名" />
+        <el-form-item v-if="!form.id" label="管理员账号" prop="username">
+          <el-input v-model="form.username" placeholder="请输入管理员账号" maxlength="30" />
         </el-form-item>
-        <el-form-item label="企业地址" prop="address">
-          <el-input v-model="form.address" placeholder="请输入企业地址" />
+        <el-form-item v-if="!form.id" label="管理员密码" prop="password">
+          <el-input v-model="form.password" type="password" placeholder="请输入管理员密码" maxlength="20" />
         </el-form-item>
-        <el-form-item label="企业代码" prop="licenseNumber">
-          <el-input v-model="form.licenseNumber" placeholder="请输入统一社会信用代码" />
+        <!--        <el-form-item label="品牌套餐" prop="packageId">-->
+        <!--          <el-select v-model="form.packageId" :disabled="!!form.tenantId" placeholder="请选择品牌套餐" clearable style="width: 100%">-->
+        <!--            <el-option v-for="item in packageList" :key="item.packageId" :label="item.packageName" :value="item.packageId" />-->
+        <!--          </el-select>-->
+        <!--        </el-form-item>-->
+        <!--        <el-form-item label="过期时间" prop="expireTime">-->
+        <!--          <el-date-picker v-model="form.expireTime" clearable type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择过期时间">-->
+        <!--          </el-date-picker>-->
+        <!--        </el-form-item>-->
+        <!--        <el-form-item label="用户数量" prop="accountCount">-->
+        <!--          <el-input v-model="form.accountCount" placeholder="请输入用户数量" />-->
+        <!--        </el-form-item>-->
+        <!--        <el-form-item label="绑定域名" prop="domain">-->
+        <!--          <el-input v-model="form.domain" placeholder="请输入绑定域名" />-->
+        <!--        </el-form-item>-->
+        <el-form-item label="总部地址" prop="address">
+          <el-input v-model="form.address" placeholder="请输入总部地址" />
         </el-form-item>
-        <el-form-item label="企业简介" prop="intro">
-          <el-input v-model="form.intro" type="textarea" placeholder="请输入企业简介" />
+        <!--        <el-form-item label="企业代码" prop="licenseNumber">-->
+        <!--          <el-input v-model="form.licenseNumber" placeholder="请输入统一社会信用代码" />-->
+        <!--        </el-form-item>-->
+        <el-form-item label="简介" prop="intro">
+          <el-input v-model="form.intro" type="textarea" placeholder="请输入简介" />
         </el-form-item>
-        <el-form-item label="备注" prop="remark">
-          <el-input v-model="form.remark" placeholder="请输入备注" />
+        <el-form-item label="LOGO" prop="logo">
+          <image-upload v-model="form.logo" :limit="1" />
         </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">
@@ -201,7 +220,8 @@ const initFormData: TenantForm = {
   packageId: '',
   expireTime: '',
   accountCount: 0,
-  status: '0'
+  status: '0',
+  logo: undefined
 };
 const data = reactive<PageData<TenantForm, TenantQuery>>({
   form: { ...initFormData },
@@ -215,7 +235,7 @@ const data = reactive<PageData<TenantForm, TenantQuery>>({
   },
   rules: {
     id: [{ required: true, message: 'id不能为空', trigger: 'blur' }],
-    tenantId: [{ required: true, message: '租户编号不能为空', trigger: 'blur' }],
+    tenantId: [{ required: true, message: '品牌编号不能为空', trigger: 'blur' }],
     contactUserName: [{ required: true, message: '联系人不能为空', trigger: 'blur' }],
     contactPhone: [{ required: true, message: '联系电话不能为空', trigger: 'blur' }],
     companyName: [{ required: true, message: '企业名称不能为空', trigger: 'blur' }],
@@ -232,13 +252,13 @@ const data = reactive<PageData<TenantForm, TenantQuery>>({
 
 const { queryParams, form, rules } = toRefs(data);
 
-/** 查询所有租户套餐 */
+/** 查询所有品牌套餐 */
 const getTenantPackage = async () => {
   const res = await selectTenantPackage();
   packageList.value = res.data;
 };
 
-/** 查询租户列表 */
+/** 查询品牌列表 */
 const getList = async () => {
   loading.value = true;
   const res = await listTenant(queryParams.value);
@@ -247,11 +267,11 @@ const getList = async () => {
   loading.value = false;
 };
 
-// 租户套餐状态修改
+// 品牌套餐状态修改
 const handleStatusChange = async (row: TenantVO) => {
   const text = row.status === '0' ? '启用' : '停用';
   try {
-    await proxy?.$modal.confirm('确认要"' + text + '""' + row.companyName + '"租户吗?');
+    await proxy?.$modal.confirm('确认要"' + text + '""' + row.companyName + '"品牌吗?');
     await changeTenantStatus(row.id, row.tenantId, row.status);
     proxy?.$modal.msgSuccess(text + '成功');
   } catch {
@@ -295,7 +315,7 @@ const handleAdd = () => {
   reset();
   getTenantPackage();
   dialog.visible = true;
-  dialog.title = '添加租户';
+  dialog.title = '添加品牌';
 };
 
 /** 修改按钮操作 */
@@ -306,7 +326,7 @@ const handleUpdate = async (row?: TenantVO) => {
   const res = await getTenant(_id);
   Object.assign(form.value, res.data);
   dialog.visible = true;
-  dialog.title = '修改租户';
+  dialog.title = '修改品牌';
 };
 
 /** 提交按钮 */
@@ -329,17 +349,17 @@ const submitForm = () => {
 /** 删除按钮操作 */
 const handleDelete = async (row?: TenantVO) => {
   const _ids = row?.id || ids.value;
-  await proxy?.$modal.confirm('是否确认删除租户编号为"' + _ids + '"的数据项?');
+  await proxy?.$modal.confirm('是否确认删除品牌编号为"' + _ids + '"的数据项?');
   loading.value = true;
   await delTenant(_ids).finally(() => (loading.value = false));
   await getList();
   proxy?.$modal.msgSuccess('删除成功');
 };
 
-/** 同步租户套餐按钮操作 */
+/** 同步品牌套餐按钮操作 */
 const handleSyncTenantPackage = async (row: TenantVO) => {
   try {
-    await proxy?.$modal.confirm('是否确认同步租户套餐租户编号为"' + row.tenantId + '"的数据项?');
+    await proxy?.$modal.confirm('是否确认同步品牌套餐品牌编号为"' + row.tenantId + '"的数据项?');
     loading.value = true;
     await syncTenantPackage(row.tenantId, row.packageId);
     await getList();
@@ -362,16 +382,16 @@ const handleExport = () => {
   );
 };
 
-/**同步租户字典*/
+/**同步品牌字典*/
 const handleSyncTenantDict = async () => {
-  await proxy?.$modal.confirm('确认要同步所有租户字典吗?');
+  await proxy?.$modal.confirm('确认要同步所有品牌字典吗?');
   const res = await syncTenantDict();
   proxy?.$modal.msgSuccess(res.msg);
 };
 
-/**同步租户参数配置*/
+/**同步品牌参数配置*/
 const handleSyncTenantConfig = async () => {
-  await proxy?.$modal.confirm('确认要同步所有租户参数配置吗?');
+  await proxy?.$modal.confirm('确认要同步所有品牌参数配置吗?');
   const res = await syncTenantConfig();
   proxy?.$modal.msgSuccess(res.msg);
 };

+ 300 - 0
src/views/system/tenantCategories/index.vue

@@ -0,0 +1,300 @@
+<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 label="分类名称" prop="name">
+              <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-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-form-item>
+            <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: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-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-col>
+          <el-col :span="1.5">
+            <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>
+      </template>
+
+      <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="图标" 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">
+          <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">
+          <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-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-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="tenantCategoriesFormRef" :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="sort">
+          <el-input v-model="form.sort" placeholder="请输入排序" />
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-switch v-model="form.status" />
+        </el-form-item>
+        <el-form-item label="图标" prop="icon">
+          <image-upload v-model="form.icon" :limit="1" />
+        </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="TenantCategories" lang="ts">
+import {
+  listTenantCategories,
+  getTenantCategories,
+  delTenantCategories,
+  addTenantCategories,
+  updateTenantCategories
+} from '@/api/system/tenantCategories';
+import { TenantCategoriesVO, TenantCategoriesQuery, TenantCategoriesForm } from '@/api/system/tenantCategories/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const tenantCategoriesList = ref<TenantCategoriesVO[]>([]);
+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 dateRangeCreateTime = ref<[DateModelType, DateModelType]>(['', '']);
+const dateRangeUpdateTime = ref<[DateModelType, DateModelType]>(['', '']);
+
+const queryFormRef = ref<ElFormInstance>();
+const tenantCategoriesFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: TenantCategoriesForm = {
+  id: undefined,
+  name: undefined,
+  sort: 0,
+  icon: undefined,
+  status: true
+};
+const data = reactive<PageData<TenantCategoriesForm, TenantCategoriesQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    name: undefined,
+    status: undefined,
+    params: {
+      createTime: undefined,
+      updateTime: undefined
+    }
+  },
+  rules: {
+    id: [{ required: true, message: '序号不能为空', trigger: 'blur' }],
+    name: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }],
+    status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询商户分类列表 */
+const getList = async () => {
+  loading.value = true;
+  queryParams.value.params = {};
+  proxy?.addDateRange(queryParams.value, dateRangeCreateTime.value, 'CreateTime');
+  proxy?.addDateRange(queryParams.value, dateRangeUpdateTime.value, 'UpdateTime');
+  const res = await listTenantCategories(queryParams.value);
+  tenantCategoriesList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  tenantCategoriesFormRef.value?.resetFields();
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  dateRangeCreateTime.value = ['', ''];
+  dateRangeUpdateTime.value = ['', ''];
+  queryFormRef.value?.resetFields();
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: TenantCategoriesVO[]) => {
+  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?: TenantCategoriesVO) => {
+  reset();
+  const _id = row?.id || ids.value[0];
+  const res = await getTenantCategories(_id);
+  Object.assign(form.value, res.data);
+  dialog.visible = true;
+  dialog.title = '修改商户分类';
+};
+
+/** 提交按钮 */
+const submitForm = () => {
+  tenantCategoriesFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.id) {
+        await updateTenantCategories(form.value).finally(() => (buttonLoading.value = false));
+      } else {
+        await addTenantCategories(form.value).finally(() => (buttonLoading.value = false));
+      }
+      proxy?.$modal.msgSuccess('操作成功');
+      dialog.visible = false;
+      await getList();
+    }
+  });
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: TenantCategoriesVO) => {
+  const _ids = row?.id || ids.value;
+  await proxy?.$modal.confirm('是否确认删除商户分类编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
+  await delTenantCategories(_ids);
+  proxy?.$modal.msgSuccess('删除成功');
+  await getList();
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'system/tenantCategories/export',
+    {
+      ...queryParams.value
+    },
+    `tenantCategories_${new Date().getTime()}.xlsx`
+  );
+};
+
+onMounted(() => {
+  getList();
+});
+</script>