fix(build): 修复 API 文档生成器嵌套对象解析问题
- 添加递归函数支持任意深度嵌套 - 修复 list 字段无法展开的问题 - 支持四层嵌套结构 - 重新生成所有 API 文档 - 修复 AmountInput ESLint 错误
Showing
12 changed files
with
837 additions
and
154 deletions
docs/api-specs/search/search.md
0 → 100644
| 1 | +# 搜索 | ||
| 2 | + | ||
| 3 | +## OpenAPI Specification | ||
| 4 | + | ||
| 5 | +```yaml | ||
| 6 | +openapi: 3.0.1 | ||
| 7 | +info: | ||
| 8 | + title: '' | ||
| 9 | + version: 1.0.0 | ||
| 10 | +paths: | ||
| 11 | + /srv/: | ||
| 12 | + get: | ||
| 13 | + summary: 搜索 | ||
| 14 | + deprecated: false | ||
| 15 | + description: '' | ||
| 16 | + tags: [] | ||
| 17 | + parameters: | ||
| 18 | + - name: f | ||
| 19 | + in: query | ||
| 20 | + description: '' | ||
| 21 | + required: true | ||
| 22 | + example: manulife | ||
| 23 | + schema: | ||
| 24 | + type: string | ||
| 25 | + - name: a | ||
| 26 | + in: query | ||
| 27 | + description: '' | ||
| 28 | + required: true | ||
| 29 | + example: search | ||
| 30 | + schema: | ||
| 31 | + type: string | ||
| 32 | + - name: t | ||
| 33 | + in: query | ||
| 34 | + description: '' | ||
| 35 | + required: false | ||
| 36 | + example: icon | ||
| 37 | + schema: | ||
| 38 | + type: string | ||
| 39 | + - name: keyword | ||
| 40 | + in: query | ||
| 41 | + description: '' | ||
| 42 | + required: false | ||
| 43 | + schema: | ||
| 44 | + type: string | ||
| 45 | + - name: type | ||
| 46 | + in: query | ||
| 47 | + description: product=产品,file=文档 | ||
| 48 | + required: false | ||
| 49 | + schema: | ||
| 50 | + type: string | ||
| 51 | + responses: | ||
| 52 | + '200': | ||
| 53 | + description: '' | ||
| 54 | + content: | ||
| 55 | + application/json: | ||
| 56 | + schema: | ||
| 57 | + type: object | ||
| 58 | + properties: | ||
| 59 | + code: | ||
| 60 | + type: integer | ||
| 61 | + msg: | ||
| 62 | + type: integer | ||
| 63 | + data: | ||
| 64 | + type: object | ||
| 65 | + properties: | ||
| 66 | + products: | ||
| 67 | + type: object | ||
| 68 | + properties: | ||
| 69 | + list: | ||
| 70 | + type: array | ||
| 71 | + items: | ||
| 72 | + type: object | ||
| 73 | + properties: | ||
| 74 | + id: | ||
| 75 | + type: integer | ||
| 76 | + title: 产品id | ||
| 77 | + product_name: | ||
| 78 | + type: string | ||
| 79 | + title: 产品名 | ||
| 80 | + product_description: | ||
| 81 | + type: string | ||
| 82 | + title: 产品描述 | ||
| 83 | + nullable: true | ||
| 84 | + recommend: | ||
| 85 | + type: string | ||
| 86 | + title: 推荐位 | ||
| 87 | + description: normal-普通, hot-热卖 | ||
| 88 | + created_time: | ||
| 89 | + type: string | ||
| 90 | + title: 创建时间 | ||
| 91 | + cover_image: | ||
| 92 | + type: string | ||
| 93 | + title: 封面图 | ||
| 94 | + nullable: true | ||
| 95 | + tags: | ||
| 96 | + type: array | ||
| 97 | + items: | ||
| 98 | + type: object | ||
| 99 | + properties: | ||
| 100 | + id: | ||
| 101 | + type: string | ||
| 102 | + title: 标签id | ||
| 103 | + name: | ||
| 104 | + type: string | ||
| 105 | + title: 标签名 | ||
| 106 | + bg_color: | ||
| 107 | + type: string | ||
| 108 | + title: 标签背景色 | ||
| 109 | + text_color: | ||
| 110 | + type: string | ||
| 111 | + title: 标签文字色 | ||
| 112 | + required: | ||
| 113 | + - id | ||
| 114 | + - name | ||
| 115 | + - bg_color | ||
| 116 | + - text_color | ||
| 117 | + x-apifox-orders: | ||
| 118 | + - id | ||
| 119 | + - name | ||
| 120 | + - bg_color | ||
| 121 | + - text_color | ||
| 122 | + title: 产品标签 | ||
| 123 | + type: | ||
| 124 | + type: string | ||
| 125 | + form_sn: | ||
| 126 | + type: string | ||
| 127 | + title: 表单类型 | ||
| 128 | + required: | ||
| 129 | + - id | ||
| 130 | + - product_name | ||
| 131 | + - product_description | ||
| 132 | + - recommend | ||
| 133 | + - created_time | ||
| 134 | + - cover_image | ||
| 135 | + - tags | ||
| 136 | + - type | ||
| 137 | + - form_sn | ||
| 138 | + x-apifox-orders: | ||
| 139 | + - id | ||
| 140 | + - product_name | ||
| 141 | + - product_description | ||
| 142 | + - recommend | ||
| 143 | + - created_time | ||
| 144 | + - cover_image | ||
| 145 | + - form_sn | ||
| 146 | + - tags | ||
| 147 | + - type | ||
| 148 | + total: | ||
| 149 | + type: integer | ||
| 150 | + title: 产品总数 | ||
| 151 | + required: | ||
| 152 | + - list | ||
| 153 | + - total | ||
| 154 | + x-apifox-orders: | ||
| 155 | + - list | ||
| 156 | + - total | ||
| 157 | + title: 产品列表 | ||
| 158 | + files: | ||
| 159 | + type: object | ||
| 160 | + properties: | ||
| 161 | + list: | ||
| 162 | + type: array | ||
| 163 | + items: | ||
| 164 | + type: object | ||
| 165 | + properties: | ||
| 166 | + id: | ||
| 167 | + type: integer | ||
| 168 | + title: 附件id | ||
| 169 | + name: | ||
| 170 | + type: string | ||
| 171 | + title: 附件名 | ||
| 172 | + value: | ||
| 173 | + type: string | ||
| 174 | + title: 附件地址 | ||
| 175 | + extension: | ||
| 176 | + type: string | ||
| 177 | + title: 附件类型 | ||
| 178 | + post_date: | ||
| 179 | + type: string | ||
| 180 | + title: 发布时间 | ||
| 181 | + size: | ||
| 182 | + type: string | ||
| 183 | + title: 附件大小 | ||
| 184 | + is_favorite: | ||
| 185 | + type: integer | ||
| 186 | + title: 是否收藏 | ||
| 187 | + type: | ||
| 188 | + type: string | ||
| 189 | + required: | ||
| 190 | + - id | ||
| 191 | + - name | ||
| 192 | + - value | ||
| 193 | + - extension | ||
| 194 | + - post_date | ||
| 195 | + - size | ||
| 196 | + - is_favorite | ||
| 197 | + - type | ||
| 198 | + x-apifox-orders: | ||
| 199 | + - id | ||
| 200 | + - name | ||
| 201 | + - value | ||
| 202 | + - extension | ||
| 203 | + - post_date | ||
| 204 | + - size | ||
| 205 | + - is_favorite | ||
| 206 | + - type | ||
| 207 | + total: | ||
| 208 | + type: integer | ||
| 209 | + title: 附件数 | ||
| 210 | + required: | ||
| 211 | + - list | ||
| 212 | + - total | ||
| 213 | + x-apifox-orders: | ||
| 214 | + - list | ||
| 215 | + - total | ||
| 216 | + title: 文件列表 | ||
| 217 | + required: | ||
| 218 | + - products | ||
| 219 | + - files | ||
| 220 | + x-apifox-orders: | ||
| 221 | + - products | ||
| 222 | + - files | ||
| 223 | + required: | ||
| 224 | + - code | ||
| 225 | + - msg | ||
| 226 | + - data | ||
| 227 | + x-apifox-orders: | ||
| 228 | + - code | ||
| 229 | + - msg | ||
| 230 | + - data | ||
| 231 | + example: |- | ||
| 232 | + { | ||
| 233 | + "code": 1, | ||
| 234 | + "msg": 0, | ||
| 235 | + "data": { | ||
| 236 | + "products": { | ||
| 237 | + "list": [ | ||
| 238 | + { | ||
| 239 | + "id": 2769848, | ||
| 240 | + "product_name": "1111", | ||
| 241 | + "product_description": "<p>5564</p>\r\n<p>hdye</p>", | ||
| 242 | + "recommend": "normal", | ||
| 243 | + "created_time": "2026-02-02 16:33:01", | ||
| 244 | + "cover_image": "https://cdn.ipadbiz.cn/space_30901/申请提交-生成计划书_FvdRVOS0K-Wmp05ZKHpx64sEXcKQ.png", | ||
| 245 | + "tags": [ | ||
| 246 | + { | ||
| 247 | + "id": "2769846", | ||
| 248 | + "name": "测试1", | ||
| 249 | + "bg_color": "#1e9fff", | ||
| 250 | + "text_color": "#ffffff" | ||
| 251 | + }, | ||
| 252 | + { | ||
| 253 | + "id": "2769847", | ||
| 254 | + "name": "111", | ||
| 255 | + "bg_color": "#3e5160", | ||
| 256 | + "text_color": "#ffffff" | ||
| 257 | + } | ||
| 258 | + ], | ||
| 259 | + "type": "product" | ||
| 260 | + }, | ||
| 261 | + { | ||
| 262 | + "id": 2769845, | ||
| 263 | + "product_name": "1", | ||
| 264 | + "product_description": null, | ||
| 265 | + "recommend": "normal", | ||
| 266 | + "created_time": "2026-02-02 16:22:26", | ||
| 267 | + "cover_image": null, | ||
| 268 | + "tags": [], | ||
| 269 | + "type": "product" | ||
| 270 | + } | ||
| 271 | + ], | ||
| 272 | + "total": 2, | ||
| 273 | + }, | ||
| 274 | + "files": { | ||
| 275 | + "list": [ | ||
| 276 | + { | ||
| 277 | + "id": 2769815, | ||
| 278 | + "name": "9751dd823b7542d78fa7bde615a43ba1", | ||
| 279 | + "value": "https://cdn.ipadbiz.cn/space_30901/9751dd823b7542d78fa7bde615a43ba1_Fsm43FPaZOwKc5pwbxhk-h2zUUsu.png", | ||
| 280 | + "extension": "png", | ||
| 281 | + "post_date": "2026-01-13 11:26:07", | ||
| 282 | + "size": "0 B", | ||
| 283 | + "is_favorite": 0, | ||
| 284 | + "type": "file" | ||
| 285 | + }, | ||
| 286 | + { | ||
| 287 | + "id": 2769814, | ||
| 288 | + "name": "微信图片_2026-01-12_173653_750", | ||
| 289 | + "value": "https://cdn.ipadbiz.cn/space_30901/微信图片_2026-01-12_173653_750_Fuu11vu9Zp5B626kEbWE62vQLeA7.png", | ||
| 290 | + "extension": "png", | ||
| 291 | + "post_date": "2026-01-13 11:26:07", | ||
| 292 | + "size": "0 B", | ||
| 293 | + "is_favorite": 0, | ||
| 294 | + "type": "file" | ||
| 295 | + }, | ||
| 296 | + { | ||
| 297 | + "id": 2769811, | ||
| 298 | + "name": "微信图片_2026-01-12_173913_253", | ||
| 299 | + "value": "https://cdn.ipadbiz.cn/space_30901/微信图片_2026-01-12_173913_253_FoQoABNxtf4dB2vKLD-Eke8Tsgu5.png", | ||
| 300 | + "extension": "png", | ||
| 301 | + "post_date": "2026-01-13 11:25:47", | ||
| 302 | + "size": "0 B", | ||
| 303 | + "is_favorite": 0, | ||
| 304 | + "type": "file" | ||
| 305 | + }, | ||
| 306 | + { | ||
| 307 | + "id": 2768696, | ||
| 308 | + "name": "123222", | ||
| 309 | + "value": "https://cdn.ipadbiz.cn/space/30901/4c2ab735c6996a89d7c423bf428a1748.png", | ||
| 310 | + "extension": "png", | ||
| 311 | + "post_date": "2025-10-30 18:30:28", | ||
| 312 | + "size": "0 B", | ||
| 313 | + "is_favorite": 0, | ||
| 314 | + "type": "file" | ||
| 315 | + } | ||
| 316 | + ], | ||
| 317 | + "total": 4, | ||
| 318 | + } | ||
| 319 | + } | ||
| 320 | + } | ||
| 321 | + headers: {} | ||
| 322 | + x-apifox-name: 成功 | ||
| 323 | + x-apifox-ordering: 0 | ||
| 324 | + security: [] | ||
| 325 | + x-apifox-folder: '' | ||
| 326 | + x-apifox-status: released | ||
| 327 | + x-run-in-apifox: https://app.apifox.com/web/project/7792797/apis/api-416069509-run | ||
| 328 | +components: | ||
| 329 | + schemas: {} | ||
| 330 | + responses: {} | ||
| 331 | + securitySchemes: {} | ||
| 332 | +servers: | ||
| 333 | + - url: https://manulife.onwall.cn | ||
| 334 | + description: 正式环境 | ||
| 335 | +security: [] | ||
| 336 | + | ||
| 337 | +``` |
| ... | @@ -366,6 +366,48 @@ function generateParamJSDoc(parameters, bodyParams, method) { | ... | @@ -366,6 +366,48 @@ function generateParamJSDoc(parameters, bodyParams, method) { |
| 366 | } | 366 | } |
| 367 | 367 | ||
| 368 | /** | 368 | /** |
| 369 | + * 递归生成属性字段的 JSDoc 注释 | ||
| 370 | + * @param {object} properties - 属性对象 | ||
| 371 | + * @param {number} indent - 缩进级别(空格数) | ||
| 372 | + * @returns {string} - JSDoc 注释 | ||
| 373 | + */ | ||
| 374 | +function generatePropertiesJSDoc(properties, indent = 0) { | ||
| 375 | + const lines = []; | ||
| 376 | + const prefix = ' '.repeat(indent); | ||
| 377 | + | ||
| 378 | + Object.entries(properties).forEach(([key, value]) => { | ||
| 379 | + const type = value.type || 'any'; | ||
| 380 | + const desc = value.description || value.title || ''; | ||
| 381 | + | ||
| 382 | + // 处理嵌套对象 | ||
| 383 | + if (type === 'object' && value.properties) { | ||
| 384 | + lines.push(`${prefix}${key}: {\n`); | ||
| 385 | + // 递归处理嵌套对象的属性 | ||
| 386 | + lines.push(generatePropertiesJSDoc(value.properties, indent + 2)); | ||
| 387 | + lines.push(`${prefix}};\n`); | ||
| 388 | + } | ||
| 389 | + // 处理数组(元素是对象) | ||
| 390 | + else if (type === 'array' && value.items && value.items.properties) { | ||
| 391 | + lines.push(`${prefix}${key}: Array<{\n`); | ||
| 392 | + // 递归处理数组元素的属性 | ||
| 393 | + lines.push(generatePropertiesJSDoc(value.items.properties, indent + 2)); | ||
| 394 | + lines.push(`${prefix}}>;\n`); | ||
| 395 | + } | ||
| 396 | + // 处理简单数组 | ||
| 397 | + else if (type === 'array' && value.items) { | ||
| 398 | + const itemType = value.items.type || 'any'; | ||
| 399 | + lines.push(`${prefix}${key}: Array<${itemType}>; // ${desc}\n`); | ||
| 400 | + } | ||
| 401 | + // 处理基本类型 | ||
| 402 | + else { | ||
| 403 | + lines.push(`${prefix}${key}: ${type}; // ${desc}\n`); | ||
| 404 | + } | ||
| 405 | + }); | ||
| 406 | + | ||
| 407 | + return lines.join(''); | ||
| 408 | +} | ||
| 409 | + | ||
| 410 | +/** | ||
| 369 | * 生成 JSDoc 返回值注释 | 411 | * 生成 JSDoc 返回值注释 |
| 370 | * @param {object} responseSchema - 响应 schema | 412 | * @param {object} responseSchema - 响应 schema |
| 371 | * @returns {string} - JSDoc 返回值注释 | 413 | * @returns {string} - JSDoc 返回值注释 |
| ... | @@ -388,44 +430,14 @@ function generateReturnJSDoc(responseSchema) { | ... | @@ -388,44 +430,14 @@ function generateReturnJSDoc(responseSchema) { |
| 388 | // 处理对象类型的 data | 430 | // 处理对象类型的 data |
| 389 | if (dataType === 'object' && data.properties) { | 431 | if (dataType === 'object' && data.properties) { |
| 390 | returnDesc += ' * data: {\n'; | 432 | returnDesc += ' * data: {\n'; |
| 391 | - | 433 | + // 使用递归函数处理 data 的所有属性 |
| 392 | - Object.entries(data.properties).forEach(([key, value]) => { | 434 | + returnDesc += generatePropertiesJSDoc(data.properties, 4); |
| 393 | - const type = value.type || 'any'; | ||
| 394 | - const desc = value.description || value.title || ''; | ||
| 395 | - | ||
| 396 | - if (type === 'object' && value.properties) { | ||
| 397 | - returnDesc += ` * ${key}: {\n`; | ||
| 398 | - Object.entries(value.properties).forEach(([subKey, subValue]) => { | ||
| 399 | - const subType = subValue.type || 'any'; | ||
| 400 | - const subDesc = subValue.description || subValue.title || ''; | ||
| 401 | - returnDesc += ` * ${subKey}: ${subType}; // ${subDesc}\n`; | ||
| 402 | - }); | ||
| 403 | - returnDesc += ` * };\n`; | ||
| 404 | - } else if (type === 'array' && value.items && value.items.properties) { | ||
| 405 | - returnDesc += ` * ${key}: Array<{\n`; | ||
| 406 | - Object.entries(value.items.properties).forEach(([subKey, subValue]) => { | ||
| 407 | - const subType = subValue.type || 'any'; | ||
| 408 | - const subDesc = subValue.description || subValue.title || ''; | ||
| 409 | - returnDesc += ` * ${subKey}: ${subType}; // ${subDesc}\n`; | ||
| 410 | - }); | ||
| 411 | - returnDesc += ` * }>;\n`; | ||
| 412 | - } else { | ||
| 413 | - returnDesc += ` * ${key}: ${type}; // ${desc}\n`; | ||
| 414 | - } | ||
| 415 | - }); | ||
| 416 | - | ||
| 417 | returnDesc += ' * };\n'; | 435 | returnDesc += ' * };\n'; |
| 418 | } | 436 | } |
| 419 | - // 处理数组类型的 data(你的情况) | 437 | + // 处理数组类型的 data(元素是对象) |
| 420 | else if (dataType === 'array' && data.items && data.items.properties) { | 438 | else if (dataType === 'array' && data.items && data.items.properties) { |
| 421 | returnDesc += ' * data: Array<{\n'; | 439 | returnDesc += ' * data: Array<{\n'; |
| 422 | - | 440 | + returnDesc += generatePropertiesJSDoc(data.items.properties, 4); |
| 423 | - Object.entries(data.items.properties).forEach(([key, value]) => { | ||
| 424 | - const type = value.type || 'any'; | ||
| 425 | - const desc = value.description || value.title || ''; | ||
| 426 | - returnDesc += ` * ${key}: ${type}; // ${desc}\n`; | ||
| 427 | - }); | ||
| 428 | - | ||
| 429 | returnDesc += ' * }>;\n'; | 441 | returnDesc += ' * }>;\n'; |
| 430 | } | 442 | } |
| 431 | // 处理简单数组类型 | 443 | // 处理简单数组类型 | ... | ... |
| ... | @@ -43,13 +43,13 @@ export const delAPI = (params) => fn(fetch.post(Api.Del, params)); | ... | @@ -43,13 +43,13 @@ export const delAPI = (params) => fn(fetch.post(Api.Del, params)); |
| 43 | * code: number; // 状态码 | 43 | * code: number; // 状态码 |
| 44 | * msg: string; // 消息 | 44 | * msg: string; // 消息 |
| 45 | * data: { | 45 | * data: { |
| 46 | - * list: Array<{ | 46 | + list: Array<{ |
| 47 | - * meta_id: integer; // 文件ID | 47 | + meta_id: integer; // 文件ID |
| 48 | - * name: string; // 文件名称 | 48 | + name: string; // 文件名称 |
| 49 | - * src: string; // 文件URL | 49 | + src: string; // 文件URL |
| 50 | - * created_time: string; // 收藏时间 | 50 | + created_time: string; // 收藏时间 |
| 51 | - * size: string; // 文件大小 | 51 | + size: string; // 文件大小 |
| 52 | - * }>; | 52 | + }>; |
| 53 | * }; | 53 | * }; |
| 54 | * }>} | 54 | * }>} |
| 55 | */ | 55 | */ | ... | ... |
| ... | @@ -30,16 +30,16 @@ export const addAPI = (params) => fn(fetch.post(Api.Add, params)); | ... | @@ -30,16 +30,16 @@ export const addAPI = (params) => fn(fetch.post(Api.Add, params)); |
| 30 | * code: number; // 状态码 | 30 | * code: number; // 状态码 |
| 31 | * msg: string; // 消息 | 31 | * msg: string; // 消息 |
| 32 | * data: { | 32 | * data: { |
| 33 | - * list: Array<{ | 33 | + list: Array<{ |
| 34 | - * id: integer; // 订单ID | 34 | + id: integer; // 订单ID |
| 35 | - * status: integer; // 3=待处理, 5=已处理 | 35 | + status: integer; // 3=待处理, 5=已处理 |
| 36 | - * category: string; // 1=功能建议, 3=界面设计, 5=车辆新鲜, 7=其他问题 | 36 | + category: string; // 1=功能建议, 3=界面设计, 5=车辆新鲜, 7=其他问题 |
| 37 | - * images: string; // 图片 | 37 | + images: string; // 图片 |
| 38 | - * contact: string; // 联系方式 | 38 | + contact: string; // 联系方式 |
| 39 | - * note: string; // 反馈内容 | 39 | + note: string; // 反馈内容 |
| 40 | - * reply: string; // 回复 | 40 | + reply: string; // 回复 |
| 41 | - * reply_time: string; // 回复时间 | 41 | + reply_time: string; // 回复时间 |
| 42 | - * }>; | 42 | + }>; |
| 43 | * }; | 43 | * }; |
| 44 | * }>} | 44 | * }>} |
| 45 | */ | 45 | */ | ... | ... |
| ... | @@ -19,32 +19,46 @@ const Api = { | ... | @@ -19,32 +19,46 @@ const Api = { |
| 19 | * code: number; // 状态码 | 19 | * code: number; // 状态码 |
| 20 | * msg: string; // 消息 | 20 | * msg: string; // 消息 |
| 21 | * data: { | 21 | * data: { |
| 22 | - * cate: { | 22 | + cate: { |
| 23 | - * id: integer; // 分类id | 23 | + id: integer; // 分类id |
| 24 | - * category_name: string; // 分类名称 | 24 | + category_name: string; // 分类名称 |
| 25 | - * category_parent: integer; // 分类父级 | 25 | + category_parent: integer; // 分类父级 |
| 26 | - * category_description: null; // 分类描述 | 26 | + category_description: null; // 分类描述 |
| 27 | - * }; | 27 | + }; |
| 28 | - * children: Array<{ | 28 | + children: Array<{ |
| 29 | - * id: integer; // 二级分类id | 29 | + id: integer; // 二级分类id |
| 30 | - * category_name: string; // 二级分类名 | 30 | + category_name: string; // 二级分类名 |
| 31 | - * category_parent: integer; // 二级分类名父级id | 31 | + category_parent: integer; // 二级分类名父级id |
| 32 | - * category_description: null; // 二级分类描述 | 32 | + category_description: null; // 二级分类描述 |
| 33 | - * icon: string; // 二级分类图标 | 33 | + icon: string; // 二级分类图标 |
| 34 | - * list: array; // 二级分类的附件列表 | 34 | + list: Array<{ |
| 35 | - * children: array; // 三级分类 | 35 | + name: string; // 附件名称 |
| 36 | - * }>; | 36 | + value: string; // 附件地址 |
| 37 | - * list: Array<{ | 37 | + extension: string; // 后缀名 |
| 38 | - * id: integer; // | 38 | + post_date: string; // 发布时间 |
| 39 | - * name: string; // 附件名称 | 39 | + size: string; // 附件大小 |
| 40 | - * value: string; // 附件地址 | 40 | + is_favorite: integer; // 是否收藏 |
| 41 | - * extension: string; // 后缀名 | 41 | + id: string; // 附件id |
| 42 | - * post_date: string; // 发布时间 | 42 | + }>; |
| 43 | - * size: string; // 附件大小 | 43 | + children: Array<{ |
| 44 | - * is_favorite: integer; // 是否收藏 | 44 | + id: integer; // 三级分类id |
| 45 | - * }>; | 45 | + category_name: string; // 三级分类名 |
| 46 | - * total: integer; // 主分类附件数量 | 46 | + category_parent: integer; // 三级分类名父级id |
| 47 | - * max_level: integer; // 页面需要层级 | 47 | + category_description: null; // 三级分类描述 |
| 48 | + icon: string; // 二级分类图标 | ||
| 49 | + }>; | ||
| 50 | + }>; | ||
| 51 | + list: Array<{ | ||
| 52 | + id: integer; // | ||
| 53 | + name: string; // 附件名称 | ||
| 54 | + value: string; // 附件地址 | ||
| 55 | + extension: string; // 后缀名 | ||
| 56 | + post_date: string; // 发布时间 | ||
| 57 | + size: string; // 附件大小 | ||
| 58 | + is_favorite: integer; // 是否收藏 | ||
| 59 | + }>; | ||
| 60 | + total: integer; // 主分类附件数量 | ||
| 61 | + max_level: integer; // 页面需要层级 | ||
| 48 | * }; | 62 | * }; |
| 49 | * }>} | 63 | * }>} |
| 50 | */ | 64 | */ |
| ... | @@ -60,15 +74,15 @@ export const fileListAPI = (params) => fn(fetch.get(Api.FileList, params)); | ... | @@ -60,15 +74,15 @@ export const fileListAPI = (params) => fn(fetch.get(Api.FileList, params)); |
| 60 | * code: number; // 状态码 | 74 | * code: number; // 状态码 |
| 61 | * msg: string; // 消息 | 75 | * msg: string; // 消息 |
| 62 | * data: { | 76 | * data: { |
| 63 | - * list: Array<{ | 77 | + list: Array<{ |
| 64 | - * meta_id: integer; // 文件ID | 78 | + meta_id: integer; // 文件ID |
| 65 | - * name: string; // 文件名称 | 79 | + name: string; // 文件名称 |
| 66 | - * src: string; // 文件URL | 80 | + src: string; // 文件URL |
| 67 | - * size: string; // 文件大小 | 81 | + size: string; // 文件大小 |
| 68 | - * read_people_count: integer; // 学习人数 | 82 | + read_people_count: integer; // 学习人数 |
| 69 | - * read_people_percent: number; // 学习人数比例 | 83 | + read_people_percent: number; // 学习人数比例 |
| 70 | - * is_favorite: string; // | 84 | + is_favorite: string; // |
| 71 | - * }>; | 85 | + }>; |
| 72 | * }; | 86 | * }; |
| 73 | * }>} | 87 | * }>} |
| 74 | */ | 88 | */ | ... | ... |
| ... | @@ -15,33 +15,33 @@ const Api = { | ... | @@ -15,33 +15,33 @@ const Api = { |
| 15 | * code: number; // 状态码 | 15 | * code: number; // 状态码 |
| 16 | * msg: string; // 消息 | 16 | * msg: string; // 消息 |
| 17 | * data: { | 17 | * data: { |
| 18 | - * id: integer; // 产品id | 18 | + id: integer; // 产品id |
| 19 | - * product_name: string; // 产品名 | 19 | + product_name: string; // 产品名 |
| 20 | - * recommend: string; // 推荐位: normal-普通, hot-热卖 | 20 | + recommend: string; // 推荐位: normal-普通, hot-热卖 |
| 21 | - * status: string; // | 21 | + status: string; // |
| 22 | - * created_by: integer; // | 22 | + created_by: integer; // |
| 23 | - * created_time: string; // | 23 | + created_time: string; // |
| 24 | - * updated_by: integer; // | 24 | + updated_by: integer; // |
| 25 | - * updated_time: string; // | 25 | + updated_time: string; // |
| 26 | - * form_sn: string; // 关联表单sn | 26 | + form_sn: string; // 关联表单sn |
| 27 | - * product_description: string; // 产品描述 | 27 | + product_description: string; // 产品描述 |
| 28 | - * categories: Array<{ | 28 | + categories: Array<{ |
| 29 | - * id: string; // 分类id | 29 | + id: string; // 分类id |
| 30 | - * name: string; // 分类名称 | 30 | + name: string; // 分类名称 |
| 31 | - * }>; | 31 | + }>; |
| 32 | - * tags: Array<{ | 32 | + tags: Array<{ |
| 33 | - * id: string; // 标签id | 33 | + id: string; // 标签id |
| 34 | - * name: string; // 标签名 | 34 | + name: string; // 标签名 |
| 35 | - * bg_color: string; // 标签背景色 | 35 | + bg_color: string; // 标签背景色 |
| 36 | - * text_color: string; // 标签文字色 | 36 | + text_color: string; // 标签文字色 |
| 37 | - * }>; | 37 | + }>; |
| 38 | - * documents: Array<{ | 38 | + documents: Array<{ |
| 39 | - * file_url: string; // 附件地址 | 39 | + file_url: string; // 附件地址 |
| 40 | - * file_name: string; // 附件名 | 40 | + file_name: string; // 附件名 |
| 41 | - * file_size: string; // 附件大小 | 41 | + file_size: string; // 附件大小 |
| 42 | - * file_size_formatted: string; // 附件大小(转换过显示) | 42 | + file_size_formatted: string; // 附件大小(转换过显示) |
| 43 | - * }>; | 43 | + }>; |
| 44 | - * cover_image: string; // 产品封面图 | 44 | + cover_image: string; // 产品封面图 |
| 45 | * }; | 45 | * }; |
| 46 | * }>} | 46 | * }>} |
| 47 | */ | 47 | */ |
| ... | @@ -61,21 +61,29 @@ export const detailAPI = (params) => fn(fetch.get(Api.Detail, params)); | ... | @@ -61,21 +61,29 @@ export const detailAPI = (params) => fn(fetch.get(Api.Detail, params)); |
| 61 | * code: number; // 状态码 | 61 | * code: number; // 状态码 |
| 62 | * msg: string; // 消息 | 62 | * msg: string; // 消息 |
| 63 | * data: { | 63 | * data: { |
| 64 | - * categories: Array<{ | 64 | + categories: Array<{ |
| 65 | - * id: integer; // 分类id | 65 | + id: integer; // 分类id |
| 66 | - * name: string; // 分类名 | 66 | + name: string; // 分类名 |
| 67 | - * }>; | 67 | + }>; |
| 68 | - * list: Array<{ | 68 | + list: Array<{ |
| 69 | - * id: integer; // 产品id | 69 | + id: integer; // 产品id |
| 70 | - * product_name: string; // 产品名 | 70 | + product_name: string; // 产品名 |
| 71 | - * recommend: string; // 推荐位: normal-普通, hot-热卖 | 71 | + recommend: string; // 推荐位: normal-普通, hot-热卖 |
| 72 | - * form_sn: string; // | 72 | + form_sn: string; // |
| 73 | - * created_time: string; // 创建时间 | 73 | + created_time: string; // 创建时间 |
| 74 | - * categories: array; // 产品所属分类 | 74 | + categories: Array<{ |
| 75 | - * tags: array; // 产品标签 | 75 | + id: string; // 分类id |
| 76 | - * cover_image: string; // 产品封面图 | 76 | + name: string; // 分类名 |
| 77 | - * }>; | 77 | + }>; |
| 78 | - * total: integer; // 产品总数 | 78 | + tags: Array<{ |
| 79 | + id: string; // 标签id | ||
| 80 | + name: string; // 标签名 | ||
| 81 | + bg_color: string; // 标签背景色 | ||
| 82 | + text_color: string; // 标签文字色 | ||
| 83 | + }>; | ||
| 84 | + cover_image: string; // 产品封面图 | ||
| 85 | + }>; | ||
| 86 | + total: integer; // 产品总数 | ||
| 79 | * }; | 87 | * }; |
| 80 | * }>} | 88 | * }>} |
| 81 | */ | 89 | */ | ... | ... |
| ... | @@ -12,11 +12,11 @@ const Api = { | ... | @@ -12,11 +12,11 @@ const Api = { |
| 12 | * code: number; // 状态码 | 12 | * code: number; // 状态码 |
| 13 | * msg: string; // 消息 | 13 | * msg: string; // 消息 |
| 14 | * data: Array<{ | 14 | * data: Array<{ |
| 15 | - * id: integer; // | 15 | + id: integer; // |
| 16 | - * name: string; // | 16 | + name: string; // |
| 17 | - * seq: integer; // | 17 | + seq: integer; // |
| 18 | - * link: string; // | 18 | + link: string; // |
| 19 | - * icon: string; // | 19 | + icon: string; // |
| 20 | * }>; | 20 | * }>; |
| 21 | * }>} | 21 | * }>} |
| 22 | */ | 22 | */ | ... | ... |
| ... | @@ -14,10 +14,10 @@ const Api = { | ... | @@ -14,10 +14,10 @@ const Api = { |
| 14 | * code: number; // 状态码 | 14 | * code: number; // 状态码 |
| 15 | * msg: string; // 消息 | 15 | * msg: string; // 消息 |
| 16 | * data: Array<{ | 16 | * data: Array<{ |
| 17 | - * id: integer; // 消息id | 17 | + id: integer; // 消息id |
| 18 | - * note: string; // 消息内容 | 18 | + note: string; // 消息内容 |
| 19 | - * created_time: string; // 发消息的时间 | 19 | + created_time: string; // 发消息的时间 |
| 20 | - * status: string; // send=以发送未读取,read=已读取 | 20 | + status: string; // send=以发送未读取,read=已读取 |
| 21 | * }>; | 21 | * }>; |
| 22 | * }>} | 22 | * }>} |
| 23 | */ | 23 | */ |
| ... | @@ -33,10 +33,10 @@ export const detailAPI = (params) => fn(fetch.get(Api.Detail, params)); | ... | @@ -33,10 +33,10 @@ export const detailAPI = (params) => fn(fetch.get(Api.Detail, params)); |
| 33 | * code: number; // 状态码 | 33 | * code: number; // 状态码 |
| 34 | * msg: string; // 消息 | 34 | * msg: string; // 消息 |
| 35 | * data: Array<{ | 35 | * data: Array<{ |
| 36 | - * id: integer; // 消息id | 36 | + id: integer; // 消息id |
| 37 | - * note: string; // 消息内容 | 37 | + note: string; // 消息内容 |
| 38 | - * created_time: string; // 发消息的时间 | 38 | + created_time: string; // 发消息的时间 |
| 39 | - * status: string; // send=以发送未读取,read=已读取 | 39 | + status: string; // send=以发送未读取,read=已读取 |
| 40 | * }>; | 40 | * }>; |
| 41 | * }>} | 41 | * }>} |
| 42 | */ | 42 | */ | ... | ... |
src/api/search.js
0 → 100644
| 1 | +import { fn, fetch } from '@/api/fn'; | ||
| 2 | + | ||
| 3 | +const Api = { | ||
| 4 | + Search: '/srv/?a=search&t=icon', | ||
| 5 | +} | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * @description 搜索 | ||
| 9 | + * @remark | ||
| 10 | + * @param {Object} params 请求参数 | ||
| 11 | + * @param {string} params.keyword (可选) | ||
| 12 | + * @param {string} params.type (可选) product=产品,file=文档 | ||
| 13 | + * @returns {Promise<{ | ||
| 14 | + * code: number; // 状态码 | ||
| 15 | + * msg: string; // 消息 | ||
| 16 | + * data: { | ||
| 17 | + products: { | ||
| 18 | + list: Array<{ | ||
| 19 | + id: integer; // 产品id | ||
| 20 | + product_name: string; // 产品名 | ||
| 21 | + product_description: string; // 产品描述 | ||
| 22 | + recommend: string; // normal-普通, hot-热卖 | ||
| 23 | + created_time: string; // 创建时间 | ||
| 24 | + cover_image: string; // 封面图 | ||
| 25 | + tags: Array<{ | ||
| 26 | + id: string; // 标签id | ||
| 27 | + name: string; // 标签名 | ||
| 28 | + bg_color: string; // 标签背景色 | ||
| 29 | + text_color: string; // 标签文字色 | ||
| 30 | + }>; | ||
| 31 | + type: string; // | ||
| 32 | + form_sn: string; // 表单类型 | ||
| 33 | + }>; | ||
| 34 | + total: integer; // 产品总数 | ||
| 35 | + }; | ||
| 36 | + files: { | ||
| 37 | + list: Array<{ | ||
| 38 | + id: integer; // 附件id | ||
| 39 | + name: string; // 附件名 | ||
| 40 | + value: string; // 附件地址 | ||
| 41 | + extension: string; // 附件类型 | ||
| 42 | + post_date: string; // 发布时间 | ||
| 43 | + size: string; // 附件大小 | ||
| 44 | + is_favorite: integer; // 是否收藏 | ||
| 45 | + type: string; // | ||
| 46 | + }>; | ||
| 47 | + total: integer; // 附件数 | ||
| 48 | + }; | ||
| 49 | + * }; | ||
| 50 | + * }>} | ||
| 51 | + */ | ||
| 52 | +export const searchAPI = (params) => fn(fetch.get(Api.Search, params)); |
| ... | @@ -16,12 +16,19 @@ const Api = { | ... | @@ -16,12 +16,19 @@ const Api = { |
| 16 | * code: number; // 状态码 | 16 | * code: number; // 状态码 |
| 17 | * msg: string; // 消息 | 17 | * msg: string; // 消息 |
| 18 | * data: { | 18 | * data: { |
| 19 | - * user: { | 19 | + user: { |
| 20 | - * id: integer; // 用户ID | 20 | + id: integer; // 用户ID |
| 21 | - * name: string; // 姓名 | 21 | + name: string; // 姓名 |
| 22 | - * employee_no: string; // 工号 | 22 | + employee_no: string; // 工号 |
| 23 | - * avatar: object; // 头像 | 23 | + avatar: { |
| 24 | - * }; | 24 | + name: string; // 文件名 |
| 25 | + hash: string; // 文件hash | ||
| 26 | + src: string; // 文件地址 | ||
| 27 | + height: string; // 文件高度 | ||
| 28 | + width: string; // 文件宽度 | ||
| 29 | + size: integer; // 文件大小 | ||
| 30 | + }; | ||
| 31 | + }; | ||
| 25 | * }; | 32 | * }; |
| 26 | * }>} | 33 | * }>} |
| 27 | */ | 34 | */ |
| ... | @@ -49,8 +56,8 @@ export const loginAPI = (params) => fn(fetch.post(Api.Login, params)); | ... | @@ -49,8 +56,8 @@ export const loginAPI = (params) => fn(fetch.post(Api.Login, params)); |
| 49 | * code: number; // 状态码 | 56 | * code: number; // 状态码 |
| 50 | * msg: string; // 消息 | 57 | * msg: string; // 消息 |
| 51 | * data: { | 58 | * data: { |
| 52 | - * is_login: boolean; // true=登录,false=未登录 | 59 | + is_login: boolean; // true=登录,false=未登录 |
| 53 | - * is_openid: boolean; // true=已授权,false=未授权 | 60 | + is_openid: boolean; // true=已授权,false=未授权 |
| 54 | * }; | 61 | * }; |
| 55 | * }>} | 62 | * }>} |
| 56 | */ | 63 | */ | ... | ... |
| ... | @@ -15,11 +15,11 @@ const Api = { | ... | @@ -15,11 +15,11 @@ const Api = { |
| 15 | * code: number; // 状态码 | 15 | * code: number; // 状态码 |
| 16 | * msg: string; // 消息 | 16 | * msg: string; // 消息 |
| 17 | * data: { | 17 | * data: { |
| 18 | - * user: { | 18 | + user: { |
| 19 | - * id: integer; // 用户ID | 19 | + id: integer; // 用户ID |
| 20 | - * avatar_url: string; // 头像 | 20 | + avatar_url: string; // 头像 |
| 21 | - * name: string; // 姓名 | 21 | + name: string; // 姓名 |
| 22 | - * }; | 22 | + }; |
| 23 | * }; | 23 | * }; |
| 24 | * }>} | 24 | * }>} |
| 25 | */ | 25 | */ | ... | ... |
src/components/PlanFields/AmountInput.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div> | ||
| 3 | + <!-- 标签 --> | ||
| 4 | + <div v-if="label" class="text-sm text-gray-600 mb-2"> | ||
| 5 | + {{ label }} | ||
| 6 | + <span v-if="currencyText" class="text-gray-500">({{ currencyText }})</span> | ||
| 7 | + </div> | ||
| 8 | + | ||
| 9 | + <!-- 多币种模式(方案 2 - 未来扩展) --> | ||
| 10 | + <div v-if="multiCurrencyEnabled" class="mb-2"> | ||
| 11 | + <div class="text-sm text-gray-600 mb-2">币种</div> | ||
| 12 | + <div class="flex gap-2"> | ||
| 13 | + <button | ||
| 14 | + v-for="curr in supportedCurrencies" | ||
| 15 | + :key="curr.value" | ||
| 16 | + :class="[ | ||
| 17 | + 'px-4 py-2 rounded-lg text-sm border transition-colors', | ||
| 18 | + selectedCurrency === curr.value | ||
| 19 | + ? 'bg-blue-600 text-white border-blue-600' | ||
| 20 | + : 'bg-white text-gray-600 border-gray-200' | ||
| 21 | + ]" | ||
| 22 | + @tap="selectCurrency(curr.value)" | ||
| 23 | + > | ||
| 24 | + {{ curr.label }} | ||
| 25 | + </button> | ||
| 26 | + </div> | ||
| 27 | + </div> | ||
| 28 | + | ||
| 29 | + <!-- 保额输入 --> | ||
| 30 | + <div class="border border-gray-200 rounded-lg flex items-center overflow-hidden"> | ||
| 31 | + <nut-input | ||
| 32 | + :model-value="formattedValue" | ||
| 33 | + @input="onInput" | ||
| 34 | + type="digit" | ||
| 35 | + :placeholder="placeholder" | ||
| 36 | + class="!p-0 !bg-transparent flex-1 !text-sm !text-gray-900" | ||
| 37 | + :border="false" | ||
| 38 | + /> | ||
| 39 | + <span class="text-sm text-gray-500 shrink-0 ml-2 mr-5">{{ currencySymbol }}</span> | ||
| 40 | + </div> | ||
| 41 | + </div> | ||
| 42 | +</template> | ||
| 43 | + | ||
| 44 | +<script setup> | ||
| 45 | +/** | ||
| 46 | + * 保额输入组件 | ||
| 47 | + * | ||
| 48 | + * @description 支持多币种的保额输入组件 | ||
| 49 | + * - 单位转换:内部存储为分(整数),显示为元(带2位小数) | ||
| 50 | + * - 币种支持:CNY、USD、HKD、EUR | ||
| 51 | + * - 多币种模式:通过 FEATURE_FLAGS.MULTI_CURRENCY_ENABLED 控制 | ||
| 52 | + * @author Claude Code | ||
| 53 | + * @example | ||
| 54 | + * <!-- 固定币种模式 --> | ||
| 55 | + * <AmountInput | ||
| 56 | + * v-model="coverage" | ||
| 57 | + * label="保额" | ||
| 58 | + * currency="USD" | ||
| 59 | + * placeholder="请输入保额" | ||
| 60 | + * /> | ||
| 61 | + * | ||
| 62 | + * @example | ||
| 63 | + * <!-- 多币种模式 --> | ||
| 64 | + * <AmountInput | ||
| 65 | + * v-model="coverage" | ||
| 66 | + * label="保额" | ||
| 67 | + * :config="{ supported_currencies: ['CNY', 'USD'], default_currency: 'CNY' }" | ||
| 68 | + * placeholder="请输入保额" | ||
| 69 | + * /> | ||
| 70 | + */ | ||
| 71 | +import { ref, computed } from 'vue' | ||
| 72 | +import { FEATURE_FLAGS, CURRENCY_SYMBOLS, CURRENCY_MAP } from '@/config/plan-templates' | ||
| 73 | + | ||
| 74 | +/** | ||
| 75 | + * 组件属性 | ||
| 76 | + */ | ||
| 77 | +const props = defineProps({ | ||
| 78 | + /** | ||
| 79 | + * 标签文本 | ||
| 80 | + * @type {string} | ||
| 81 | + */ | ||
| 82 | + label: { | ||
| 83 | + type: String, | ||
| 84 | + default: '' | ||
| 85 | + }, | ||
| 86 | + | ||
| 87 | + /** | ||
| 88 | + * 占位符文本 | ||
| 89 | + * @type {string} | ||
| 90 | + */ | ||
| 91 | + placeholder: { | ||
| 92 | + type: String, | ||
| 93 | + default: '请输入保额' | ||
| 94 | + }, | ||
| 95 | + | ||
| 96 | + /** | ||
| 97 | + * 绑定的值(单位:分) | ||
| 98 | + * @type {number} | ||
| 99 | + * @example 100000 表示 1000.00 元 | ||
| 100 | + */ | ||
| 101 | + modelValue: { | ||
| 102 | + type: Number, | ||
| 103 | + default: null | ||
| 104 | + }, | ||
| 105 | + | ||
| 106 | + /** | ||
| 107 | + * 币种代码(固定币种模式) | ||
| 108 | + * @type {string} | ||
| 109 | + * @default 'CNY' | ||
| 110 | + */ | ||
| 111 | + currency: { | ||
| 112 | + type: String, | ||
| 113 | + default: 'CNY' | ||
| 114 | + }, | ||
| 115 | + | ||
| 116 | + /** | ||
| 117 | + * 模版配置(多币种模式) | ||
| 118 | + * @type {Object} | ||
| 119 | + * @property {Array<string>} supported_currencies - 支持的币种代码数组 | ||
| 120 | + * @property {string} default_currency - 默认币种代码 | ||
| 121 | + * @example { supported_currencies: ['CNY', 'USD'], default_currency: 'CNY' } | ||
| 122 | + */ | ||
| 123 | + config: { | ||
| 124 | + type: Object, | ||
| 125 | + default: () => ({}) | ||
| 126 | + } | ||
| 127 | +}) | ||
| 128 | + | ||
| 129 | +/** | ||
| 130 | + * 组件事件 | ||
| 131 | + */ | ||
| 132 | +const emit = defineEmits([ | ||
| 133 | + /** | ||
| 134 | + * 更新值事件 | ||
| 135 | + * @event update:modelValue | ||
| 136 | + * @param {number} value - 保额值(单位:分) | ||
| 137 | + */ | ||
| 138 | + 'update:modelValue' | ||
| 139 | +]) | ||
| 140 | + | ||
| 141 | +/** | ||
| 142 | + * 判断是否启用多币种 | ||
| 143 | + * @type {ComputedRef<boolean>} | ||
| 144 | + */ | ||
| 145 | +const multiCurrencyEnabled = computed(() => FEATURE_FLAGS.MULTI_CURRENCY_ENABLED) | ||
| 146 | + | ||
| 147 | +/** | ||
| 148 | + * 当前选中的币种 | ||
| 149 | + * @type {Ref<string>} | ||
| 150 | + */ | ||
| 151 | +const selectedCurrency = ref(props.config.default_currency || props.currency || 'CNY') | ||
| 152 | + | ||
| 153 | +/** | ||
| 154 | + * 支持的币种列表(多币种模式) | ||
| 155 | + * @type {ComputedRef<Array<{label: string, symbol: string, value: string}>>} | ||
| 156 | + */ | ||
| 157 | +const supportedCurrencies = computed(() => { | ||
| 158 | + if (!multiCurrencyEnabled.value) return [] | ||
| 159 | + | ||
| 160 | + return (props.config.supported_currencies || ['CNY']) | ||
| 161 | + .map(code => CURRENCY_MAP[code]) | ||
| 162 | + .filter(Boolean) | ||
| 163 | +}) | ||
| 164 | + | ||
| 165 | +/** | ||
| 166 | + * 当前币种符号 | ||
| 167 | + * @type {ComputedRef<string>} | ||
| 168 | + * @example | ||
| 169 | + * // CNY -> '¥' | ||
| 170 | + * // USD -> '$' | ||
| 171 | + */ | ||
| 172 | +const currencySymbol = computed(() => { | ||
| 173 | + if (multiCurrencyEnabled.value) { | ||
| 174 | + // 多币种模式:使用用户选择的币种 | ||
| 175 | + const curr = supportedCurrencies.value.find(c => c.value === selectedCurrency.value) | ||
| 176 | + return curr?.symbol || '¥' | ||
| 177 | + } | ||
| 178 | + | ||
| 179 | + // 固定币种模式:使用 props.currency | ||
| 180 | + return CURRENCY_SYMBOLS[props.currency] || '¥' | ||
| 181 | +}) | ||
| 182 | + | ||
| 183 | +/** | ||
| 184 | + * 币种文本(用于标签显示) | ||
| 185 | + * @type {ComputedRef<string>} | ||
| 186 | + */ | ||
| 187 | +const currencyText = computed(() => { | ||
| 188 | + if (multiCurrencyEnabled.value) { | ||
| 189 | + const curr = supportedCurrencies.value.find(c => c.value === selectedCurrency.value) | ||
| 190 | + return curr?.label || '' | ||
| 191 | + } | ||
| 192 | + | ||
| 193 | + const CURRENCY_NAMES = { | ||
| 194 | + CNY: '人民币', | ||
| 195 | + USD: '美元', | ||
| 196 | + HKD: '港币', | ||
| 197 | + EUR: '欧元' | ||
| 198 | + } | ||
| 199 | + | ||
| 200 | + return CURRENCY_NAMES[props.currency] || '' | ||
| 201 | +}) | ||
| 202 | + | ||
| 203 | +/** | ||
| 204 | + * 格式化显示值(元,带2位小数) | ||
| 205 | + * @description 将分转换为元进行显示 | ||
| 206 | + * @type {ComputedRef<string>} | ||
| 207 | + * @example | ||
| 208 | + * // modelValue = 100000 (分) | ||
| 209 | + * // formattedValue() // 返回: '1000.00' | ||
| 210 | + */ | ||
| 211 | +const formattedValue = computed(() => { | ||
| 212 | + if (props.modelValue === null || props.modelValue === undefined) { | ||
| 213 | + return '' | ||
| 214 | + } | ||
| 215 | + // 分 -> 元,保留2位小数 | ||
| 216 | + return (props.modelValue / 100).toFixed(2) | ||
| 217 | +}) | ||
| 218 | + | ||
| 219 | +/** | ||
| 220 | + * 用户输入处理 | ||
| 221 | + * @description 将用户输入的元转换为分存储 | ||
| 222 | + * @param {string} value - 输入值 | ||
| 223 | + * | ||
| 224 | + * @example | ||
| 225 | + * // 用户输入: '1000.50' | ||
| 226 | + * // onInput('1000.50') | ||
| 227 | + * // -> emit('update:modelValue', 100050) // 分 | ||
| 228 | + */ | ||
| 229 | +const onInput = (value) => { | ||
| 230 | + // 移除非数字和小数点 | ||
| 231 | + const cleanValue = value.replace(/[^\d.]/g, '') | ||
| 232 | + | ||
| 233 | + // 转换为分(整数) | ||
| 234 | + const yuan = parseFloat(cleanValue) | ||
| 235 | + if (!Number.isNaN(yuan)) { | ||
| 236 | + emit('update:modelValue', Math.round(yuan * 100)) | ||
| 237 | + } else { | ||
| 238 | + emit('update:modelValue', 0) | ||
| 239 | + } | ||
| 240 | +} | ||
| 241 | + | ||
| 242 | +/** | ||
| 243 | + * 选择币种(多币种模式) | ||
| 244 | + * @param {string} value - 币种代码 | ||
| 245 | + */ | ||
| 246 | +const selectCurrency = (value) => { | ||
| 247 | + selectedCurrency.value = value | ||
| 248 | +} | ||
| 249 | +</script> | ||
| 250 | + | ||
| 251 | +<style lang="less" scoped> | ||
| 252 | +/* 组件样式 */ | ||
| 253 | +</style> |
-
Please register or login to post a comment