董国庆
2026-02-11 a4bfa88389770f9dd206022fc4cfdfae4dbd3ea4
修改加密,传入方法
4个文件已添加
2个文件已修改
686 ■■■■■ 已修改文件
docs/点位管理/点位管理接口文档.md 273 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/service/api/point.ts 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/service/http/alova.ts 167 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/typings/api/point.d.ts 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/typings/entities/point.d.ts 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/operationsManagement/pointManagement/index.vue 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/点位管理/点位管理接口文档.md
New file
@@ -0,0 +1,273 @@
# 点位管理接口文档
## 接口概览
| 接口名称 | 接口路径 | 请求方式 | 说明 |
|---------|---------|---------|------|
| 获取点位列表 | `/map/api/v1/point/list` | POST | 获取点位列表 |
| 新增点位 | `/map/api/v1/point/add` | POST | 创建新点位 |
| 更新点位信息 | `/map/api/v1/point/edit` | POST | 编辑点位 |
| 启用/禁用点位 | `/map/api/v1/point/enableDisable` | POST | 启用/禁用点位 |
| 获取所有运营中的区域 | `/map/api/v1/point/listElectronicFenceArea` | POST | 获取所有运营中的区域 |
| 点位详情 | `/map/api/v1/point/queryById` | GET | 获取点位详情 |
---
## 1. 获取点位列表
### 接口信息
- **接口路径**: `/map/api/v1/point/list`
- **请求方式**: `POST`
- **接口说明**: 获取点位列表,支持按类型、状态筛选和关键词搜索
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|-------|------|------|------|
| queryParams | string | 否 | 搜索关键词(点位名称或地址) |
| pointType | integer($int32) | 否 | 点位类型1运营点位 2测试点位 |
| operationStatus | integer($int32) | 否 | 运营状态 1=待启用 2=启用中 3=已停用 4=已失效 |
**请求示例**:
```javascript
{
  "queryParams": "天河",
  "pointType": 1,
  "operationStatus": 1,2、3、4
}
```
### 响应参数
| 参数名 | 类型 | 说明 |
|-------|------|------|
| code | number | 响应状态码 |
| msg | string | 响应消息 |
| data | array | 响应数据 --点位列表|
**点位对象结构**:
```javascript
{
    createBy    string 记录创建人,前端忽略
    createTime    string($date-time)  记录创建时间,前端忽略
    deleted    boolean
    是否删除 0未删除 1已删除
    distance    number
    距离 单位米
    effectivePeriod    integer($int32)
    生效周期 1=“每日生效”、2=“仅工作日生效”、3=“仅非工作日生效”、4=“仅节假日生效”
    effectiveTimePeriod    string
    生效时间段
    electronicAreaId    integer($int64)
    电子围栏区域id
    id    integer($int64)
    operationStatus    integer($int32)
    运营状态 1=待启用 2=启用中 3=已停用 4=已失效
    operationType    integer($int32)
    运营类型 1=与运营区同步 2=自定义时间段
    parkingTimeLimit    integer($int32)
    限停时长
    pointAddress    string
    点位地址
    pointLat    number($double)
    纬度
    pointLon    number($double)
    经度
    pointName    string
    点位名称
    pointNumber    string
    点位编号唯一
    pointPictures    string
    点位照片
    pointPoi    string
    点位poi
    pointType    integer($int32)
    点位类型 1=运营点位 2=测试点位
    remarks    string
    备注
    updateBy    string
    记录修改人,前端忽略
    updateTime    string($date-time)
    记录修改时间,前端忽略
    verificationStatus    integer($int32)
    验证状态 1=待验证、2=验证中、3=已验证 4验证失败
}
```
**响应示例**:
```javascript
{
  "code": 200,
  "msg": "success",
  "data": { [
      {
        "id": 1,
        "name": "天河体育中心站点",
        "address": "广州市天河区天河路299号",
        "status": "待启用",
        "type": "运营",
        "isOpen": false,
        "poi": "113.264499,23.130061",
        "longitude": 113.264499,
        "latitude": 23.130061,
        "limitTime": 60,
        "images": ["https://example.com/image1.jpg"],
        "operationTimeType": "sync",
        "customTimeRanges": []
      }
    ]
}
```
---
## 2. 新增点位
### 接口信息
- **接口路径**: `/map/api/v1/point/add`
- **请求方式**: `POST`
- **接口说明**: 创建新点位
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|-------|------|------|------|
| effectivePeriod | string | 是 | 生效周期 1=“每日生效”、2=“仅工作日生效”、3=“仅非工作日生效”、4=“仅节假日生效” |
| effectiveTimePeriod | string | 是 | 生效时间段 |
| electronicAreaId | string | 是 | 电子围栏区域ID |
| operationStatus | string | 是 | 运营状态 1=待启用 2=启用中 3=已停用 4=已失效 |
| operationType | string | 是 | 运营类型 1=与运营区同步 2=自定义时间段 |
| parkingTimeLimit | string | 是 | 限停时长 |
| pointAddress | string | 是 | 点位地址 |
| pointLat | number | 是 | 纬度 |
| pointLon | number | 是 | 经度 |
| pointName | string | 是 | 点位名称 |
| pointNumber | string | 是 | 点位编号唯一 |
| pointPictures | string | 是 | 点位照片 |
| pointPoi | string | 否 | 点位POI |
| pointType | integer($int32) | 是 | 点位类型 1=运营点位 2=测试点位 |
| remarks | string | 否 | 备注 |
| verificationStatus | string | 否 | 验证状态 1=待验证、2=验证中、3=已验证 4=验证失败 |
## 启用/禁用点位
### 接口信息
- **接口路径**: `/api/points/toggle`
- **请求方式**: `POST`
- **接口说明**: 切换点位的启用/禁用状态
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|-------|------|------|------|
| id | number | 是 | 点位ID |
| operationStatus | integer($int32) | 是 | 运营状态 1=待启用 2=启用中 3=已停用 4=已失效 |
**请求示例**:
```javascript
{
  "id": 1,
  "operationStatus": 3, // 3=已停用
}
```
### 响应参数
| 参数名 | 类型 | 说明 |
|-------|------|------|
| code | number | 响应状态码 |
| message | string | 响应消息 |
**响应示例**:
```javascript
{
  "code": 200,
  "message": "点位已停用"
}
```
---
## 获取所有运营中的区域
### 接口信息
- **接口路径**: `/map/api/v1/point/listElectronicFenceArea`
- **请求方式**: `POST`
- **接口说明**: 获取所有运营中的区域
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|-------|------|------|------|
**请求示例**:
```javascript
{
}
```
### 响应参数
| 参数名 | 类型 | 说明 |
|-------|------|------|
| code | number | 响应状态码 |
| message | string | 响应消息 |
**响应示例**:
```javascript
{
  "code": 200,
  "msg": "success",
  "data": [
    {
      "areaDescription": "天河体育中心运营区",
      "areaName": "天河体育中心",
      "areaStatus": 1,
      "areaType": 1,
      "createBy": "admin",
      "createTime": "2023-08-01T00:00:00Z",
      "deleted": false,
      "electronicId": 123456,
      "id": 123456,
      "fencePoints": "113.264499,23.130061,113.264499,23.130061",
    }
  ]
}
```
---
## 附录
### 点位状态说明
| 状态值 | 说明 | 适用类型 |
|-------|------|---------|
| 待测试 | 点位创建后等待测试 | 测试 |
| 测试中 | 点位正在测试中 | 测试 |
| 测试不通过 | 测试未通过 | 测试 |
| 可运营 | 测试通过,可以运营 | 测试 |
| 待生效 | 等待生效 | 测试 |
| 待启用 | 等待启用 | 运营 |
| 启用中 | 正在启用中 | 运营 |
| 已停用 | 已停用 | 运营 |
| 失效 | 失效 | 运营/测试 |
### 点位类型说明
| 类型值 | 说明 |
|-------|------|
| 测试 | 测试点位 |
| 运营 | 运营点位 |
### 错误码说明
| 错误码 | 说明 |
|-------|------|
| 200 | 成功 |
| 400 | 请求参数错误 |
| 401 | 未授权 |
| 403 | 禁止访问 |
| 404 | 资源不存在 |
| 500 | 服务器错误 |
src/service/api/point.ts
New file
@@ -0,0 +1,62 @@
import { createEncryptedRequest, request } from '../http'
export function fetchPointList(params: Api.Point.ListParams) {
  return createEncryptedRequest<Entity.Point[]>(request, {
    path: '/map/api/v1/point/list',
    data: params,
    options: {
      sensitiveFields: [],
    },
  })
}
export function createPoint(data: Api.Point.CreateData) {
  return createEncryptedRequest<any>(request, {
    path: '/map/api/v1/point/add',
    data,
    options: {
      sensitiveFields: [],
    },
  })
}
export function updatePoint(data: Api.Point.UpdateData) {
  return createEncryptedRequest<any>(request, {
    path: '/map/api/v1/point/edit',
    data,
    options: {
      sensitiveFields: [],
    },
  })
}
export function enableDisablePoint(data: Api.Point.EnableDisableData) {
  return createEncryptedRequest<any>(request, {
    path: '/map/api/v1/point/enableDisable',
    data,
    options: {
      sensitiveFields: [],
    },
  })
}
export function fetchPointDetail(id: number) {
  return createEncryptedRequest<Entity.Point>(request, {
    path: '/map/api/v1/point/queryById',
    method: 'GET',
    data: { id },
    options: {
      sensitiveFields: [],
    },
  })
}
export function fetchElectronicFenceAreaList() {
  return createEncryptedRequest<Entity.ElectronicFenceArea[]>(request, {
    path: '/map/api/v1/point/listElectronicFenceArea',
    data: {},
    options: {
      sensitiveFields: [],
    },
  })
}
src/service/http/alova.ts
@@ -35,6 +35,7 @@
export interface EncryptedRequestConfig {
  path: string
  data?: any
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
  options?: EncryptedRequestOptions
}
@@ -94,84 +95,100 @@
      const sensitiveFields = method.meta?.sensitiveFields || [] // 从meta中获取敏感字段
      console.log('secretKey', secretKey)
      console.log('method.meta', method.meta, 'method.data', method.data)
      if (secretKey) {
        if (method.data && typeof method.data === 'object') {
          // 分离敏感字段和非敏感字段
          const sensitiveData: Record<string, any> = {}
          const normalData: Record<string, any> = {}
          Object.keys(method.data).forEach((key) => {
            if (sensitiveFields.includes(key)) {
              sensitiveData[key] = method.data[key]
            }
            else {
              normalData[key] = method.data[key]
            }
          })
      // 获取请求参数:GET请求使用params,其他请求使用data
      const requestData = method.type === 'GET' ? (method.config.params || {}) : (method.data || {})
          // 加密敏感数据
          if (Object.keys(sensitiveData).length > 0 && rsaPublicKey) {
            const aesKey = generateAesKey()
            const encryptedAesKey = rsaEncrypt(aesKey, rsaPublicKey)
      if (secretKey && Object.keys(requestData).length > 0) {
        // 分离敏感字段和非敏感字段
        const sensitiveData: Record<string, any> = {}
        const normalData: Record<string, any> = {}
            // 将所有敏感字段一起加密成一个密文
            const encryptedData = aesEncrypt(JSON.stringify(sensitiveData), aesKey)
            // 替换敏感字段为加密后的数据(保持字段名不变)
            const encryptedParams: Record<string, any> = {}
            Object.keys(sensitiveData).forEach((key) => {
              encryptedParams[key] = encryptedData
            })
            method.data = {
              ...normalData,
              ...encryptedParams,
            }
            // 生成签名,使用加密后的数据
            const signParams = {
              ...normalData,
              ...encryptedParams,
            }
            const sign = generateSign(signParams, secretKey, timestamp)
            // 添加请求头
            const headers = generateHeaders(sign, encryptedAesKey, timestamp)
            Object.assign(method.config.headers, headers)
        Object.keys(requestData).forEach((key) => {
          if (sensitiveFields.includes(key)) {
            sensitiveData[key] = requestData[key]
          }
          else {
            // 所有接口都需要签名,即使没有敏感数据
            const sign = generateSign(method.data, secretKey, timestamp)
            // 添加签名相关请求头
            const headers = {
              timestamp: timestamp.toString(),
              nonce: secretKey,
              sign,
            }
            Object.assign(method.config.headers, headers)
            normalData[key] = requestData[key]
          }
        })
        // 加密敏感数据
        if (Object.keys(sensitiveData).length > 0 && rsaPublicKey) {
          const aesKey = generateAesKey()
          const encryptedAesKey = rsaEncrypt(aesKey, rsaPublicKey)
          // 将所有敏感字段一起加密成一个密文
          const encryptedData = aesEncrypt(JSON.stringify(sensitiveData), aesKey)
          // 替换敏感字段为加密后的数据(保持字段名不变)
          const encryptedParams: Record<string, any> = {}
          Object.keys(sensitiveData).forEach((key) => {
            encryptedParams[key] = encryptedData
          })
          const finalData = {
            ...normalData,
            ...encryptedParams,
          }
          // 根据请求类型设置参数
          if (method.type === 'GET') {
            method.config.params = finalData
          }
          else {
            method.data = finalData
          }
          // 生成签名,使用加密后的数据
          const signParams = {
            ...normalData,
            ...encryptedParams,
          }
          const sign = generateSign(signParams, secretKey, timestamp)
          // 添加请求头
          const headers = generateHeaders(sign, encryptedAesKey, timestamp)
          Object.assign(method.config.headers, headers)
        }
        else {
          // 没有传参数,但仍然需要添加时间戳和签名
          // 对于无参数的请求,默认传一个时间戳的参数
          const paramsWithTimestamp = {
            timestamp: timestamp.toString(),
          }
          // 使用包含时间戳的对象生成签名
          const sign = generateSign(paramsWithTimestamp, secretKey, timestamp)
          console.log('method', method)
          // 将时间戳作为参数传递
          method.data = paramsWithTimestamp
          // 所有接口都需要签名,即使没有敏感数据
          const sign = generateSign(requestData, secretKey, timestamp)
          // 添加签名相关请求头
          const headers = {
            timestamp: timestamp.toString(),
            nonce: secretKey,
            sign,
          }
          Object.assign(method.config.headers, headers)
        }
      }
      else {
        // 没有传参数,但仍然需要添加时间戳和签名
        // 对于无参数的请求,默认传一个时间戳的参数
        const paramsWithTimestamp = {
          timestamp: timestamp.toString(),
        }
        // 使用包含时间戳的对象生成签名
        const sign = generateSign(paramsWithTimestamp, secretKey, timestamp)
        console.log('method', method)
        // 根据请求类型设置参数
        // if (method.type === 'GET') {
        //   method.config.params = paramsWithTimestamp
        // }
        // else {
        //   method.data = paramsWithTimestamp
        // }
        // 添加签名相关请求头
        const headers = {
          timestamp: timestamp.toString(),
          nonce: secretKey,
          sign,
        }
        Object.assign(method.config.headers, headers)
      }
      alovaConfig.beforeRequest?.(method)
@@ -216,7 +233,7 @@
  requestInstance: any,
  config: EncryptedRequestConfig,
): Promise<Service.ResponseResult<T>> {
  const { path, data, options = {} } = config
  const { path, data, method = 'POST', options = {} } = config
  const { sensitiveFields = [], rsaPublicKey: providedPublicKey, secretKey: providedSecretKey, meta = {} } = options
  let rsaPublicKey = providedPublicKey
@@ -237,7 +254,23 @@
    secretKey = generateSignKey()
  }
  const methodInstance = requestInstance.Post<Service.ResponseResult<T>>(path, data || {})
  let methodInstance
  if (method === 'GET') {
    methodInstance = requestInstance.Get<Service.ResponseResult<T>>(path, { params: data || {} })
  }
  else if (method === 'PUT') {
    methodInstance = requestInstance.Put<Service.ResponseResult<T>>(path, data || {})
  }
  else if (method === 'DELETE') {
    methodInstance = requestInstance.Delete<Service.ResponseResult<T>>(path, { data: data || {} })
  }
  else if (method === 'PATCH') {
    methodInstance = requestInstance.Patch<Service.ResponseResult<T>>(path, data || {})
  }
  else {
    methodInstance = requestInstance.Post<Service.ResponseResult<T>>(path, data || {})
  }
  methodInstance.meta = {
    ...meta,
    rsaPublicKey,
@@ -245,5 +278,11 @@
    sensitiveFields,
  }
  // 手动添加token到请求头
  const accessToken = local.get('accessToken')
  if (accessToken) {
    methodInstance.config.headers.Authorization = `Bearer ${accessToken}`
  }
  return methodInstance.send()
}
src/typings/api/point.d.ts
New file
@@ -0,0 +1,93 @@
/// <reference path="../global.d.ts"/>
namespace Api {
  namespace Point {
    interface ListParams {
      /** 搜索关键词(点位名称或地址) */
      queryParams?: string
      /** 点位类型 1=运营点位 2=测试点位 */
      pointType?: number
      /** 运营状态 1=待启用 2=启用中 3=已停用 4=已失效 */
      operationStatus?: number
    }
    interface CreateData {
      /** 生效周期 1=“每日生效”、2=“仅工作日生效”、3=“仅非工作日生效”、4=“仅节假日生效” */
      effectivePeriod: string
      /** 生效时间段 */
      effectiveTimePeriod: string
      /** 电子围栏区域ID */
      electronicAreaId: string
      /** 运营状态 1=待启用 2=启用中 3=已停用 4=已失效 */
      operationStatus: string
      /** 运营类型 1=与运营区同步 2=自定义时间段 */
      operationType: string
      /** 限停时长 */
      parkingTimeLimit: string
      /** 点位地址 */
      pointAddress: string
      /** 纬度 */
      pointLat: number
      /** 经度 */
      pointLon: number
      /** 点位名称 */
      pointName: string
      /** 点位编号唯一 */
      pointNumber: string
      /** 点位照片 */
      pointPictures: string
      /** 点位POI */
      pointPoi?: string
      /** 点位类型 1=运营点位 2=测试点位 */
      pointType: number
      /** 备注 */
      remarks?: string
      /** 验证状态 1=待验证、2=验证中、3=已验证 4=验证失败 */
      verificationStatus?: string
    }
    interface UpdateData {
      /** 点位ID */
      id: number
      /** 生效周期 1=“每日生效”、2=“仅工作日生效”、3=“仅非工作日生效”、4=“仅节假日生效” */
      effectivePeriod?: string
      /** 生效时间段 */
      effectiveTimePeriod?: string
      /** 电子围栏区域ID */
      electronicAreaId?: string
      /** 运营状态 1=待启用 2=启用中 3=已停用 4=已失效 */
      operationStatus?: string
      /** 运营类型 1=与运营区同步 2=自定义时间段 */
      operationType?: string
      /** 限停时长 */
      parkingTimeLimit?: string
      /** 点位地址 */
      pointAddress?: string
      /** 纬度 */
      pointLat?: number
      /** 经度 */
      pointLon?: number
      /** 点位名称 */
      pointName?: string
      /** 点位编号唯一 */
      pointNumber?: string
      /** 点位照片 */
      pointPictures?: string
      /** 点位POI */
      pointPoi?: string
      /** 点位类型 1=运营点位 2=测试点位 */
      pointType?: number
      /** 备注 */
      remarks?: string
      /** 验证状态 1=待验证、2=验证中、3=已验证 4=验证失败 */
      verificationStatus?: string
    }
    interface EnableDisableData {
      /** 点位ID */
      id: number
      /** 运营状态 1=待启用 2=启用中 3=已停用 4=已失效 */
      operationStatus: number
    }
  }
}
src/typings/entities/point.d.ts
New file
@@ -0,0 +1,75 @@
/// <reference path="../global.d.ts"/>
namespace Entity {
  interface Point {
    /** 点位ID */
    id?: number
    /** 点位名称 */
    pointName?: string
    /** 点位地址 */
    pointAddress?: string
    /** 点位编号唯一 */
    pointNumber?: string
    /** 点位POI */
    pointPoi?: string
    /** 经度 */
    pointLon?: number
    /** 纬度 */
    pointLat?: number
    /** 点位类型 1=运营点位 2=测试点位 */
    pointType?: number
    /** 运营状态 1=待启用 2=启用中 3=已停用 4=已失效 */
    operationStatus?: number
    /** 验证状态 1=待验证、2=验证中、3=已验证 4=验证失败 */
    verificationStatus?: number
    /** 运营类型 1=与运营区同步 2=自定义时间段 */
    operationType?: number
    /** 限停时长 */
    parkingTimeLimit?: number
    /** 点位照片 */
    pointPictures?: string
    /** 电子围栏区域id */
    electronicAreaId?: number
    /** 生效周期 1=“每日生效”、2=“仅工作日生效”、3=“仅非工作日生效”、4=“仅节假日生效” */
    effectivePeriod?: number
    /** 生效时间段 */
    effectiveTimePeriod?: string
    /** 备注 */
    remarks?: string
    /** 距离 单位米 */
    distance?: number
    /** 是否删除 0未删除 1已删除 */
    deleted?: boolean
    /** 记录创建人,前端忽略 */
    createBy?: string
    /** 记录创建时间,前端忽略 */
    createTime?: string
    /** 记录修改人,前端忽略 */
    updateBy?: string
    /** 记录修改时间,前端忽略 */
    updateTime?: string
  }
  interface ElectronicFenceArea {
    /** 区域ID */
    id?: number
    /** 电子围栏ID */
    electronicId?: number
    /** 区域名称 */
    areaName?: string
    /** 区域描述 */
    areaDescription?: string
    /** 区域状态 */
    areaStatus?: number
    /** 区域类型 */
    areaType?: number
    /** 围栏点坐标 */
    fencePoints?: string
    /** 是否删除 */
    deleted?: boolean
    /** 创建人 */
    createBy?: string
    /** 创建时间 */
    createTime?: string
  }
}
src/views/operationsManagement/pointManagement/index.vue
@@ -1,6 +1,7 @@
<script setup lang="tsx">
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useScriptTag } from '@vueuse/core'
import { fetchElectronicFenceAreaList } from '@/service/api/point'
import PointStatusSteps from '@/components/PointStatusSteps/custom.vue'
import ConfirmDialog from '@/components/ConfirmDialog/index.vue'
@@ -515,8 +516,23 @@
  showDeleteConfirm.value = false
}
// 获取所有运营中的区域
async function loadElectronicFenceAreaList() {
  try {
    const { data, isSuccess } = await fetchElectronicFenceAreaList()
    if (isSuccess) {
      console.log('运营区域列表:', data)
    }
  }
  catch (error) {
    console.error('获取运营区域失败:', error)
  }
}
onMounted(() => {
  initMap()
  // 获取运营区域列表
  loadElectronicFenceAreaList()
  // 默认选中第一个点位
  if (pointList.value.length > 0) {
    currentPoint.value = pointList.value[0]