hookehuyr

fix(build): 修复 API 文档生成器嵌套对象解析问题

- 添加递归函数支持任意深度嵌套
- 修复 list 字段无法展开的问题
- 支持四层嵌套结构
- 重新生成所有 API 文档
- 修复 AmountInput ESLint 错误
# 搜索
## OpenAPI Specification
```yaml
openapi: 3.0.1
info:
title: ''
version: 1.0.0
paths:
/srv/:
get:
summary: 搜索
deprecated: false
description: ''
tags: []
parameters:
- name: f
in: query
description: ''
required: true
example: manulife
schema:
type: string
- name: a
in: query
description: ''
required: true
example: search
schema:
type: string
- name: t
in: query
description: ''
required: false
example: icon
schema:
type: string
- name: keyword
in: query
description: ''
required: false
schema:
type: string
- name: type
in: query
description: product=产品,file=文档
required: false
schema:
type: string
responses:
'200':
description: ''
content:
application/json:
schema:
type: object
properties:
code:
type: integer
msg:
type: integer
data:
type: object
properties:
products:
type: object
properties:
list:
type: array
items:
type: object
properties:
id:
type: integer
title: 产品id
product_name:
type: string
title: 产品名
product_description:
type: string
title: 产品描述
nullable: true
recommend:
type: string
title: 推荐位
description: normal-普通, hot-热卖
created_time:
type: string
title: 创建时间
cover_image:
type: string
title: 封面图
nullable: true
tags:
type: array
items:
type: object
properties:
id:
type: string
title: 标签id
name:
type: string
title: 标签名
bg_color:
type: string
title: 标签背景色
text_color:
type: string
title: 标签文字色
required:
- id
- name
- bg_color
- text_color
x-apifox-orders:
- id
- name
- bg_color
- text_color
title: 产品标签
type:
type: string
form_sn:
type: string
title: 表单类型
required:
- id
- product_name
- product_description
- recommend
- created_time
- cover_image
- tags
- type
- form_sn
x-apifox-orders:
- id
- product_name
- product_description
- recommend
- created_time
- cover_image
- form_sn
- tags
- type
total:
type: integer
title: 产品总数
required:
- list
- total
x-apifox-orders:
- list
- total
title: 产品列表
files:
type: object
properties:
list:
type: array
items:
type: object
properties:
id:
type: integer
title: 附件id
name:
type: string
title: 附件名
value:
type: string
title: 附件地址
extension:
type: string
title: 附件类型
post_date:
type: string
title: 发布时间
size:
type: string
title: 附件大小
is_favorite:
type: integer
title: 是否收藏
type:
type: string
required:
- id
- name
- value
- extension
- post_date
- size
- is_favorite
- type
x-apifox-orders:
- id
- name
- value
- extension
- post_date
- size
- is_favorite
- type
total:
type: integer
title: 附件数
required:
- list
- total
x-apifox-orders:
- list
- total
title: 文件列表
required:
- products
- files
x-apifox-orders:
- products
- files
required:
- code
- msg
- data
x-apifox-orders:
- code
- msg
- data
example: |-
{
"code": 1,
"msg": 0,
"data": {
"products": {
"list": [
{
"id": 2769848,
"product_name": "1111",
"product_description": "<p>5564</p>\r\n<p>hdye</p>",
"recommend": "normal",
"created_time": "2026-02-02 16:33:01",
"cover_image": "https://cdn.ipadbiz.cn/space_30901/申请提交-生成计划书_FvdRVOS0K-Wmp05ZKHpx64sEXcKQ.png",
"tags": [
{
"id": "2769846",
"name": "测试1",
"bg_color": "#1e9fff",
"text_color": "#ffffff"
},
{
"id": "2769847",
"name": "111",
"bg_color": "#3e5160",
"text_color": "#ffffff"
}
],
"type": "product"
},
{
"id": 2769845,
"product_name": "1",
"product_description": null,
"recommend": "normal",
"created_time": "2026-02-02 16:22:26",
"cover_image": null,
"tags": [],
"type": "product"
}
],
"total": 2,
},
"files": {
"list": [
{
"id": 2769815,
"name": "9751dd823b7542d78fa7bde615a43ba1",
"value": "https://cdn.ipadbiz.cn/space_30901/9751dd823b7542d78fa7bde615a43ba1_Fsm43FPaZOwKc5pwbxhk-h2zUUsu.png",
"extension": "png",
"post_date": "2026-01-13 11:26:07",
"size": "0 B",
"is_favorite": 0,
"type": "file"
},
{
"id": 2769814,
"name": "微信图片_2026-01-12_173653_750",
"value": "https://cdn.ipadbiz.cn/space_30901/微信图片_2026-01-12_173653_750_Fuu11vu9Zp5B626kEbWE62vQLeA7.png",
"extension": "png",
"post_date": "2026-01-13 11:26:07",
"size": "0 B",
"is_favorite": 0,
"type": "file"
},
{
"id": 2769811,
"name": "微信图片_2026-01-12_173913_253",
"value": "https://cdn.ipadbiz.cn/space_30901/微信图片_2026-01-12_173913_253_FoQoABNxtf4dB2vKLD-Eke8Tsgu5.png",
"extension": "png",
"post_date": "2026-01-13 11:25:47",
"size": "0 B",
"is_favorite": 0,
"type": "file"
},
{
"id": 2768696,
"name": "123222",
"value": "https://cdn.ipadbiz.cn/space/30901/4c2ab735c6996a89d7c423bf428a1748.png",
"extension": "png",
"post_date": "2025-10-30 18:30:28",
"size": "0 B",
"is_favorite": 0,
"type": "file"
}
],
"total": 4,
}
}
}
headers: {}
x-apifox-name: 成功
x-apifox-ordering: 0
security: []
x-apifox-folder: ''
x-apifox-status: released
x-run-in-apifox: https://app.apifox.com/web/project/7792797/apis/api-416069509-run
components:
schemas: {}
responses: {}
securitySchemes: {}
servers:
- url: https://manulife.onwall.cn
description: 正式环境
security: []
```
......@@ -366,6 +366,48 @@ function generateParamJSDoc(parameters, bodyParams, method) {
}
/**
* 递归生成属性字段的 JSDoc 注释
* @param {object} properties - 属性对象
* @param {number} indent - 缩进级别(空格数)
* @returns {string} - JSDoc 注释
*/
function generatePropertiesJSDoc(properties, indent = 0) {
const lines = [];
const prefix = ' '.repeat(indent);
Object.entries(properties).forEach(([key, value]) => {
const type = value.type || 'any';
const desc = value.description || value.title || '';
// 处理嵌套对象
if (type === 'object' && value.properties) {
lines.push(`${prefix}${key}: {\n`);
// 递归处理嵌套对象的属性
lines.push(generatePropertiesJSDoc(value.properties, indent + 2));
lines.push(`${prefix}};\n`);
}
// 处理数组(元素是对象)
else if (type === 'array' && value.items && value.items.properties) {
lines.push(`${prefix}${key}: Array<{\n`);
// 递归处理数组元素的属性
lines.push(generatePropertiesJSDoc(value.items.properties, indent + 2));
lines.push(`${prefix}}>;\n`);
}
// 处理简单数组
else if (type === 'array' && value.items) {
const itemType = value.items.type || 'any';
lines.push(`${prefix}${key}: Array<${itemType}>; // ${desc}\n`);
}
// 处理基本类型
else {
lines.push(`${prefix}${key}: ${type}; // ${desc}\n`);
}
});
return lines.join('');
}
/**
* 生成 JSDoc 返回值注释
* @param {object} responseSchema - 响应 schema
* @returns {string} - JSDoc 返回值注释
......@@ -388,44 +430,14 @@ function generateReturnJSDoc(responseSchema) {
// 处理对象类型的 data
if (dataType === 'object' && data.properties) {
returnDesc += ' * data: {\n';
Object.entries(data.properties).forEach(([key, value]) => {
const type = value.type || 'any';
const desc = value.description || value.title || '';
if (type === 'object' && value.properties) {
returnDesc += ` * ${key}: {\n`;
Object.entries(value.properties).forEach(([subKey, subValue]) => {
const subType = subValue.type || 'any';
const subDesc = subValue.description || subValue.title || '';
returnDesc += ` * ${subKey}: ${subType}; // ${subDesc}\n`;
});
returnDesc += ` * };\n`;
} else if (type === 'array' && value.items && value.items.properties) {
returnDesc += ` * ${key}: Array<{\n`;
Object.entries(value.items.properties).forEach(([subKey, subValue]) => {
const subType = subValue.type || 'any';
const subDesc = subValue.description || subValue.title || '';
returnDesc += ` * ${subKey}: ${subType}; // ${subDesc}\n`;
});
returnDesc += ` * }>;\n`;
} else {
returnDesc += ` * ${key}: ${type}; // ${desc}\n`;
}
});
// 使用递归函数处理 data 的所有属性
returnDesc += generatePropertiesJSDoc(data.properties, 4);
returnDesc += ' * };\n';
}
// 处理数组类型的 data(你的情况
// 处理数组类型的 data(元素是对象
else if (dataType === 'array' && data.items && data.items.properties) {
returnDesc += ' * data: Array<{\n';
Object.entries(data.items.properties).forEach(([key, value]) => {
const type = value.type || 'any';
const desc = value.description || value.title || '';
returnDesc += ` * ${key}: ${type}; // ${desc}\n`;
});
returnDesc += generatePropertiesJSDoc(data.items.properties, 4);
returnDesc += ' * }>;\n';
}
// 处理简单数组类型
......
......@@ -43,13 +43,13 @@ export const delAPI = (params) => fn(fetch.post(Api.Del, params));
* code: number; // 状态码
* msg: string; // 消息
* data: {
* list: Array<{
* meta_id: integer; // 文件ID
* name: string; // 文件名称
* src: string; // 文件URL
* created_time: string; // 收藏时间
* size: string; // 文件大小
* }>;
list: Array<{
meta_id: integer; // 文件ID
name: string; // 文件名称
src: string; // 文件URL
created_time: string; // 收藏时间
size: string; // 文件大小
}>;
* };
* }>}
*/
......
......@@ -30,16 +30,16 @@ export const addAPI = (params) => fn(fetch.post(Api.Add, params));
* code: number; // 状态码
* msg: string; // 消息
* data: {
* list: Array<{
* id: integer; // 订单ID
* status: integer; // 3=待处理, 5=已处理
* category: string; // 1=功能建议, 3=界面设计, 5=车辆新鲜, 7=其他问题
* images: string; // 图片
* contact: string; // 联系方式
* note: string; // 反馈内容
* reply: string; // 回复
* reply_time: string; // 回复时间
* }>;
list: Array<{
id: integer; // 订单ID
status: integer; // 3=待处理, 5=已处理
category: string; // 1=功能建议, 3=界面设计, 5=车辆新鲜, 7=其他问题
images: string; // 图片
contact: string; // 联系方式
note: string; // 反馈内容
reply: string; // 回复
reply_time: string; // 回复时间
}>;
* };
* }>}
*/
......
......@@ -19,32 +19,46 @@ const Api = {
* code: number; // 状态码
* msg: string; // 消息
* data: {
* cate: {
* id: integer; // 分类id
* category_name: string; // 分类名称
* category_parent: integer; // 分类父级
* category_description: null; // 分类描述
* };
* children: Array<{
* id: integer; // 二级分类id
* category_name: string; // 二级分类名
* category_parent: integer; // 二级分类名父级id
* category_description: null; // 二级分类描述
* icon: string; // 二级分类图标
* list: array; // 二级分类的附件列表
* children: array; // 三级分类
* }>;
* list: Array<{
* id: integer; //
* name: string; // 附件名称
* value: string; // 附件地址
* extension: string; // 后缀名
* post_date: string; // 发布时间
* size: string; // 附件大小
* is_favorite: integer; // 是否收藏
* }>;
* total: integer; // 主分类附件数量
* max_level: integer; // 页面需要层级
cate: {
id: integer; // 分类id
category_name: string; // 分类名称
category_parent: integer; // 分类父级
category_description: null; // 分类描述
};
children: Array<{
id: integer; // 二级分类id
category_name: string; // 二级分类名
category_parent: integer; // 二级分类名父级id
category_description: null; // 二级分类描述
icon: string; // 二级分类图标
list: Array<{
name: string; // 附件名称
value: string; // 附件地址
extension: string; // 后缀名
post_date: string; // 发布时间
size: string; // 附件大小
is_favorite: integer; // 是否收藏
id: string; // 附件id
}>;
children: Array<{
id: integer; // 三级分类id
category_name: string; // 三级分类名
category_parent: integer; // 三级分类名父级id
category_description: null; // 三级分类描述
icon: string; // 二级分类图标
}>;
}>;
list: Array<{
id: integer; //
name: string; // 附件名称
value: string; // 附件地址
extension: string; // 后缀名
post_date: string; // 发布时间
size: string; // 附件大小
is_favorite: integer; // 是否收藏
}>;
total: integer; // 主分类附件数量
max_level: integer; // 页面需要层级
* };
* }>}
*/
......@@ -60,15 +74,15 @@ export const fileListAPI = (params) => fn(fetch.get(Api.FileList, params));
* code: number; // 状态码
* msg: string; // 消息
* data: {
* list: Array<{
* meta_id: integer; // 文件ID
* name: string; // 文件名称
* src: string; // 文件URL
* size: string; // 文件大小
* read_people_count: integer; // 学习人数
* read_people_percent: number; // 学习人数比例
* is_favorite: string; //
* }>;
list: Array<{
meta_id: integer; // 文件ID
name: string; // 文件名称
src: string; // 文件URL
size: string; // 文件大小
read_people_count: integer; // 学习人数
read_people_percent: number; // 学习人数比例
is_favorite: string; //
}>;
* };
* }>}
*/
......
......@@ -15,33 +15,33 @@ const Api = {
* code: number; // 状态码
* msg: string; // 消息
* data: {
* id: integer; // 产品id
* product_name: string; // 产品名
* recommend: string; // 推荐位: normal-普通, hot-热卖
* status: string; //
* created_by: integer; //
* created_time: string; //
* updated_by: integer; //
* updated_time: string; //
* form_sn: string; // 关联表单sn
* product_description: string; // 产品描述
* categories: Array<{
* id: string; // 分类id
* name: string; // 分类名称
* }>;
* tags: Array<{
* id: string; // 标签id
* name: string; // 标签名
* bg_color: string; // 标签背景色
* text_color: string; // 标签文字色
* }>;
* documents: Array<{
* file_url: string; // 附件地址
* file_name: string; // 附件名
* file_size: string; // 附件大小
* file_size_formatted: string; // 附件大小(转换过显示)
* }>;
* cover_image: string; // 产品封面图
id: integer; // 产品id
product_name: string; // 产品名
recommend: string; // 推荐位: normal-普通, hot-热卖
status: string; //
created_by: integer; //
created_time: string; //
updated_by: integer; //
updated_time: string; //
form_sn: string; // 关联表单sn
product_description: string; // 产品描述
categories: Array<{
id: string; // 分类id
name: string; // 分类名称
}>;
tags: Array<{
id: string; // 标签id
name: string; // 标签名
bg_color: string; // 标签背景色
text_color: string; // 标签文字色
}>;
documents: Array<{
file_url: string; // 附件地址
file_name: string; // 附件名
file_size: string; // 附件大小
file_size_formatted: string; // 附件大小(转换过显示)
}>;
cover_image: string; // 产品封面图
* };
* }>}
*/
......@@ -61,21 +61,29 @@ export const detailAPI = (params) => fn(fetch.get(Api.Detail, params));
* code: number; // 状态码
* msg: string; // 消息
* data: {
* categories: Array<{
* id: integer; // 分类id
* name: string; // 分类名
* }>;
* list: Array<{
* id: integer; // 产品id
* product_name: string; // 产品名
* recommend: string; // 推荐位: normal-普通, hot-热卖
* form_sn: string; //
* created_time: string; // 创建时间
* categories: array; // 产品所属分类
* tags: array; // 产品标签
* cover_image: string; // 产品封面图
* }>;
* total: integer; // 产品总数
categories: Array<{
id: integer; // 分类id
name: string; // 分类名
}>;
list: Array<{
id: integer; // 产品id
product_name: string; // 产品名
recommend: string; // 推荐位: normal-普通, hot-热卖
form_sn: string; //
created_time: string; // 创建时间
categories: Array<{
id: string; // 分类id
name: string; // 分类名
}>;
tags: Array<{
id: string; // 标签id
name: string; // 标签名
bg_color: string; // 标签背景色
text_color: string; // 标签文字色
}>;
cover_image: string; // 产品封面图
}>;
total: integer; // 产品总数
* };
* }>}
*/
......
......@@ -12,11 +12,11 @@ const Api = {
* code: number; // 状态码
* msg: string; // 消息
* data: Array<{
* id: integer; //
* name: string; //
* seq: integer; //
* link: string; //
* icon: string; //
id: integer; //
name: string; //
seq: integer; //
link: string; //
icon: string; //
* }>;
* }>}
*/
......
......@@ -14,10 +14,10 @@ const Api = {
* code: number; // 状态码
* msg: string; // 消息
* data: Array<{
* id: integer; // 消息id
* note: string; // 消息内容
* created_time: string; // 发消息的时间
* status: string; // send=以发送未读取,read=已读取
id: integer; // 消息id
note: string; // 消息内容
created_time: string; // 发消息的时间
status: string; // send=以发送未读取,read=已读取
* }>;
* }>}
*/
......@@ -33,10 +33,10 @@ export const detailAPI = (params) => fn(fetch.get(Api.Detail, params));
* code: number; // 状态码
* msg: string; // 消息
* data: Array<{
* id: integer; // 消息id
* note: string; // 消息内容
* created_time: string; // 发消息的时间
* status: string; // send=以发送未读取,read=已读取
id: integer; // 消息id
note: string; // 消息内容
created_time: string; // 发消息的时间
status: string; // send=以发送未读取,read=已读取
* }>;
* }>}
*/
......
import { fn, fetch } from '@/api/fn';
const Api = {
Search: '/srv/?a=search&t=icon',
}
/**
* @description 搜索
* @remark
* @param {Object} params 请求参数
* @param {string} params.keyword (可选)
* @param {string} params.type (可选) product=产品,file=文档
* @returns {Promise<{
* code: number; // 状态码
* msg: string; // 消息
* data: {
products: {
list: Array<{
id: integer; // 产品id
product_name: string; // 产品名
product_description: string; // 产品描述
recommend: string; // normal-普通, hot-热卖
created_time: string; // 创建时间
cover_image: string; // 封面图
tags: Array<{
id: string; // 标签id
name: string; // 标签名
bg_color: string; // 标签背景色
text_color: string; // 标签文字色
}>;
type: string; //
form_sn: string; // 表单类型
}>;
total: integer; // 产品总数
};
files: {
list: Array<{
id: integer; // 附件id
name: string; // 附件名
value: string; // 附件地址
extension: string; // 附件类型
post_date: string; // 发布时间
size: string; // 附件大小
is_favorite: integer; // 是否收藏
type: string; //
}>;
total: integer; // 附件数
};
* };
* }>}
*/
export const searchAPI = (params) => fn(fetch.get(Api.Search, params));
......@@ -16,12 +16,19 @@ const Api = {
* code: number; // 状态码
* msg: string; // 消息
* data: {
* user: {
* id: integer; // 用户ID
* name: string; // 姓名
* employee_no: string; // 工号
* avatar: object; // 头像
* };
user: {
id: integer; // 用户ID
name: string; // 姓名
employee_no: string; // 工号
avatar: {
name: string; // 文件名
hash: string; // 文件hash
src: string; // 文件地址
height: string; // 文件高度
width: string; // 文件宽度
size: integer; // 文件大小
};
};
* };
* }>}
*/
......@@ -49,8 +56,8 @@ export const loginAPI = (params) => fn(fetch.post(Api.Login, params));
* code: number; // 状态码
* msg: string; // 消息
* data: {
* is_login: boolean; // true=登录,false=未登录
* is_openid: boolean; // true=已授权,false=未授权
is_login: boolean; // true=登录,false=未登录
is_openid: boolean; // true=已授权,false=未授权
* };
* }>}
*/
......
......@@ -15,11 +15,11 @@ const Api = {
* code: number; // 状态码
* msg: string; // 消息
* data: {
* user: {
* id: integer; // 用户ID
* avatar_url: string; // 头像
* name: string; // 姓名
* };
user: {
id: integer; // 用户ID
avatar_url: string; // 头像
name: string; // 姓名
};
* };
* }>}
*/
......
<template>
<div>
<!-- 标签 -->
<div v-if="label" class="text-sm text-gray-600 mb-2">
{{ label }}
<span v-if="currencyText" class="text-gray-500">{{ currencyText }}</span>
</div>
<!-- 多币种模式(方案 2 - 未来扩展) -->
<div v-if="multiCurrencyEnabled" class="mb-2">
<div class="text-sm text-gray-600 mb-2">币种</div>
<div class="flex gap-2">
<button
v-for="curr in supportedCurrencies"
:key="curr.value"
:class="[
'px-4 py-2 rounded-lg text-sm border transition-colors',
selectedCurrency === curr.value
? 'bg-blue-600 text-white border-blue-600'
: 'bg-white text-gray-600 border-gray-200'
]"
@tap="selectCurrency(curr.value)"
>
{{ curr.label }}
</button>
</div>
</div>
<!-- 保额输入 -->
<div class="border border-gray-200 rounded-lg flex items-center overflow-hidden">
<nut-input
:model-value="formattedValue"
@input="onInput"
type="digit"
:placeholder="placeholder"
class="!p-0 !bg-transparent flex-1 !text-sm !text-gray-900"
:border="false"
/>
<span class="text-sm text-gray-500 shrink-0 ml-2 mr-5">{{ currencySymbol }}</span>
</div>
</div>
</template>
<script setup>
/**
* 保额输入组件
*
* @description 支持多币种的保额输入组件
* - 单位转换:内部存储为分(整数),显示为元(带2位小数)
* - 币种支持:CNY、USD、HKD、EUR
* - 多币种模式:通过 FEATURE_FLAGS.MULTI_CURRENCY_ENABLED 控制
* @author Claude Code
* @example
* <!-- 固定币种模式 -->
* <AmountInput
* v-model="coverage"
* label="保额"
* currency="USD"
* placeholder="请输入保额"
* />
*
* @example
* <!-- 多币种模式 -->
* <AmountInput
* v-model="coverage"
* label="保额"
* :config="{ supported_currencies: ['CNY', 'USD'], default_currency: 'CNY' }"
* placeholder="请输入保额"
* />
*/
import { ref, computed } from 'vue'
import { FEATURE_FLAGS, CURRENCY_SYMBOLS, CURRENCY_MAP } from '@/config/plan-templates'
/**
* 组件属性
*/
const props = defineProps({
/**
* 标签文本
* @type {string}
*/
label: {
type: String,
default: ''
},
/**
* 占位符文本
* @type {string}
*/
placeholder: {
type: String,
default: '请输入保额'
},
/**
* 绑定的值(单位:分)
* @type {number}
* @example 100000 表示 1000.00 元
*/
modelValue: {
type: Number,
default: null
},
/**
* 币种代码(固定币种模式)
* @type {string}
* @default 'CNY'
*/
currency: {
type: String,
default: 'CNY'
},
/**
* 模版配置(多币种模式)
* @type {Object}
* @property {Array<string>} supported_currencies - 支持的币种代码数组
* @property {string} default_currency - 默认币种代码
* @example { supported_currencies: ['CNY', 'USD'], default_currency: 'CNY' }
*/
config: {
type: Object,
default: () => ({})
}
})
/**
* 组件事件
*/
const emit = defineEmits([
/**
* 更新值事件
* @event update:modelValue
* @param {number} value - 保额值(单位:分)
*/
'update:modelValue'
])
/**
* 判断是否启用多币种
* @type {ComputedRef<boolean>}
*/
const multiCurrencyEnabled = computed(() => FEATURE_FLAGS.MULTI_CURRENCY_ENABLED)
/**
* 当前选中的币种
* @type {Ref<string>}
*/
const selectedCurrency = ref(props.config.default_currency || props.currency || 'CNY')
/**
* 支持的币种列表(多币种模式)
* @type {ComputedRef<Array<{label: string, symbol: string, value: string}>>}
*/
const supportedCurrencies = computed(() => {
if (!multiCurrencyEnabled.value) return []
return (props.config.supported_currencies || ['CNY'])
.map(code => CURRENCY_MAP[code])
.filter(Boolean)
})
/**
* 当前币种符号
* @type {ComputedRef<string>}
* @example
* // CNY -> '¥'
* // USD -> '$'
*/
const currencySymbol = computed(() => {
if (multiCurrencyEnabled.value) {
// 多币种模式:使用用户选择的币种
const curr = supportedCurrencies.value.find(c => c.value === selectedCurrency.value)
return curr?.symbol || '¥'
}
// 固定币种模式:使用 props.currency
return CURRENCY_SYMBOLS[props.currency] || '¥'
})
/**
* 币种文本(用于标签显示)
* @type {ComputedRef<string>}
*/
const currencyText = computed(() => {
if (multiCurrencyEnabled.value) {
const curr = supportedCurrencies.value.find(c => c.value === selectedCurrency.value)
return curr?.label || ''
}
const CURRENCY_NAMES = {
CNY: '人民币',
USD: '美元',
HKD: '港币',
EUR: '欧元'
}
return CURRENCY_NAMES[props.currency] || ''
})
/**
* 格式化显示值(元,带2位小数)
* @description 将分转换为元进行显示
* @type {ComputedRef<string>}
* @example
* // modelValue = 100000 (分)
* // formattedValue() // 返回: '1000.00'
*/
const formattedValue = computed(() => {
if (props.modelValue === null || props.modelValue === undefined) {
return ''
}
// 分 -> 元,保留2位小数
return (props.modelValue / 100).toFixed(2)
})
/**
* 用户输入处理
* @description 将用户输入的元转换为分存储
* @param {string} value - 输入值
*
* @example
* // 用户输入: '1000.50'
* // onInput('1000.50')
* // -> emit('update:modelValue', 100050) // 分
*/
const onInput = (value) => {
// 移除非数字和小数点
const cleanValue = value.replace(/[^\d.]/g, '')
// 转换为分(整数)
const yuan = parseFloat(cleanValue)
if (!Number.isNaN(yuan)) {
emit('update:modelValue', Math.round(yuan * 100))
} else {
emit('update:modelValue', 0)
}
}
/**
* 选择币种(多币种模式)
* @param {string} value - 币种代码
*/
const selectCurrency = (value) => {
selectedCurrency.value = value
}
</script>
<style lang="less" scoped>
/* 组件样式 */
</style>