瀏覽代碼

feat(api): 添加广告管理、banner图、企业信息及第三方设备相关API

- 新增广告管理模块API,包括分页查询、表单获取、创建、更新和删除功能
- 新增小程序banner图管理API,支持图片上传、跳转配置及状态管理
- 新增企业信息管理API,提供企业数据的CURD操作接口
- 新增第三方充电设备及充电站信息API,支持设备和站点数据管理
- 定义各模块对应的表单对象和分页查询参数类型
- 实现统一的请求封装和基础URL配置
zouzexu 4 天之前
父節點
當前提交
166e0797cd

+ 1 - 1
package.json

@@ -1,5 +1,5 @@
 {
-  "name": "vue3-element-admin",
+  "name": "zhongshudiandong",
   "description": "Vue3 + Vite + TypeScript + Element-Plus 的后台管理模板,vue-element-admin 的 Vue3 版本",
   "version": "3.4.2",
   "private": true,

+ 101 - 0
src/api/equipmentManage/third-party-equipment-info-api.ts

@@ -0,0 +1,101 @@
+import request from "@/utils/request";
+
+const THIRDPARTYEQUIPMENTINFO_BASE_URL = "/api/v1/third-party-charging/equipments";
+
+const ThirdPartyEquipmentInfoAPI = {
+  /** 获取第三方充电设备信息分页数据 */
+  getPage(data: ThirdPartyEquipmentInfoForm) {
+    return request<any, PageResult<ThirdPartyEquipmentInfoPageVO[]>>({
+      url: `${THIRDPARTYEQUIPMENTINFO_BASE_URL}/page`,
+      method: "post",
+      data,
+    });
+  },
+  /**
+   * 获取第三方充电设备信息表单数据
+   *
+   * @param id 第三方充电设备信息ID
+   * @returns 第三方充电设备信息表单数据
+   */
+  getFormData(id: number) {
+    return request<any, ThirdPartyEquipmentInfoForm>({
+      url: `${THIRDPARTYEQUIPMENTINFO_BASE_URL}/${id}/form`,
+      method: "get",
+    });
+  },
+
+  /**
+   *  添加第三方充电设备信息
+   *
+   *  @param data 第三方充电设备信息表单数据
+   */
+  create(data: ThirdPartyEquipmentInfoForm) {
+    return request({
+      url: `${THIRDPARTYEQUIPMENTINFO_BASE_URL}`,
+      method: "post",
+      data,
+    });
+  },
+
+  /**
+   * 更新第三方充电设备信息
+   *
+   * @param id 第三方充电设备信息ID
+   * @param data 第三方充电设备信息表单数据
+   */
+  update(id: string, data: ThirdPartyEquipmentInfoForm) {
+    return request({
+      url: `${THIRDPARTYEQUIPMENTINFO_BASE_URL}/${id}`,
+      method: "put",
+      data,
+    });
+  },
+
+  /**
+   * 批量删除第三方充电设备信息,多个以英文逗号(,)分割
+   *
+   * @param ids 第三方充电设备信息ID字符串,多个以英文逗号(,)分割
+   */
+  deleteByIds(ids: string) {
+    return request({
+      url: `${THIRDPARTYEQUIPMENTINFO_BASE_URL}/${ids}`,
+      method: "delete",
+    });
+  },
+};
+
+export default ThirdPartyEquipmentInfoAPI;
+
+/** 第三方充电设备信息分页查询参数 */
+export interface ThirdPartyEquipmentInfoPageQuery extends PageQuery {
+  /** 设备编码 */
+  equipmentId?: string;
+  /** 设备名称 */
+  equipmentName?: string;
+}
+
+/** 第三方充电设备信息表单对象 */
+export interface ThirdPartyEquipmentInfoForm {
+  /** 设备编码 */
+  equipmentId?: string;
+  /** 生产商名称 */
+  manufacturerName?: string;
+  /** 总功率(kW) */
+  power?: number;
+  /** 设备名称 */
+  equipmentName?: string;
+}
+
+/** 第三方充电设备信息分页对象 */
+export interface ThirdPartyEquipmentInfoPageVO {
+  /** 设备编码 */
+  equipmentId?: string;
+  /** 所属充电站ID */
+  stationId?: string;
+  /** 生产商名称 */
+  manufacturerName?: string;
+  /** 总功率(kW) */
+  power?: number;
+  /** 设备名称 */
+  equipmentName?: string;
+}

+ 177 - 0
src/api/equipmentManage/third-party-station-info-api.ts

@@ -0,0 +1,177 @@
+import request from "@/utils/request";
+
+const THIRDPARTYSTATIONINFO_BASE_URL = "/api/v1/third-party-charging/stations";
+
+const ThirdPartyStationInfoAPI = {
+  /** 获取第三方充电站信息分页数据 */
+  getPage(data: ThirdPartyStationInfoForm) {
+    return request<any, PageResult<ThirdPartyStationInfoPageVO[]>>({
+      url: `${THIRDPARTYSTATIONINFO_BASE_URL}/page`,
+      method: "post",
+      data,
+    });
+  },
+  /**
+   * 获取第三方充电站信息表单数据
+   *
+   * @param id 第三方充电站信息ID
+   * @returns 第三方充电站信息表单数据
+   */
+  getFormData(id: number) {
+    return request<any, ThirdPartyStationInfoForm>({
+      url: `${THIRDPARTYSTATIONINFO_BASE_URL}/${id}/form`,
+      method: "get",
+    });
+  },
+
+  /**
+   *  添加第三方充电站信息
+   *
+   *  @param data 第三方充电站信息表单数据
+   */
+  create(data: ThirdPartyStationInfoForm) {
+    return request({
+      url: `${THIRDPARTYSTATIONINFO_BASE_URL}`,
+      method: "post",
+      data,
+    });
+  },
+
+  /**
+   * 更新第三方充电站信息
+   *
+   * @param id 第三方充电站信息ID
+   * @param data 第三方充电站信息表单数据
+   */
+  update(id: string, data: ThirdPartyStationInfoForm) {
+    return request({
+      url: `${THIRDPARTYSTATIONINFO_BASE_URL}/${id}`,
+      method: "put",
+      data,
+    });
+  },
+
+  /**
+   * 批量删除第三方充电站信息,多个以英文逗号(,)分割
+   *
+   * @param ids 第三方充电站信息ID字符串,多个以英文逗号(,)分割
+   */
+  deleteByIds(ids: string) {
+    return request({
+      url: `${THIRDPARTYSTATIONINFO_BASE_URL}/${ids}`,
+      method: "delete",
+    });
+  },
+};
+
+export default ThirdPartyStationInfoAPI;
+
+/** 第三方充电站信息分页查询参数 */
+export interface ThirdPartyStationInfoPageQuery extends PageQuery {
+  /** 充电站ID */
+  stationId?: string;
+  /** 充电站名称 */
+  stationName?: string;
+  /** 站点状态 */
+  stationStatus?: number;
+}
+
+/** 第三方充电站信息表单对象 */
+export interface ThirdPartyStationInfoForm {
+  /** 经度 */
+  stationLng?: number;
+  /** 纬度 */
+  stationLat?: number;
+  /** 站点引导 */
+  siteGuide?: string;
+  /** 建设场所 */
+  construction?: number;
+  /** 站点照片(JSON数组) */
+  pictures?: string;
+  /** 营业时间 */
+  busineHours?: string;
+  /** 电费 */
+  electricityFee?: number;
+  /** 服务费 */
+  serviceFee?: number;
+  /** 停车费 */
+  parkFee?: string;
+  /** 支付方式 */
+  payment?: string;
+  /** 是否支持预约 */
+  supportOrder?: number;
+  /** 备注 */
+  remark?: string;
+  /** 创建时间 */
+  createTime?: Date;
+  /** 创建人ID */
+  createBy?: number;
+  /** 更新时间 */
+  updateTime?: Date;
+  /** 更新人ID */
+  updateBy?: number;
+  /** 逻辑删除(0-未删除 1-已删除) */
+  isDeleted?: number;
+}
+
+/** 第三方充电站信息分页对象 */
+export interface ThirdPartyStationInfoPageVO {
+  /** 充电站ID */
+  stationId?: string;
+  /** 运营商ID */
+  operatorId?: string;
+  /** 设备所属方ID */
+  equipmentOwnerId?: string;
+  /** 充电站名称 */
+  stationName?: string;
+  /** 国家代码 */
+  countryCode?: string;
+  /** 省市辖区编码 */
+  areaCode?: string;
+  /** 详细地址 */
+  address?: string;
+  /** 站点电话 */
+  stationTel?: string;
+  /** 服务电话 */
+  serviceTel?: string;
+  /** 站点类型 */
+  stationType?: number;
+  /** 站点状态 */
+  stationStatus?: number;
+  /** 车位数量 */
+  parkNums?: number;
+  /** 经度 */
+  stationLng?: number;
+  /** 纬度 */
+  stationLat?: number;
+  /** 站点引导 */
+  siteGuide?: string;
+  /** 建设场所 */
+  construction?: number;
+  /** 站点照片(JSON数组) */
+  pictures?: string;
+  /** 营业时间 */
+  busineHours?: string;
+  /** 电费 */
+  electricityFee?: number;
+  /** 服务费 */
+  serviceFee?: number;
+  /** 停车费 */
+  parkFee?: string;
+  /** 支付方式 */
+  payment?: string;
+  /** 是否支持预约 */
+  supportOrder?: number;
+  /** 备注 */
+  remark?: string;
+  /** 创建时间 */
+  createTime?: Date;
+  /** 创建人ID */
+  createBy?: number;
+  /** 更新时间 */
+  updateTime?: Date;
+  /** 更新人ID */
+  updateBy?: number;
+  /** 逻辑删除(0-未删除 1-已删除) */
+  isDeleted?: number;
+}

+ 115 - 0
src/api/operationsManage/advertising-api.ts

@@ -0,0 +1,115 @@
+import request from "@/utils/request";
+
+const ADVERTISING_BASE_URL = "/api/v1/advertising";
+
+const AdvertisingAPI = {
+    /** 获取广告管理分页数据 */
+    getPage(queryParams?: AdvertisingPageQuery) {
+        return request<any, PageResult<AdvertisingPageVO[]>>({
+            url: `${ADVERTISING_BASE_URL}/page`,
+            method: "get",
+            params: queryParams,
+        });
+    },
+    /**
+     * 获取广告管理表单数据
+     *
+     * @param id 广告管理ID
+     * @returns 广告管理表单数据
+     */
+    getFormData(id: number) {
+        return request<any, AdvertisingForm>({
+            url: `${ADVERTISING_BASE_URL}/${id}/form`,
+            method: "get",
+        });
+    },
+
+    /**
+     *  添加广告管理
+     *
+     *  @param data 广告管理表单数据
+     */
+    create(data: AdvertisingForm) {
+        return request({
+            url: `${ADVERTISING_BASE_URL}`,
+            method: "post",
+            data,
+        });
+    },
+
+    /**
+     * 更新广告管理
+     *
+     * @param id 广告管理ID
+     * @param data 广告管理表单数据
+     */
+     update(id: string, data: AdvertisingForm) {
+        return request({
+            url: `${ADVERTISING_BASE_URL}/${id}`,
+            method: "put",
+            data,
+        });
+    },
+
+    /**
+     * 批量删除广告管理,多个以英文逗号(,)分割
+     *
+     * @param ids 广告管理ID字符串,多个以英文逗号(,)分割
+     */
+     deleteByIds(ids: string) {
+        return request({
+            url: `${ADVERTISING_BASE_URL}/${ids}`,
+            method: "delete",
+        });
+    }
+}
+
+export default AdvertisingAPI;
+
+/** 广告管理分页查询参数 */
+export interface AdvertisingPageQuery extends PageQuery {
+}
+
+/** 广告管理表单对象 */
+export interface AdvertisingForm {
+    /** 主键 */
+    id?:  number;
+    /** 名称 */
+    name?:  string;
+    /** 状态 1.上线 2.下线 */
+    status?:  number;
+    /** 排序 */
+    sort?:  number;
+    /** 广告位置  1.首页弹框 */
+    advertisingPosition?:  number;
+    /** 图片 */
+    picture?:  string;
+    /** 跳转路径 */
+    skipUrl?:  string;
+    /** 创建时间 */
+    createTime?:  Date;
+    /** 更新时间 */
+    updateTime?:  Date;
+}
+
+/** 广告管理分页对象 */
+export interface AdvertisingPageVO {
+    /** 主键 */
+    id?: number;
+    /** 名称 */
+    name?: string;
+    /** 状态 1.上线 2.下线 */
+    status?: number;
+    /** 排序 */
+    sort?: number;
+    /** 广告位置  1.首页弹框 */
+    advertisingPosition?: number;
+    /** 图片 */
+    picture?: string;
+    /** 跳转路径 */
+    skipUrl?: string;
+    /** 创建时间 */
+    createTime?: Date;
+    /** 更新时间 */
+    updateTime?: Date;
+}

+ 111 - 0
src/api/operationsManage/banner-info-api.ts

@@ -0,0 +1,111 @@
+import request from "@/utils/request";
+
+const BANNERINFO_BASE_URL = "/api/v1/bannerInfo";
+
+const BannerInfoAPI = {
+    /** 获取小程序banner图分页数据 */
+    getPage(queryParams?: BannerInfoPageQuery) {
+        return request<any, PageResult<BannerInfoPageVO[]>>({
+            url: `${BANNERINFO_BASE_URL}/page`,
+            method: "get",
+            params: queryParams,
+        });
+    },
+    /**
+     * 获取小程序banner图表单数据
+     *
+     * @param id 小程序banner图ID
+     * @returns 小程序banner图表单数据
+     */
+    getFormData(id: number) {
+        return request<any, BannerInfoForm>({
+            url: `${BANNERINFO_BASE_URL}/${id}/form`,
+            method: "get",
+        });
+    },
+
+    /**
+     *  添加小程序banner图
+     *
+     *  @param data 小程序banner图表单数据
+     */
+    create(data: BannerInfoForm) {
+        return request({
+            url: `${BANNERINFO_BASE_URL}`,
+            method: "post",
+            data,
+        });
+    },
+
+    /**
+     * 更新小程序banner图
+     *
+     * @param id 小程序banner图ID
+     * @param data 小程序banner图表单数据
+     */
+     update(id: string, data: BannerInfoForm) {
+        return request({
+            url: `${BANNERINFO_BASE_URL}/${id}`,
+            method: "put",
+            data,
+        });
+    },
+
+    /**
+     * 批量删除小程序banner图,多个以英文逗号(,)分割
+     *
+     * @param ids 小程序banner图ID字符串,多个以英文逗号(,)分割
+     */
+     deleteByIds(ids: string) {
+        return request({
+            url: `${BANNERINFO_BASE_URL}/${ids}`,
+            method: "delete",
+        });
+    }
+}
+
+export default BannerInfoAPI;
+
+/** 小程序banner图分页查询参数 */
+export interface BannerInfoPageQuery extends PageQuery {
+}
+
+/** 小程序banner图表单对象 */
+export interface BannerInfoForm {
+    /** 图片 */
+    picture?:  string;
+    /** 是否跳转(0-不跳转 1-跳转) */
+    orJump?:  number;
+    /** 跳转小程序appid */
+    jumpAppid?:  string;
+    /** 跳转页面 */
+    jumpPage?:  string;
+    /** 排序 越大越靠前 */
+    sort?:  number;
+    /** 状态 (0-无效 1-生效) */
+    status?:  number;
+}
+
+/** 小程序banner图分页对象 */
+export interface BannerInfoPageVO {
+    /** 图片 */
+    picture?: string;
+    /** 是否跳转(0-不跳转 1-跳转) */
+    orJump?: number;
+    /** 跳转小程序appid */
+    jumpAppid?: string;
+    /** 跳转页面 */
+    jumpPage?: string;
+    /** 排序 越大越靠前 */
+    sort?: number;
+    /** 状态 (0-无效 1-生效) */
+    status?: number;
+    /** 创建时间 */
+    createTime?: string;
+    /** 创建人 */
+    createBy?: number;
+    /** 更新时间 */
+    updateTime?: string;
+    /** 更新人 */
+    updateBy?: number;
+}

+ 95 - 0
src/api/toBManage/firm-info-api.ts

@@ -0,0 +1,95 @@
+import request from "@/utils/request";
+
+const FIRMINFO_BASE_URL = "/api/v1/firmInfo";
+
+const FirmInfoAPI = {
+  /** 获取企业信息分页数据 */
+  getPage(queryParams?: FirmInfoPageQuery) {
+    return request<any, PageResult<FirmInfoPageVO[]>>({
+      url: `${FIRMINFO_BASE_URL}/page`,
+      method: "get",
+      params: queryParams,
+    });
+  },
+  /**
+   * 获取企业信息表单数据
+   *
+   * @param id 企业信息ID
+   * @returns 企业信息表单数据
+   */
+  getFormData(id: number) {
+    return request<any, FirmInfoForm>({
+      url: `${FIRMINFO_BASE_URL}/${id}/form`,
+      method: "get",
+    });
+  },
+
+  /**
+   *  添加企业信息
+   *
+   *  @param data 企业信息表单数据
+   */
+  create(data: FirmInfoForm) {
+    return request({
+      url: `${FIRMINFO_BASE_URL}`,
+      method: "post",
+      data,
+    });
+  },
+
+  /**
+   * 更新企业信息
+   *
+   * @param id 企业信息ID
+   * @param data 企业信息表单数据
+   */
+  update(id: string, data: FirmInfoForm) {
+    return request({
+      url: `${FIRMINFO_BASE_URL}/${id}`,
+      method: "put",
+      data,
+    });
+  },
+
+  /**
+   * 批量删除企业信息,多个以英文逗号(,)分割
+   *
+   * @param ids 企业信息ID字符串,多个以英文逗号(,)分割
+   */
+  deleteByIds(ids: string) {
+    return request({
+      url: `${FIRMINFO_BASE_URL}/${ids}`,
+      method: "delete",
+    });
+  },
+};
+
+export default FirmInfoAPI;
+
+/** 企业信息分页查询参数 */
+export interface FirmInfoPageQuery extends PageQuery {
+  /** B端企业名称 */
+  name?: string;
+  /** 状态 0 已下线  1 上线中 */
+  status?: number;
+}
+
+/** 企业信息表单对象 */
+export interface FirmInfoForm {
+  /** 部门id */
+  deptId?: number;
+  /** B端企业名称 */
+  name?: string;
+  /** 状态 0 已下线  1 上线中 */
+  status?: number;
+}
+
+/** 企业信息分页对象 */
+export interface FirmInfoPageVO {
+  /** B端企业名称 */
+  name?: string;
+  /** 状态 0 已下线  1 上线中 */
+  status?: number;
+  /** 创建时间 */
+  createTime?: Date;
+}

+ 112 - 0
src/api/userManage/user-feedback-api.ts

@@ -0,0 +1,112 @@
+import request from "@/utils/request";
+
+const USERFEEDBACK_BASE_URL = "/api/v1/userFeedback";
+
+const UserFeedbackAPI = {
+  /** 获取用户反馈分页数据 */
+  getPage(queryParams?: UserFeedbackPageQuery) {
+    return request<any, PageResult<UserFeedbackPageVO[]>>({
+      url: `${USERFEEDBACK_BASE_URL}/page`,
+      method: "get",
+      params: queryParams,
+    });
+  },
+  /**
+   * 获取用户反馈表单数据
+   *
+   * @param id 用户反馈ID
+   * @returns 用户反馈表单数据
+   */
+  getFormData(id: number) {
+    return request<any, UserFeedbackForm>({
+      url: `${USERFEEDBACK_BASE_URL}/${id}/form`,
+      method: "get",
+    });
+  },
+
+  /**
+   *  添加用户反馈
+   *
+   *  @param data 用户反馈表单数据
+   */
+  create(data: UserFeedbackForm) {
+    return request({
+      url: `${USERFEEDBACK_BASE_URL}`,
+      method: "post",
+      data,
+    });
+  },
+
+  /**
+   * 更新用户反馈
+   *
+   * @param id 用户反馈ID
+   * @param data 用户反馈表单数据
+   */
+  update(id: string, reply: string, data: UserFeedbackForm) {
+    return request({
+      url: `${USERFEEDBACK_BASE_URL}/${id}/${reply}`,
+      method: "put",
+      data,
+    });
+  },
+
+  /**
+   * 批量删除用户反馈,多个以英文逗号(,)分割
+   *
+   * @param ids 用户反馈ID字符串,多个以英文逗号(,)分割
+   */
+  deleteByIds(ids: string) {
+    return request({
+      url: `${USERFEEDBACK_BASE_URL}/${ids}`,
+      method: "delete",
+    });
+  },
+};
+
+export default UserFeedbackAPI;
+
+/** 用户反馈分页查询参数 */
+export interface UserFeedbackPageQuery extends PageQuery {}
+
+/** 用户反馈表单对象 */
+export interface UserFeedbackForm {
+  /** 问题类型(1投诉吐槽,2功能异常,3体验问题,4功能建议,9其他 ) */
+  type?: number;
+  /** 问题描述 */
+  describe?: string;
+  /** 相关图片 */
+  images?: string;
+  /** 联系方式(手机号或者邮箱) */
+  contactWay?: string;
+  /** 答复信息 */
+  reply?: string;
+  /** 用户编号 */
+  userId?: number;
+  /** 回复时间 */
+  replyTime?: string;
+  /** 0未回复,1已回复 */
+  replyStatus?: number;
+}
+
+/** 用户反馈分页对象 */
+export interface UserFeedbackPageVO {
+  /** 问题类型(1投诉吐槽,2功能异常,3体验问题,4功能建议,9其他 ) */
+  type?: number;
+  /** 问题描述 */
+  describe?: string;
+  /** 相关图片 */
+  images?: string;
+  /** 联系方式(手机号或者邮箱) */
+  contactWay?: string;
+  /** 答复信息 */
+  reply?: string;
+  /** 用户编号 */
+  userId?: number;
+  /** 回复时间 */
+  replyTime?: string;
+  /** 0未回复,1已回复 */
+  replyStatus?: number;
+  /** 创建时间 */
+  createTime?: Date;
+}

+ 5 - 4
src/components/CURD/PageContent.vue

@@ -60,8 +60,8 @@
                 <template v-if="Array.isArray(scope.row[col.prop])">
                   <template v-for="(item, index) in scope.row[col.prop]" :key="item">
                     <el-image
-                      :src="item"
-                      :preview-src-list="scope.row[col.prop]"
+                      :src="getFullImageUrl(item)"
+                      :preview-src-list="scope.row[col.prop].map((url: string) => getFullImageUrl(url))"
                       :initial-index="index"
                       :preview-teleported="true"
                       :style="`width: ${col.imageWidth ?? 40}px; height: ${col.imageHeight ?? 40}px`"
@@ -70,8 +70,8 @@
                 </template>
                 <template v-else>
                   <el-image
-                    :src="scope.row[col.prop]"
-                    :preview-src-list="[scope.row[col.prop]]"
+                    :src="getFullImageUrl(scope.row[col.prop])"
+                    :preview-src-list="[getFullImageUrl(scope.row[col.prop])]"
                     :preview-teleported="true"
                     :style="`width: ${col.imageWidth ?? 40}px; height: ${col.imageHeight ?? 40}px`"
                   />
@@ -324,6 +324,7 @@
 <script setup lang="ts">
 import { hasPerm } from "@/utils/auth";
 import { useDateFormat, useThrottleFn } from "@vueuse/core";
+import { getFullImageUrl } from "@/utils";
 import {
   genFileId,
   type FormInstance,

+ 4 - 3
src/components/Upload/MultiImageUpload.vue

@@ -15,7 +15,7 @@
     <el-icon><Plus /></el-icon>
     <template #file="{ file }">
       <div style="width: 100%">
-        <img class="el-upload-list__item-thumbnail" :src="file.url" />
+        <img class="el-upload-list__item-thumbnail" :src="getFullImageUrl(file.url)" />
         <span class="el-upload-list__item-actions">
           <!-- 预览 -->
           <span @click="handlePreviewImage(file.url!)">
@@ -34,13 +34,14 @@
     v-if="previewVisible"
     :zoom-rate="1.2"
     :initial-index="previewImageIndex"
-    :url-list="modelValue"
+    :url-list="modelValue.map((url) => getFullImageUrl(url))"
     @close="handlePreviewClose"
   />
 </template>
 <script setup lang="ts">
 import { UploadRawFile, UploadRequestOptions, UploadUserFile } from "element-plus";
 import FileAPI, { FileInfo } from "@/api/file-api";
+import { getFullImageUrl } from "@/utils";
 
 const props = defineProps({
   /**
@@ -209,7 +210,7 @@ const handlePreviewClose = () => {
 };
 
 onMounted(() => {
-  fileList.value = modelValue.value.map((url) => ({ url }) as UploadUserFile);
+  fileList.value = modelValue.value.map((url) => ({ url: getFullImageUrl(url) }) as UploadUserFile);
 });
 </script>
 <style lang="scss" scoped></style>

+ 16 - 3
src/components/Upload/SingleImageUpload.vue

@@ -9,16 +9,17 @@
     :http-request="handleUpload"
     :on-success="onSuccess"
     :on-error="onError"
+    :disabled="props.disabled"
   >
     <template #default>
       <template v-if="modelValue">
         <el-image
           class="single-upload__image"
-          :src="modelValue"
-          :preview-src-list="[modelValue]"
+          :src="displayUrl"
+          :preview-src-list="[displayUrl]"
           @click.stop="handlePreview"
         />
-        <el-icon class="single-upload__delete-btn" @click.stop="handleDelete">
+        <el-icon v-if="!props.disabled" class="single-upload__delete-btn" @click.stop="handleDelete">
           <CircleCloseFilled />
         </el-icon>
       </template>
@@ -34,6 +35,7 @@
 <script setup lang="ts">
 import { UploadRawFile, UploadRequestOptions } from "element-plus";
 import FileAPI, { FileInfo } from "@/api/file-api";
+import { getFullImageUrl } from "@/utils";
 
 const props = defineProps({
   /**
@@ -80,6 +82,14 @@ const props = defineProps({
       };
     },
   },
+
+  /**
+   * 是否禁用上传
+   */
+  disabled: {
+    type: Boolean,
+    default: false,
+  },
 });
 
 const modelValue = defineModel("modelValue", {
@@ -87,6 +97,9 @@ const modelValue = defineModel("modelValue", {
   default: () => "",
 });
 
+// 计算完整的图片URL(用于显示)
+const displayUrl = computed(() => getFullImageUrl(modelValue.value));
+
 /**
  * 限制用户上传文件的格式和大小
  */

+ 4 - 0
src/constants/index.ts

@@ -34,6 +34,10 @@ export const STORAGE_KEYS = {
 
 export const ROLE_ROOT = "ROOT"; // 超级管理员角色
 
+// 图片URL前缀
+// export const IMAGE_BASE_URL = "http://192.168.0.11:8989";
+export const IMAGE_BASE_URL = "http://47.109.18.141:8989/home/zsElectric-boot/picture";
+
 // 分组键集合(便于批量操作)
 export const AUTH_KEYS = {
   ACCESS_TOKEN: STORAGE_KEYS.ACCESS_TOKEN,

+ 24 - 0
src/utils/index.ts

@@ -1,3 +1,5 @@
+import { IMAGE_BASE_URL } from "@/constants";
+
 /**
  * Check if an element has a class
  * @param {HTMLElement} ele
@@ -56,3 +58,25 @@ export function formatGrowthRate(growthRate: number) {
     .replace(/\.?0+$/, "");
   return formattedRate + "%";
 }
+
+/**
+ * 获取完整图片URL
+ * 如果是相对路径,则拼接前缀;如果已经是完整URL,则直接返回
+ *
+ * @param {string | undefined} url - 图片路径
+ * @returns {string} 完整的图片URL
+ */
+export function getFullImageUrl(url: string | undefined): string {
+  if (!url) return "";
+  // 如果已经是完整的URL(http://或https://),直接返回
+  if (url.startsWith("http://") || url.startsWith("https://")) {
+    return url;
+  }
+  // 将反斜杠转换为正斜杠,并确保路径以斜杠开头
+  let normalizedUrl = url.replace(/\\/g, "/");
+  if (!normalizedUrl.startsWith("/")) {
+    normalizedUrl = "/" + normalizedUrl;
+  }
+  const fullUrl = IMAGE_BASE_URL + normalizedUrl;
+  return fullUrl;
+}

+ 1 - 31
src/views/login/components/Login.vue

@@ -78,36 +78,6 @@
         </el-button>
       </el-form-item>
     </el-form>
-
-    <div flex-center gap-10px>
-      <el-text size="default">{{ t("login.noAccount") }}</el-text>
-      <el-link type="primary" underline="never" @click="toOtherForm('register')">
-        {{ t("login.reg") }}
-      </el-link>
-    </div>
-
-    <!-- 第三方登录 -->
-    <div class="third-party-login">
-      <div class="divider-container">
-        <div class="divider-line"></div>
-        <span class="divider-text">{{ t("login.otherLoginMethods") }}</span>
-        <div class="divider-line"></div>
-      </div>
-      <div class="flex-center gap-x-5 w-full text-[var(--el-text-color-secondary)]">
-        <CommonWrapper>
-          <div text-20px class="i-svg:wechat" />
-        </CommonWrapper>
-        <CommonWrapper>
-          <div text-20px cursor-pointer class="i-svg:qq" />
-        </CommonWrapper>
-        <CommonWrapper>
-          <div text-20px cursor-pointer class="i-svg:github" />
-        </CommonWrapper>
-        <CommonWrapper>
-          <div text-20px cursor-pointer class="i-svg:gitee" />
-        </CommonWrapper>
-      </div>
-    </div>
   </div>
 </template>
 <script setup lang="ts">
@@ -134,7 +104,7 @@ const captchaBase64 = ref();
 const rememberMe = AuthStorage.getRememberMe();
 
 const loginFormData = ref<LoginFormData>({
-  username: "admin",
+  username: "root",
   password: "123456",
   captchaKey: "",
   captchaCode: "",

+ 2 - 2
src/views/login/index.vue

@@ -24,9 +24,9 @@
 
           <!-- 标题 -->
           <h2>
-            <el-badge :value="`v ${defaultSettings.version}`" type="success">
+<!--            <el-badge :value="`v ${defaultSettings.version}`" type="success">-->
               {{ defaultSettings.title }}
-            </el-badge>
+<!--            </el-badge>-->
           </h2>
 
           <!-- 组件切换 -->

+ 362 - 0
src/views/operationsManage/banner-info/index.vue

@@ -0,0 +1,362 @@
+<template>
+  <div class="app-container h-full flex flex-1 flex-col">
+    <!-- 搜索 -->
+    <page-search
+      ref="searchRef"
+      :search-config="searchConfig"
+      @query-click="handleQueryClick"
+      @reset-click="handleResetClick"
+    ></page-search>
+
+    <!-- 列表 -->
+    <page-content
+      ref="contentRef"
+      :content-config="contentConfig"
+      @add-click="handleAddClick"
+      @export-click="handleExportClick"
+      @search-click="handleSearchClick"
+      @toolbar-click="handleToolbarClick"
+      @operate-click="handleOperateClick"
+      @filter-change="handleFilterChange"
+    >
+      <template #orJump="scope">
+        <el-tag :type="scope.row.orJump == 1 ? 'success' : 'warning'">
+          {{ scope.row.orJump == 1 ? "跳转" : "不跳转" }}
+        </el-tag>
+      </template>
+      <template #status="scope">
+        <el-tag :type="scope.row.status == 1 ? 'success' : 'warning'">
+          {{ scope.row.status == 1 ? "启用" : "禁用" }}
+        </el-tag>
+      </template>
+    </page-content>
+
+    <!-- 新增 -->
+    <page-modal ref="addModalRef" :modal-config="addModalConfig" @submit-click="handleSubmitClick">
+      <template #picture="{ formData }">
+        <SingleImageUpload v-model="formData.picture" />
+      </template>
+      <template #orJump="{ formData }">
+        <el-radio-group
+          v-model="formData.orJump"
+          @change="handleOrJumpChange(addModalConfig.formItems, formData.orJump)"
+        >
+          <el-radio :value="0">不跳转</el-radio>
+          <el-radio :value="1">跳转</el-radio>
+        </el-radio-group>
+      </template>
+    </page-modal>
+
+    <!-- 编辑 -->
+    <page-modal
+      ref="editModalRef"
+      :modal-config="editModalConfig"
+      @submit-click="handleSubmitClick"
+    >
+      <template #picture="{ formData }">
+        <SingleImageUpload v-model="formData.picture" />
+      </template>
+      <template #orJump="{ formData }">
+        <el-radio-group
+          v-model="formData.orJump"
+          @change="handleOrJumpChange(editModalConfig.formItems, formData.orJump)"
+        >
+          <el-radio :value="0">不跳转</el-radio>
+          <el-radio :value="1">跳转</el-radio>
+        </el-radio-group>
+      </template>
+    </page-modal>
+  </div>
+</template>
+
+<script setup lang="ts">
+import SingleImageUpload from "@/components/Upload/SingleImageUpload.vue";
+
+defineOptions({ name: "BannerInfo" });
+
+import BannerInfoAPI, {
+  BannerInfoForm,
+  BannerInfoPageQuery,
+} from "@/api/operationsManage/banner-info-api";
+import type { IObject, IModalConfig, IContentConfig, ISearchConfig } from "@/components/CURD/types";
+import usePage from "@/components/CURD/usePage";
+import { IMAGE_BASE_URL } from "@/constants";
+// 组合式 CRUD
+const {
+  searchRef,
+  contentRef,
+  addModalRef,
+  editModalRef,
+  handleQueryClick,
+  handleResetClick,
+  handleAddClick,
+  handleEditClick,
+  handleSubmitClick,
+  handleExportClick,
+  handleSearchClick,
+  handleFilterChange,
+} = usePage();
+
+// 搜索配置
+const searchConfig: ISearchConfig = reactive({
+  permPrefix: "system:banner-info",
+  formItems: [
+    {
+      type: "select",
+      label: "状态",
+      prop: "status",
+      attrs: {
+        placeholder: "请选择状态",
+        clearable: true,
+        style: { width: "200px" },
+      },
+      options: [
+        { label: "禁用", value: 0 },
+        { label: "启用", value: 1 },
+        { label: "其他", value: 2 },
+      ],
+    },
+  ],
+});
+
+// 列表配置
+const contentConfig: IContentConfig<BannerInfoPageQuery> = reactive({
+  // 权限前缀
+  permPrefix: "system:banner-info",
+  table: {
+    border: true,
+    highlightCurrentRow: true,
+  },
+  // 主键
+  pk: "id",
+  // 列表查询接口
+  indexAction: BannerInfoAPI.getPage,
+  // 删除接口
+  deleteAction: BannerInfoAPI.deleteByIds,
+  // 数据解析函数
+  parseData(res: any) {
+    // console.log("列表数据", res);
+    // console.log("IMAGE_BASE_URL:", IMAGE_BASE_URL);
+    return {
+      total: res.total,
+      list: res.list,
+    };
+  },
+  // 分页配置
+  pagination: {
+    background: true,
+    layout: "total, sizes, prev, pager, next, jumper",
+    pageSize: 20,
+    pageSizes: [10, 20, 30, 50],
+  },
+  // 工具栏配置
+  toolbar: ["add", "delete"],
+  defaultToolbar: ["refresh", "filter"],
+  // 表格列配置
+  cols: [
+    { type: "selection", width: 55, align: "center" },
+    {
+      label: "图片",
+      prop: "picture",
+      width: 120,
+      templet: "image",
+      imageWidth: 80,
+      imageHeight: 60,
+    },
+    {
+      label: "是否跳转",
+      prop: "orJump",
+      width: 100,
+      align: "center",
+      templet: "custom",
+    },
+    {
+      label: "跳转小程序appid",
+      prop: "jumpAppid",
+      minWidth: 150,
+      templet: "format",
+      formatter: (value: string) => {
+        return value.jumpAppid || '无';
+      },
+    },
+    {
+      label: "跳转页面",
+      prop: "jumpPage",
+      minWidth: 200,
+      templet: "format",
+      formatter: (value: string) => {
+        return value.jumpPage || '无';
+      },
+    },
+    {
+      label: "排序",
+      prop: "sort",
+      width: 80,
+      align: "center",
+    },
+    {
+      label: "状态",
+      prop: "status",
+      width: 100,
+      align: "center",
+      templet: "custom",
+    },
+    {
+      label: "创建时间",
+      prop: "createTime",
+      width: 160,
+    },
+    {
+      label: "更新时间",
+      prop: "updateTime",
+      width: 160,
+    },
+    {
+      label: "操作",
+      prop: "operation",
+      width: 150,
+      fixed: "right",
+      templet: "tool",
+      operat: ["edit", "delete"],
+    },
+  ],
+});
+
+// 新增配置
+const addModalConfig: IModalConfig<BannerInfoForm> = reactive({
+  // 权限前缀
+  permPrefix: "system:banner-info",
+  // 主键
+  pk: "id",
+  // 弹窗配置
+  dialog: {
+    title: "新增",
+    width: 800,
+    draggable: true,
+  },
+  form: {
+    labelWidth: 100,
+  },
+  // 表单项配置
+  formItems: [
+    {
+      type: "custom",
+      label: "图片",
+      prop: "picture",
+      slotName: "picture",
+      rules: [{ required: true, message: "请上传图片", trigger: "change" }],
+    },
+    {
+      type: "custom",
+      label: "是否跳转",
+      prop: "orJump",
+      slotName: "orJump",
+      initialValue: 0,
+      rules: [{ required: true, message: "请选择是否跳转", trigger: "change" }],
+    },
+    {
+      type: "input",
+      attrs: {
+        placeholder: "请输入跳转小程序appid",
+        clearable: true,
+      },
+      label: "跳转小程序appid",
+      prop: "jumpAppid",
+      hidden: true,
+    },
+    {
+      type: "input",
+      attrs: {
+        placeholder: "请输入跳转页面路径",
+        clearable: true,
+      },
+      label: "跳转页面",
+      prop: "jumpPage",
+      hidden: true,
+    },
+    {
+      type: "input-number",
+      attrs: {
+        placeholder: "请输入排序值",
+        min: 0,
+        max: 9999,
+        controlsPosition: "right",
+        style: { width: "100%" },
+      },
+      label: "排序",
+      prop: "sort",
+      initialValue: 0,
+      tips: "数值越大越靠前",
+      rules: [{ required: true, message: "请输入排序值", trigger: "blur" }],
+    },
+    {
+      type: "switch",
+      attrs: {
+        activeText: "生效",
+        inactiveText: "无效",
+        activeValue: 1,
+        inactiveValue: 0,
+      },
+      initialValue: 1,
+      label: "状态",
+      prop: "status",
+    },
+  ],
+  // 提交函数
+  formAction: (data: BannerInfoForm) => {
+    if (data.id) {
+      // 编辑
+      return BannerInfoAPI.update(data.id as string, data);
+    } else {
+      // 新增
+      return BannerInfoAPI.create(data);
+    }
+  },
+});
+
+// 编辑配置
+const editModalConfig: IModalConfig<BannerInfoForm> = reactive({
+  permPrefix: "system:banner-info",
+  component: "drawer",
+  drawer: {
+    title: "编辑",
+    size: 500,
+  },
+  pk: "id",
+  formAction(data: any) {
+    return BannerInfoAPI.update(data.id as string, data);
+  },
+  formItems: addModalConfig.formItems, // 复用新增的表单项
+});
+
+// 处理操作按钮点击
+const handleOperateClick = (data: IObject) => {
+  if (data.name === "edit") {
+    handleEditClick(data.row, async () => {
+      const formData = await BannerInfoAPI.getFormData(data.row.id);
+      // 编辑时根据 orJump 的值设置显示/隐藏
+      nextTick(() => {
+        handleOrJumpChange(editModalConfig.formItems, formData.orJump);
+      });
+      return formData;
+    });
+  }
+};
+
+// 处理是否跳转变化
+const handleOrJumpChange = (formItems: any[], value: number) => {
+  const jumpAppidItem = formItems.find((item) => item.prop === "jumpAppid");
+  const jumpPageItem = formItems.find((item) => item.prop === "jumpPage");
+
+  if (jumpAppidItem && jumpPageItem) {
+    // 当选择“跳转”(value === 1)时显示,否则隐藏
+    jumpAppidItem.hidden = value !== 1;
+    jumpPageItem.hidden = value !== 1;
+  }
+};
+
+// 处理工具栏按钮点击(删除等)
+const handleToolbarClick = (name: string) => {
+  console.log(name);
+};
+</script>

+ 244 - 0
src/views/toBManage/firm-info/stationList.vue

@@ -0,0 +1,244 @@
+<template>
+  <div class="app-container h-full flex flex-1 flex-col">
+    <!-- 返回按钮 -->
+    <div class="mb-4">
+      <el-button icon="ArrowLeft" @click="router.back()">返回</el-button>
+    </div>
+
+    <!-- 搜索 -->
+    <page-search
+      ref="searchRef"
+      :search-config="searchConfig"
+      @query-click="handleQueryClick"
+      @reset-click="handleResetClick"
+    ></page-search>
+
+    <!-- 列表 -->
+    <page-content
+      ref="contentRef"
+      :content-config="contentConfig"
+      @add-click="handleAddClick"
+      @export-click="handleExportClick"
+      @search-click="handleSearchClick"
+      @toolbar-click="handleToolbarClick"
+      @operate-click="handleOperateClick"
+      @filter-change="handleFilterChange"
+    >
+      <template #status="scope">
+        <el-tag :type="scope.row.status == 0 ? 'warning' : 'success'">
+          {{ scope.row.status == 0 ? "已下线" : "上线中" }}
+        </el-tag>
+      </template>
+    </page-content>
+
+    <!-- 新增 -->
+    <page-modal
+      ref="addModalRef"
+      :modal-config="addModalConfig"
+      @submit-click="handleSubmitClick"
+    ></page-modal>
+
+    <!-- 编辑 -->
+    <page-modal
+      ref="editModalRef"
+      :modal-config="editModalConfig"
+      @submit-click="handleSubmitClick"
+    ></page-modal>
+  </div>
+</template>
+
+<script setup lang="ts">
+defineOptions({ name: "FirmInfo" });
+
+import FirmInfoAPI, { FirmInfoForm, FirmInfoPageQuery } from "@/api/toBManage/firm-info-api";
+import type { IObject, IModalConfig, IContentConfig, ISearchConfig } from "@/components/CURD/types";
+import usePage from "@/components/CURD/usePage";
+
+const router = useRouter();
+
+// 组合式 CRUD
+const {
+  searchRef,
+  contentRef,
+  addModalRef,
+  editModalRef,
+  handleQueryClick,
+  handleResetClick,
+  handleAddClick,
+  handleEditClick,
+  handleSubmitClick,
+  handleExportClick,
+  handleSearchClick,
+  handleFilterChange,
+} = usePage();
+
+// 搜索配置
+const searchConfig: ISearchConfig = reactive({
+  permPrefix: "business:firm-info",
+  formItems: [
+    {
+      type: "input",
+      label: "充电站ID",
+      prop: "name",
+      attrs: {
+        placeholder: "请输入",
+        clearable: true,
+        style: { width: "200px" },
+      },
+    },
+    {
+      type: "select",
+      label: "充电站名称", // 0 已下线  1 上线中
+      prop: "status",
+      attrs: {
+        placeholder: "请输入状态",
+        clearable: true,
+        style: { width: "200px" },
+      },
+      options: [
+        { label: "已下线", value: 0 },
+        { label: "上线中", value: 1 },
+      ],
+    },
+  ],
+});
+
+// 列表配置
+const contentConfig: IContentConfig<FirmInfoPageQuery> = reactive({
+  // 权限前缀
+  permPrefix: "business:firm-info",
+  table: {
+    border: true,
+    highlightCurrentRow: true,
+  },
+  // 主键
+  pk: "id",
+  // 列表查询接口
+  indexAction: FirmInfoAPI.getPage,
+  // 删除接口
+  deleteAction: FirmInfoAPI.deleteByIds,
+  // 数据解析函数
+  parseData(res: any) {
+    return {
+      total: res.total,
+      list: res.list,
+    };
+  },
+  // 分页配置
+  pagination: {
+    background: true,
+    layout: "total, sizes, prev, pager, next, jumper",
+    pageSize: 20,
+    pageSizes: [10, 20, 30, 50],
+  },
+  // 工具栏配置
+  defaultToolbar: ["refresh", "filter"],
+  // 表格列配置
+  cols: [
+    { type: "selection", width: 55, align: "center" },
+    { label: "企业名称", prop: "name" },
+    { label: "员工数", prop: "" },
+    {
+      label: "状态", // 0 已下线  1 上线中
+      prop: "status",
+      templet: "custom",
+    },
+    { label: "创建时间", prop: "createTime" },
+    {
+      label: "操作",
+      prop: "operation",
+      templet: "tool",
+      operat: [
+        {
+          name: "setting",
+          text: "设置企业专享价",
+          attrs: {
+            type: "success",
+            icon: "Setting",
+          },
+        },
+      ],
+    },
+  ],
+});
+
+// 新增配置
+const addModalConfig: IModalConfig<FirmInfoForm> = reactive({
+  // 权限前缀
+  permPrefix: "business:firm-info",
+  // 主键
+  pk: "id",
+  // 弹窗配置
+  dialog: {
+    title: "新增",
+    width: 800,
+    draggable: true,
+  },
+  form: {
+    labelWidth: 100,
+  },
+  // 表单项配置
+  formItems: [
+    {
+      type: "input",
+      attrs: {
+        placeholder: "企业名称",
+      },
+      label: "企业名称",
+      prop: "name",
+    },
+    {
+      type: "switch",
+      attrs: {
+        activeValue: 1,
+        inactiveValue: 0,
+      },
+      initialValue: 1,
+      label: "状态", // 0 已下线  1 上线中
+      prop: "status",
+    },
+  ],
+  // 提交函数
+  formAction: (data: FirmInfoForm) => {
+    if (data.id) {
+      // 编辑
+      return FirmInfoAPI.update(data.id as string, data);
+    } else {
+      // 新增
+      return FirmInfoAPI.create(data);
+    }
+  },
+});
+
+// 编辑配置
+const editModalConfig: IModalConfig<FirmInfoForm> = reactive({
+  permPrefix: "business:firm-info",
+  component: "drawer",
+  drawer: {
+    title: "编辑",
+    size: 500,
+  },
+  pk: "id",
+  formAction(data: any) {
+    return FirmInfoAPI.update(data.id as string, data);
+  },
+  formItems: addModalConfig.formItems, // 复用新增的表单项
+});
+
+// 处理操作按钮点击
+const handleOperateClick = (data: IObject) => {
+  if (data.name === "edit") {
+    handleEditClick(data.row, async () => {
+      return await FirmInfoAPI.getFormData(data.row.id);
+    });
+  }
+  if (data.name === "setting") {
+    console.log(data.row);
+  }
+};
+
+// 处理工具栏按钮点击(删除等)
+const handleToolbarClick = (name: string) => {
+  console.log(name);
+};
+</script>