Browse Source

背部产品穴位接口

jiapu 3 months ago
parent
commit
0c698bca7a

+ 112 - 0
code/app对接接口说明.md

@@ -0,0 +1,112 @@
+# APP 对接接口说明
+
+## 1. 文档说明
+
+本文档用于统一维护 APP 对接后台接口说明。
+
+- 服务前缀:`/api`
+- 本地开发示例地址:`http://localhost:18888`
+- 鉴权方式:除登录等白名单接口外,请求头需携带后台登录后的 Token
+- 通用返回结构:`code`、`message`、`data`
+
+## 2. 接口目录
+
+| 序号 | 接口名称 | 方法 | URI | 说明 |
+| --- | --- | --- | --- | --- |
+| 1 | 按身体部位和产品穴位查询穴位分页列表 | `GET` | `/api/acupoint/page` | 按 `bodyPart`、`productAcupoint` 等条件筛选穴位 |
+
+## 3. 接口详情
+
+### 3.1 按身体部位和产品穴位查询穴位分页列表
+
+#### 3.1.1 接口用途
+
+用于按身体部位和产品穴位标记筛选穴位列表。
+
+当前场景:获取身体部位为`背部`,且为产品穴位的所有穴位。
+
+#### 3.1.2 请求信息
+
+- 请求方式:`GET`
+- URI:`/api/acupoint/page`
+- 完整示例:`http://localhost:18888/api/acupoint/page`
+
+#### 3.1.3 请求参数
+
+| 参数名 | 类型 | 是否必填 | 说明 | 示例 |
+| --- | --- | --- | --- | --- |
+| `bodyPart` | `string` | 否 | 身体部位,精确匹配 | `背部` |
+| `productAcupoint` | `number` | 否 | 是否产品穴位:`1=是`,`0=否` | `1` |
+| `pageNum` | `number` | 否 | 页码,默认 `1` | `1` |
+| `pageSize` | `number` | 否 | 每页条数,默认 `10` | `20` |
+| `name` | `string` | 否 | 穴位名称,模糊匹配 | `大椎` |
+| `meridian` | `string` | 否 | 所属经络,精确匹配 | `督脉` |
+| `status` | `number` | 否 | 状态:`1=活跃`,`0=待完善` | `1` |
+| `parseStatus` | `string` | 否 | 解析状态 | `已解析` |
+
+#### 3.1.4 调用示例
+
+```http
+GET /api/acupoint/page?pageNum=1&pageSize=20&bodyPart=背部&productAcupoint=1 HTTP/1.1
+Host: localhost:18888
+Authorization: Bearer <token>
+```
+
+```bash
+curl -X GET "http://localhost:18888/api/acupoint/page?pageNum=1&pageSize=20&bodyPart=%E8%83%8C%E9%83%A8&productAcupoint=1" \
+  -H "Authorization: Bearer <token>"
+```
+
+#### 3.1.5 返回示例
+
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    "records": [
+      {
+        "id": 371,
+        "acupointCode": "dumai-14",
+        "name": "大椎",
+        "singleDouble": "单穴",
+        "meridian": "督脉",
+        "bodyPart": "背部",
+        "bodyRegion": "背部",
+        "locationDescription": "在脊柱区,第7颈椎棘突下凹陷中,后正中线上。",
+        "productAcupoint": 1,
+        "parseStatus": "已解析",
+        "status": 1,
+        "createTime": "2026-04-16T15:27:36",
+        "updateTime": "2026-04-16T15:27:36"
+      },
+      {
+        "id": 362,
+        "acupointCode": "dumai-04",
+        "name": "命门",
+        "singleDouble": "单穴",
+        "meridian": "督脉",
+        "bodyPart": "背部",
+        "bodyRegion": "腰部",
+        "locationDescription": "在脊柱区,第2腰椎棘突下凹陷中,后正中线上。",
+        "productAcupoint": 1,
+        "parseStatus": "已解析",
+        "status": 1,
+        "createTime": "2026-04-16T15:27:36",
+        "updateTime": "2026-04-16T15:27:36"
+      }
+    ],
+    "total": 42,
+    "size": 20,
+    "current": 1,
+    "pages": 3
+  }
+}
+```
+
+#### 3.1.6 备注
+
+- `bodyPart` 当前是精确匹配,不是模糊搜索。
+- `productAcupoint` 传 `1` 表示只返回产品穴位,传 `0` 表示只返回非产品穴位。
+- 如果只想获取身体部位选项列表,可调用:`GET /api/acupoint/options/body-parts`
+- 如果需要导出身体部位为背部且为产品穴位的全部穴位数据,可调用:`GET /api/acupoint/export?bodyPart=背部&productAcupoint=1`

+ 11 - 0
code/backend/src/main/java/com/aijiuyi/admin/controller/AcupointController.java

@@ -53,6 +53,17 @@ public class AcupointController {
         return Result.success(acupointService.listYReferenceOptions());
     }
 
+    /**
+     * 获取身体部位筛选选项
+     *
+     * @return 身体部位列表
+     */
+    @GetMapping("/options/body-parts")
+    @Log(value = "获取穴位身体部位选项", module = "穴位管理", operationType = OperationType.QUERY)
+    public Result<List<String>> bodyPartOptions() {
+        return Result.success(acupointService.listBodyPartOptions());
+    }
+
     /**
      * 分页查询穴位列表
      *

+ 6 - 0
code/backend/src/main/java/com/aijiuyi/admin/controller/dto/AcupointQueryDTO.java

@@ -14,6 +14,12 @@ public class AcupointQueryDTO {
     /** 所属经络 */
     private String meridian;
 
+    /** 身体部位 */
+    private String bodyPart;
+
+    /** 是否产品穴位:1=是,0=否 */
+    private Integer productAcupoint;
+
     /** 定位方式:当前固定为相对定位,保留字段兼容旧查询参数 */
     private Integer locationType;
 

+ 7 - 0
code/backend/src/main/java/com/aijiuyi/admin/service/AcupointService.java

@@ -36,6 +36,13 @@ public interface AcupointService extends IService<Acupoint> {
      */
     List<YReferenceOptionDTO> listYReferenceOptions();
 
+    /**
+     * 获取已有穴位身体部位选项
+     *
+     * @return 身体部位列表
+     */
+    List<String> listBodyPartOptions();
+
     /**
      * 新增穴位
      *

+ 17 - 0
code/backend/src/main/java/com/aijiuyi/admin/service/impl/AcupointServiceImpl.java

@@ -55,6 +55,8 @@ public class AcupointServiceImpl extends ServiceImpl<AcupointMapper, Acupoint> i
         LambdaQueryWrapper<Acupoint> wrapper = new LambdaQueryWrapper<>();
         wrapper.like(StringUtils.hasText(queryDTO.getName()), Acupoint::getName, queryDTO.getName())
                .eq(StringUtils.hasText(queryDTO.getMeridian()), Acupoint::getMeridian, queryDTO.getMeridian())
+               .eq(StringUtils.hasText(queryDTO.getBodyPart()), Acupoint::getBodyPart, queryDTO.getBodyPart())
+               .eq(queryDTO.getProductAcupoint() != null, Acupoint::getProductAcupoint, queryDTO.getProductAcupoint())
                .eq(queryDTO.getLocationType() != null, Acupoint::getLocationType, queryDTO.getLocationType())
                .eq(queryDTO.getStatus() != null, Acupoint::getStatus, queryDTO.getStatus())
                .and(StringUtils.hasText(queryDTO.getParseStatus()), w -> {
@@ -88,6 +90,21 @@ public class AcupointServiceImpl extends ServiceImpl<AcupointMapper, Acupoint> i
         return AcupointYReference.listOptions();
     }
 
+    @Override
+    public List<String> listBodyPartOptions() {
+        return lambdaQuery()
+                .select(Acupoint::getBodyPart)
+                .isNotNull(Acupoint::getBodyPart)
+                .ne(Acupoint::getBodyPart, "")
+                .groupBy(Acupoint::getBodyPart)
+                .orderByAsc(Acupoint::getBodyPart)
+                .list()
+                .stream()
+                .map(Acupoint::getBodyPart)
+                .filter(StringUtils::hasText)
+                .collect(java.util.stream.Collectors.toList());
+    }
+
     /**
      * 新增穴位,校验编码和名称唯一性
      *

+ 8 - 1
code/frontend/src/api/acupoint.js

@@ -20,9 +20,16 @@ export function getYReferenceOptions() {
   return request.get('/acupoint/y-reference-options')
 }
 
+/**
+ * 获取身体部位筛选选项
+ */
+export function getBodyPartOptions() {
+  return request.get('/acupoint/options/body-parts')
+}
+
 /**
  * 分页查询穴位列表
- * @param {Object} params 查询参数:name/meridian/status/parseStatus/pageNum/pageSize
+ * @param {Object} params 查询参数:name/meridian/bodyPart/productAcupoint/status/parseStatus/pageNum/pageSize
  */
 export function getAcupointPage(params) {
   return request.get('/acupoint/page', { params })

+ 50 - 8
code/frontend/src/views/acupoint/index.vue

@@ -16,6 +16,22 @@
             <el-option label="其他" value="其他" />
           </el-select>
         </el-form-item>
+        <el-form-item label="身体部位">
+          <el-select v-model="queryForm.bodyPart" placeholder="全部" clearable filterable style="width:140px">
+            <el-option
+              v-for="item in bodyPartOptions"
+              :key="item"
+              :label="item"
+              :value="item"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="产品穴位">
+          <el-select v-model="queryForm.productAcupoint" placeholder="全部" clearable style="width:120px">
+            <el-option label="是" :value="1" />
+            <el-option label="否" :value="0" />
+          </el-select>
+        </el-form-item>
         <el-form-item label="状态">
           <el-select v-model="queryForm.status" placeholder="全部" clearable style="width:110px">
             <el-option label="活跃" :value="1" />
@@ -314,6 +330,7 @@ import { parsePageData } from '@/utils/pagination'
 import {
   getAcupointPage,
   exportAcupoint,
+  getBodyPartOptions,
   addAcupoint,
   updateAcupoint,
   deleteAcupoint,
@@ -325,6 +342,8 @@ import {
 const queryForm = reactive({
   name: '',
   meridian: '',
+  bodyPart: '',
+  productAcupoint: null,
   status: null,
   parseStatus: '',
   pageNum: 1,
@@ -335,6 +354,7 @@ const tableData = ref([])
 const total = ref(0)
 const loading = ref(false)
 const selectedIds = ref([])
+const bodyPartOptions = ref([])
 
 // 弹窗
 const dialogVisible = ref(false)
@@ -410,13 +430,29 @@ const exportHeaders = [
   { label: '状态', prop: 'statusLabel' }
 ]
 
+function buildQueryParams(includePage = true) {
+  const params = {
+    name: queryForm.name,
+    meridian: queryForm.meridian,
+    bodyPart: queryForm.bodyPart,
+    productAcupoint: queryForm.productAcupoint,
+    status: queryForm.status,
+    parseStatus: queryForm.parseStatus
+  }
+  if (includePage) {
+    params.pageNum = queryForm.pageNum
+    params.pageSize = queryForm.pageSize
+  }
+  return params
+}
+
 /**
  * 加载穴位列表
  */
 async function loadData() {
   loading.value = true
   try {
-    const res = await getAcupointPage(queryForm)
+    const res = await getAcupointPage(buildQueryParams())
     const { records, total: pageTotal } = parsePageData(res.data)
     // 添加辅助显示字段
     tableData.value = records.map(formatAcupointRow)
@@ -426,6 +462,15 @@ async function loadData() {
   }
 }
 
+async function loadBodyPartOptions() {
+  try {
+    const res = await getBodyPartOptions()
+    bodyPartOptions.value = res.data || []
+  } catch {
+    bodyPartOptions.value = []
+  }
+}
+
 function formatAcupointRow(item) {
   return {
     ...item,
@@ -442,13 +487,7 @@ function formatAcupointRow(item) {
  * 获取导出数据(全量,传入当前筛选条件)
  */
 async function fetchExportData() {
-  const params = {
-    name: queryForm.name,
-    meridian: queryForm.meridian,
-    status: queryForm.status,
-    parseStatus: queryForm.parseStatus
-  }
-  const res = await exportAcupoint(params)
+  const res = await exportAcupoint(buildQueryParams(false))
   const list = res.data || []
   return list.map(formatAcupointRow)
 }
@@ -503,6 +542,8 @@ function handleSearch() {
 function handleReset() {
   queryForm.name = ''
   queryForm.meridian = ''
+  queryForm.bodyPart = ''
+  queryForm.productAcupoint = null
   queryForm.status = null
   queryForm.parseStatus = ''
   queryForm.pageNum = 1
@@ -649,6 +690,7 @@ async function handleImport(data) {
 }
 
 onMounted(() => {
+  loadBodyPartOptions()
   loadData()
 })
 </script>