mirror of
https://github.com/certd/certd.git
synced 2026-07-30 01:07:38 +08:00
Merge branch 'v2-invite' of https://github.com/certd/certd into v2-invite
This commit is contained in:
@@ -177,6 +177,7 @@ Certd 是一个支持私有化部署的 SSL/TLS 证书自动化管理平台。
|
|||||||
- 优先沿用现有模块、插件、服务模式,再考虑新增抽象。
|
- 优先沿用现有模块、插件、服务模式,再考虑新增抽象。
|
||||||
- `packages/ui/certd-server/data/`、`logs/`、生成的 metadata/dist 等通常视为运行时或构建产物,除非任务明确要求处理它们。
|
- `packages/ui/certd-server/data/`、`logs/`、生成的 metadata/dist 等通常视为运行时或构建产物,除非任务明确要求处理它们。
|
||||||
- 注意本地数据和配置里可能包含凭据、证书材料等敏感信息。
|
- 注意本地数据和配置里可能包含凭据、证书材料等敏感信息。
|
||||||
|
- 使用 `/basic/file/upload` 上传文件后,接口返回的是临时缓存 key。业务保存表单或设置时,后端必须调用 `FileService.saveFile(userId, key, "public" | "private")` 转成永久文件 key 后再入库/入设置;不要直接保存 `tmpfile_key_...`,否则后续回显或下载会失效。
|
||||||
- 本仓库代码注释优先使用中文,尤其是解释业务规则、兼容逻辑、协议细节和隐藏风险时;除非文件已有明确英文注释风格或引用外部英文术语,否则不要新增英文说明性注释。
|
- 本仓库代码注释优先使用中文,尤其是解释业务规则、兼容逻辑、协议细节和隐藏风险时;除非文件已有明确英文注释风格或引用外部英文术语,否则不要新增英文说明性注释。
|
||||||
- 代码可读性优先于短写法。遇到包含业务分支的复杂三元表达式、内联对象、链式调用或条件组合时,优先拆成命名清晰的中间变量、独立分支或小函数,让读代码的人能一眼看出业务意图;不要为了少写几行把逻辑压成难读的一坨。
|
- 代码可读性优先于短写法。遇到包含业务分支的复杂三元表达式、内联对象、链式调用或条件组合时,优先拆成命名清晰的中间变量、独立分支或小函数,让读代码的人能一眼看出业务意图;不要为了少写几行把逻辑压成难读的一坨。
|
||||||
|
|
||||||
@@ -232,4 +233,3 @@ Get-ChildItem packages\ui\certd-client\src\views\certd
|
|||||||
- 优先对改动包运行聚焦的测试;后端可按包运行单元测试,前端优先使用 Prettier/ESLint 做改动文件验证。只有跨包影响明显时再考虑全 monorepo 构建。
|
- 优先对改动包运行聚焦的测试;后端可按包运行单元测试,前端优先使用 Prettier/ESLint 做改动文件验证。只有跨包影响明显时再考虑全 monorepo 构建。
|
||||||
|
|
||||||
- 不要主动运行 `pnpm install` 安装依赖:用户会事先准备好 `node_modules`。如果 `pnpm install` 或 `test:unit` 因缺少依赖、TTY 或网络问题失败,立即停止尝试,告知用户解决环境问题。
|
- 不要主动运行 `pnpm install` 安装依赖:用户会事先准备好 `node_modules`。如果 `pnpm install` 或 `test:unit` 因缺少依赖、TTY 或网络问题失败,立即停止尝试,告知用户解决环境问题。
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,17 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.40.4](https://github.com/certd/certd/compare/v1.40.3...v1.40.4) (2026-05-24)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **pipeline-service:** 修复流水线运行时超过套餐部署次数仍然能够正常运行的bug ([5e59651](https://github.com/certd/certd/commit/5e59651d45bc91919629e35995ff1b3cff6b87ea))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 商业版套餐只支持设置为可叠加 ([5e72f75](https://github.com/certd/certd/commit/5e72f75395fb632a30e80c07d35d8ba40ef631fa))
|
||||||
|
* 新增阿里云直播证书部署插件 ([8edb6f8](https://github.com/certd/certd/commit/8edb6f8727bd148f106801bef25567880fd35e9e))
|
||||||
|
|
||||||
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -3,6 +3,17 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.40.4](https://github.com/certd/certd/compare/v1.40.3...v1.40.4) (2026-05-24)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **pipeline-service:** 修复流水线运行时超过套餐部署次数仍然能够正常运行的bug ([5e59651](https://github.com/certd/certd/commit/5e59651d45bc91919629e35995ff1b3cff6b87ea))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 商业版套餐只支持设置为可叠加 ([5e72f75](https://github.com/certd/certd/commit/5e72f75395fb632a30e80c07d35d8ba40ef631fa))
|
||||||
|
* 新增阿里云直播证书部署插件 ([8edb6f8](https://github.com/certd/certd/commit/8edb6f8727bd148f106801bef25567880fd35e9e))
|
||||||
|
|
||||||
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# 任务插件
|
# 任务插件
|
||||||
共 `131` 款任务插件
|
共 `132` 款任务插件
|
||||||
## 1. 证书申请
|
## 1. 证书申请
|
||||||
|
|
||||||
| 序号 | 名称 | 说明 |
|
| 序号 | 名称 | 说明 |
|
||||||
@@ -93,13 +93,14 @@
|
|||||||
| 9.| **阿里云-部署至ESA** | 部署证书到阿里云ESA(边缘安全加速),自动删除过期证书 |
|
| 9.| **阿里云-部署至ESA** | 部署证书到阿里云ESA(边缘安全加速),自动删除过期证书 |
|
||||||
| 10.| **阿里云-部署至阿里云FC(3.0)** | 部署证书到阿里云函数计算(FC3.0) |
|
| 10.| **阿里云-部署至阿里云FC(3.0)** | 部署证书到阿里云函数计算(FC3.0) |
|
||||||
| 11.| **阿里云-部署至GA** | 部署证书到阿里云GA(全球加速),支持更新默认证书和扩展证书 |
|
| 11.| **阿里云-部署至GA** | 部署证书到阿里云GA(全球加速),支持更新默认证书和扩展证书 |
|
||||||
| 12.| **阿里云-部署至NLB(网络负载均衡)** | NLB,网络负载均衡,更新监听器的默认证书 |
|
| 12.| **阿里云-部署至直播(Live)** | 部署证书到阿里云视频直播(Live)域名 |
|
||||||
| 13.| **阿里云-部署证书至OSS** | 部署域名证书至阿里云OSS自定义域名,不是上传到阿里云oss |
|
| 13.| **阿里云-部署至NLB(网络负载均衡)** | NLB,网络负载均衡,更新监听器的默认证书 |
|
||||||
| 14.| **阿里云-部署至CLB(传统负载均衡)** | 部署证书到阿里云CLB(传统负载均衡) |
|
| 14.| **阿里云-部署证书至OSS** | 部署域名证书至阿里云OSS自定义域名,不是上传到阿里云oss |
|
||||||
| 15.| **阿里云-部署至VOD** | 部署证书到阿里云视频点播(vod) |
|
| 15.| **阿里云-部署至CLB(传统负载均衡)** | 部署证书到阿里云CLB(传统负载均衡) |
|
||||||
| 16.| **阿里云-部署至阿里云WAF(云产品接入)** | 部署证书到阿里云WAF(云产品接入),CNAME方式接入的请选择另外一个waf插件 |
|
| 16.| **阿里云-部署至VOD** | 部署证书到阿里云视频点播(vod) |
|
||||||
| 17.| **阿里云-部署至阿里云WAF(cname接入)** | 部署证书到阿里云WAF(cname接入),云资源的请选择另外一个waf插件 |
|
| 17.| **阿里云-部署至阿里云WAF(云产品接入)** | 部署证书到阿里云WAF(云产品接入),CNAME方式接入的请选择另外一个waf插件 |
|
||||||
| 18.| **阿里云-上传证书到CAS** | 上传证书到阿里云证书管理服务(CAS),如果不想在阿里云上同一份证书上传多次,可以把此任务作为前置任务,其他阿里云任务证书那一项选择此任务的输出 |
|
| 18.| **阿里云-部署至阿里云WAF(cname接入)** | 部署证书到阿里云WAF(cname接入),云资源的请选择另外一个waf插件 |
|
||||||
|
| 19.| **阿里云-上传证书到CAS** | 上传证书到阿里云证书管理服务(CAS),如果不想在阿里云上同一份证书上传多次,可以把此任务作为前置任务,其他阿里云任务证书那一项选择此任务的输出 |
|
||||||
## 6. 华为云
|
## 6. 华为云
|
||||||
|
|
||||||
| 序号 | 名称 | 说明 |
|
| 序号 | 名称 | 说明 |
|
||||||
|
|||||||
+1
-1
@@ -9,5 +9,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"npmClient": "pnpm",
|
"npmClient": "pnpm",
|
||||||
"version": "1.40.3"
|
"version": "1.40.4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.40.4](https://github.com/publishlab/node-acme-client/compare/v1.40.3...v1.40.4) (2026-05-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/acme-client
|
||||||
|
|
||||||
## [1.40.3](https://github.com/publishlab/node-acme-client/compare/v1.40.2...v1.40.3) (2026-05-21)
|
## [1.40.3](https://github.com/publishlab/node-acme-client/compare/v1.40.2...v1.40.3) (2026-05-21)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/acme-client
|
**Note:** Version bump only for package @certd/acme-client
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"description": "Simple and unopinionated ACME client",
|
"description": "Simple and unopinionated ACME client",
|
||||||
"private": false,
|
"private": false,
|
||||||
"author": "nmorsman",
|
"author": "nmorsman",
|
||||||
"version": "1.40.3",
|
"version": "1.40.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"types"
|
"types"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/basic": "^1.40.3",
|
"@certd/basic": "^1.40.4",
|
||||||
"@peculiar/x509": "^1.11.0",
|
"@peculiar/x509": "^1.11.0",
|
||||||
"asn1js": "^3.0.5",
|
"asn1js": "^3.0.5",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
@@ -76,5 +76,5 @@
|
|||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/publishlab/node-acme-client/issues"
|
"url": "https://github.com/publishlab/node-acme-client/issues"
|
||||||
},
|
},
|
||||||
"gitHead": "01c91ba294f88bd07fddf9358c4301bbb4027916"
|
"gitHead": "4a09cf289d3f360c830f1a203fa11baa6af20f1c"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.40.4](https://github.com/certd/certd/compare/v1.40.3...v1.40.4) (2026-05-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/basic
|
||||||
|
|
||||||
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/basic
|
**Note:** Version bump only for package @certd/basic
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
22:57
|
00:18
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/basic",
|
"name": "@certd/basic",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.40.3",
|
"version": "1.40.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -52,5 +52,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "01c91ba294f88bd07fddf9358c4301bbb4027916"
|
"gitHead": "4a09cf289d3f360c830f1a203fa11baa6af20f1c"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.40.4](https://github.com/certd/certd/compare/v1.40.3...v1.40.4) (2026-05-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/pipeline
|
||||||
|
|
||||||
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/pipeline
|
**Note:** Version bump only for package @certd/pipeline
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/pipeline",
|
"name": "@certd/pipeline",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.40.3",
|
"version": "1.40.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -19,8 +19,8 @@
|
|||||||
"compile": "tsc --skipLibCheck --watch"
|
"compile": "tsc --skipLibCheck --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/basic": "^1.40.3",
|
"@certd/basic": "^1.40.4",
|
||||||
"@certd/plus-core": "^1.40.3",
|
"@certd/plus-core": "^1.40.4",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"reflect-metadata": "^0.1.13"
|
"reflect-metadata": "^0.1.13"
|
||||||
@@ -49,5 +49,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "01c91ba294f88bd07fddf9358c4301bbb4027916"
|
"gitHead": "4a09cf289d3f360c830f1a203fa11baa6af20f1c"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.40.4](https://github.com/certd/certd/compare/v1.40.3...v1.40.4) (2026-05-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-huawei
|
||||||
|
|
||||||
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/lib-huawei
|
**Note:** Version bump only for package @certd/lib-huawei
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-huawei",
|
"name": "@certd/lib-huawei",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.40.3",
|
"version": "1.40.4",
|
||||||
"main": "./dist/bundle.js",
|
"main": "./dist/bundle.js",
|
||||||
"module": "./dist/bundle.js",
|
"module": "./dist/bundle.js",
|
||||||
"types": "./dist/d/index.d.ts",
|
"types": "./dist/d/index.d.ts",
|
||||||
@@ -27,5 +27,5 @@
|
|||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"tslib": "^2.8.1"
|
"tslib": "^2.8.1"
|
||||||
},
|
},
|
||||||
"gitHead": "01c91ba294f88bd07fddf9358c4301bbb4027916"
|
"gitHead": "4a09cf289d3f360c830f1a203fa11baa6af20f1c"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.40.4](https://github.com/certd/certd/compare/v1.40.3...v1.40.4) (2026-05-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-iframe
|
||||||
|
|
||||||
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/lib-iframe
|
**Note:** Version bump only for package @certd/lib-iframe
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-iframe",
|
"name": "@certd/lib-iframe",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.40.3",
|
"version": "1.40.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -34,5 +34,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "01c91ba294f88bd07fddf9358c4301bbb4027916"
|
"gitHead": "4a09cf289d3f360c830f1a203fa11baa6af20f1c"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.40.4](https://github.com/certd/certd/compare/v1.40.3...v1.40.4) (2026-05-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/jdcloud
|
||||||
|
|
||||||
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/jdcloud
|
**Note:** Version bump only for package @certd/jdcloud
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/jdcloud",
|
"name": "@certd/jdcloud",
|
||||||
"version": "1.40.3",
|
"version": "1.40.4",
|
||||||
"description": "jdcloud openApi sdk",
|
"description": "jdcloud openApi sdk",
|
||||||
"main": "./dist/bundle.js",
|
"main": "./dist/bundle.js",
|
||||||
"module": "./dist/bundle.js",
|
"module": "./dist/bundle.js",
|
||||||
@@ -59,5 +59,5 @@
|
|||||||
"fetch"
|
"fetch"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"gitHead": "01c91ba294f88bd07fddf9358c4301bbb4027916"
|
"gitHead": "4a09cf289d3f360c830f1a203fa11baa6af20f1c"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.40.4](https://github.com/certd/certd/compare/v1.40.3...v1.40.4) (2026-05-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-k8s
|
||||||
|
|
||||||
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/lib-k8s
|
**Note:** Version bump only for package @certd/lib-k8s
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-k8s",
|
"name": "@certd/lib-k8s",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.40.3",
|
"version": "1.40.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
"compile": "tsc --skipLibCheck --watch"
|
"compile": "tsc --skipLibCheck --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/basic": "^1.40.3",
|
"@certd/basic": "^1.40.4",
|
||||||
"@kubernetes/client-node": "0.21.0"
|
"@kubernetes/client-node": "0.21.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -36,5 +36,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "01c91ba294f88bd07fddf9358c4301bbb4027916"
|
"gitHead": "4a09cf289d3f360c830f1a203fa11baa6af20f1c"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.40.4](https://github.com/certd/certd/compare/v1.40.3...v1.40.4) (2026-05-24)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 商业版套餐只支持设置为可叠加 ([5e72f75](https://github.com/certd/certd/commit/5e72f75395fb632a30e80c07d35d8ba40ef631fa))
|
||||||
|
|
||||||
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/lib-server
|
**Note:** Version bump only for package @certd/lib-server
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-server",
|
"name": "@certd/lib-server",
|
||||||
"version": "1.40.3",
|
"version": "1.40.4",
|
||||||
"description": "midway with flyway, sql upgrade way ",
|
"description": "midway with flyway, sql upgrade way ",
|
||||||
"private": false,
|
"private": false,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -29,11 +29,11 @@
|
|||||||
],
|
],
|
||||||
"license": "AGPL",
|
"license": "AGPL",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/acme-client": "^1.40.3",
|
"@certd/acme-client": "^1.40.4",
|
||||||
"@certd/basic": "^1.40.3",
|
"@certd/basic": "^1.40.4",
|
||||||
"@certd/pipeline": "^1.40.3",
|
"@certd/pipeline": "^1.40.4",
|
||||||
"@certd/plugin-lib": "^1.40.3",
|
"@certd/plugin-lib": "^1.40.4",
|
||||||
"@certd/plus-core": "^1.40.3",
|
"@certd/plus-core": "^1.40.4",
|
||||||
"@midwayjs/cache": "3.14.0",
|
"@midwayjs/cache": "3.14.0",
|
||||||
"@midwayjs/core": "3.20.11",
|
"@midwayjs/core": "3.20.11",
|
||||||
"@midwayjs/i18n": "3.20.13",
|
"@midwayjs/i18n": "3.20.13",
|
||||||
@@ -69,5 +69,5 @@
|
|||||||
"typeorm": "^0.3.11",
|
"typeorm": "^0.3.11",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "01c91ba294f88bd07fddf9358c4301bbb4027916"
|
"gitHead": "4a09cf289d3f360c830f1a203fa11baa6af20f1c"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ export const Constants = {
|
|||||||
guest: '_guest_',
|
guest: '_guest_',
|
||||||
//无需登录
|
//无需登录
|
||||||
anonymous: '_guest_',
|
anonymous: '_guest_',
|
||||||
|
//无需登录,有 token 时解析当前用户
|
||||||
|
guestOptionalAuth: '_guestOptionalAuth_',
|
||||||
//仅需要登录
|
//仅需要登录
|
||||||
authOnly: '_authOnly_',
|
authOnly: '_authOnly_',
|
||||||
//仅需要登录
|
//仅需要登录
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/// <reference types="mocha" />
|
||||||
|
/// <reference types="node" />
|
||||||
|
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import os from "node:os";
|
||||||
|
import path from "node:path";
|
||||||
|
import { FileService } from "./file-service.js";
|
||||||
|
|
||||||
|
function createUploadFile(key: string) {
|
||||||
|
const uploadRootDir = "./data/upload";
|
||||||
|
const filePath = path.join(uploadRootDir, key);
|
||||||
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||||
|
fs.writeFileSync(filePath, "test");
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("FileService.getFile", () => {
|
||||||
|
let cwd: string;
|
||||||
|
let oldCwd: string;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
oldCwd = process.cwd();
|
||||||
|
cwd = fs.mkdtempSync(path.join(os.tmpdir(), "certd-file-service-"));
|
||||||
|
process.chdir(cwd);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.chdir(oldCwd);
|
||||||
|
fs.rmSync(cwd, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows admin to read another user's private file", () => {
|
||||||
|
const service = new FileService();
|
||||||
|
const userIdMd5 = Buffer.from(Buffer.from("2").toString("base64")).toString("hex");
|
||||||
|
const key = `/private/${userIdMd5}/2026_05_25/qr.png`;
|
||||||
|
const expectedPath = createUploadFile(key);
|
||||||
|
|
||||||
|
const filePath = service.getFile(key, 1, true);
|
||||||
|
|
||||||
|
assert.equal(filePath, expectedPath);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -56,7 +56,7 @@ export class FileService {
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFile(key: string, userId?: number) {
|
getFile(key: string, userId?: number, allowAnyPrivateUser = false) {
|
||||||
if (!key) {
|
if (!key) {
|
||||||
throw new ParamException('参数错误');
|
throw new ParamException('参数错误');
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,7 @@ export class FileService {
|
|||||||
const keyArr = key.split('/');
|
const keyArr = key.split('/');
|
||||||
const permission = keyArr[1];
|
const permission = keyArr[1];
|
||||||
const userIdMd5 = keyArr[2];
|
const userIdMd5 = keyArr[2];
|
||||||
if (permission !== 'public') {
|
if (permission !== 'public' && !allowAnyPrivateUser) {
|
||||||
//非公开文件需要验证用户
|
//非公开文件需要验证用户
|
||||||
const userIdStr = Buffer.from(Buffer.from(userIdMd5, 'hex').toString('base64')).toString();
|
const userIdStr = Buffer.from(Buffer.from(userIdMd5, 'hex').toString('base64')).toString();
|
||||||
const userIdInt: number = parseInt(userIdStr, 10);
|
const userIdInt: number = parseInt(userIdStr, 10);
|
||||||
|
|||||||
@@ -245,6 +245,8 @@ export class SysSuiteSetting extends BaseSettings {
|
|||||||
|
|
||||||
enabled: boolean = false;
|
enabled: boolean = false;
|
||||||
|
|
||||||
|
allowSuiteStack: boolean = false;
|
||||||
|
|
||||||
registerGift?: {
|
registerGift?: {
|
||||||
productId: number;
|
productId: number;
|
||||||
duration: number;
|
duration: number;
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.40.4](https://github.com/certd/certd/compare/v1.40.3...v1.40.4) (2026-05-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||||
|
|
||||||
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/midway-flyway-js
|
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/midway-flyway-js",
|
"name": "@certd/midway-flyway-js",
|
||||||
"version": "1.40.3",
|
"version": "1.40.4",
|
||||||
"description": "midway with flyway, sql upgrade way ",
|
"description": "midway with flyway, sql upgrade way ",
|
||||||
"private": false,
|
"private": false,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -49,5 +49,5 @@
|
|||||||
"typeorm": "^0.3.11",
|
"typeorm": "^0.3.11",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "01c91ba294f88bd07fddf9358c4301bbb4027916"
|
"gitHead": "4a09cf289d3f360c830f1a203fa11baa6af20f1c"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.40.4](https://github.com/certd/certd/compare/v1.40.3...v1.40.4) (2026-05-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/plugin-cert
|
||||||
|
|
||||||
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/plugin-cert
|
**Note:** Version bump only for package @certd/plugin-cert
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/plugin-cert",
|
"name": "@certd/plugin-cert",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.40.3",
|
"version": "1.40.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
@@ -18,10 +18,10 @@
|
|||||||
"compile": "tsc --skipLibCheck --watch"
|
"compile": "tsc --skipLibCheck --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/acme-client": "^1.40.3",
|
"@certd/acme-client": "^1.40.4",
|
||||||
"@certd/basic": "^1.40.3",
|
"@certd/basic": "^1.40.4",
|
||||||
"@certd/pipeline": "^1.40.3",
|
"@certd/pipeline": "^1.40.4",
|
||||||
"@certd/plugin-lib": "^1.40.3",
|
"@certd/plugin-lib": "^1.40.4",
|
||||||
"psl": "^1.9.0",
|
"psl": "^1.9.0",
|
||||||
"punycode.js": "^2.3.1"
|
"punycode.js": "^2.3.1"
|
||||||
},
|
},
|
||||||
@@ -41,5 +41,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "01c91ba294f88bd07fddf9358c4301bbb4027916"
|
"gitHead": "4a09cf289d3f360c830f1a203fa11baa6af20f1c"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.40.4](https://github.com/certd/certd/compare/v1.40.3...v1.40.4) (2026-05-24)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 新增阿里云直播证书部署插件 ([8edb6f8](https://github.com/certd/certd/commit/8edb6f8727bd148f106801bef25567880fd35e9e))
|
||||||
|
|
||||||
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
||||||
|
|
||||||
**Note:** Version bump only for package @certd/plugin-lib
|
**Note:** Version bump only for package @certd/plugin-lib
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/plugin-lib",
|
"name": "@certd/plugin-lib",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.40.3",
|
"version": "1.40.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
@@ -23,10 +23,10 @@
|
|||||||
"@alicloud/pop-core": "^1.7.10",
|
"@alicloud/pop-core": "^1.7.10",
|
||||||
"@alicloud/tea-util": "^1.4.11",
|
"@alicloud/tea-util": "^1.4.11",
|
||||||
"@aws-sdk/client-s3": "^3.964.0",
|
"@aws-sdk/client-s3": "^3.964.0",
|
||||||
"@certd/acme-client": "^1.40.3",
|
"@certd/acme-client": "^1.40.4",
|
||||||
"@certd/basic": "^1.40.3",
|
"@certd/basic": "^1.40.4",
|
||||||
"@certd/pipeline": "^1.40.3",
|
"@certd/pipeline": "^1.40.4",
|
||||||
"@certd/plus-core": "^1.40.3",
|
"@certd/plus-core": "^1.40.4",
|
||||||
"@kubernetes/client-node": "0.21.0",
|
"@kubernetes/client-node": "0.21.0",
|
||||||
"ali-oss": "^6.22.0",
|
"ali-oss": "^6.22.0",
|
||||||
"basic-ftp": "^5.0.5",
|
"basic-ftp": "^5.0.5",
|
||||||
@@ -61,5 +61,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "01c91ba294f88bd07fddf9358c4301bbb4027916"
|
"gitHead": "4a09cf289d3f360c830f1a203fa11baa6af20f1c"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.40.4](https://github.com/certd/certd/compare/v1.40.3...v1.40.4) (2026-05-24)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 商业版套餐只支持设置为可叠加 ([5e72f75](https://github.com/certd/certd/commit/5e72f75395fb632a30e80c07d35d8ba40ef631fa))
|
||||||
|
|
||||||
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/ui-client",
|
"name": "@certd/ui-client",
|
||||||
"version": "1.40.3",
|
"version": "1.40.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --open",
|
"dev": "vite --open",
|
||||||
@@ -106,8 +106,8 @@
|
|||||||
"zod-defaults": "^0.1.3"
|
"zod-defaults": "^0.1.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@certd/lib-iframe": "^1.40.3",
|
"@certd/lib-iframe": "^1.40.4",
|
||||||
"@certd/pipeline": "^1.40.3",
|
"@certd/pipeline": "^1.40.4",
|
||||||
"@rollup/plugin-commonjs": "^25.0.7",
|
"@rollup/plugin-commonjs": "^25.0.7",
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@types/chai": "^4.3.12",
|
"@types/chai": "^4.3.12",
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<fs-page class="page-cert-dns-persist">
|
<fs-page class="page-cert-dns-persist">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div>
|
<div class="title">
|
||||||
<div class="title">DNS持久验证记录</div>
|
DNS持久验证记录
|
||||||
<div class="text-orange-500 mt-5">当前仅 Let's Encrypt 测试环境可以申请 DNS 持久验证证书。</div>
|
<span class="red sub" style="color: red">当前仅 Let's Encrypt 测试环境可以申请 DNS 持久验证证书。</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<fs-crud ref="crudRef" v-bind="crudBinding"></fs-crud>
|
<fs-crud ref="crudRef" v-bind="crudBinding"></fs-crud>
|
||||||
|
|||||||
@@ -32,7 +32,9 @@
|
|||||||
<div class="invite-info-row">
|
<div class="invite-info-row">
|
||||||
<span class="info-label">我的等级:</span>
|
<span class="info-label">我的等级:</span>
|
||||||
<a-button type="link" class="level-button" @click="levelDialogOpen = true">
|
<a-button type="link" class="level-button" @click="levelDialogOpen = true">
|
||||||
<span v-if="inviteInfo.currentLevel" class="level-medal" :class="levelMedalClass(inviteInfo.currentLevel)">{{ levelMedal(inviteInfo.currentLevel) }}</span>
|
<span v-if="inviteInfo.currentLevel" class="level-medal">
|
||||||
|
<fs-icon :icon="levelIcon(inviteInfo.currentLevel)" />
|
||||||
|
</span>
|
||||||
<span>{{ inviteInfo.currentLevel?.name || "未设置" }}</span>
|
<span>{{ inviteInfo.currentLevel?.name || "未设置" }}</span>
|
||||||
<span v-if="inviteInfo.currentLevel" class="current-level-rate">{{ inviteInfo.currentLevel.commissionRate }}%</span>
|
<span v-if="inviteInfo.currentLevel" class="current-level-rate">{{ inviteInfo.currentLevel.commissionRate }}%</span>
|
||||||
</a-button>
|
</a-button>
|
||||||
@@ -61,9 +63,11 @@
|
|||||||
<div class="level-card-grid modal-level-grid">
|
<div class="level-card-grid modal-level-grid">
|
||||||
<div v-for="level in visibleLevels" :key="level.id" class="level-card" :class="{ active: level.id === inviteInfo.currentLevel?.id }">
|
<div v-for="level in visibleLevels" :key="level.id" class="level-card" :class="{ active: level.id === inviteInfo.currentLevel?.id }">
|
||||||
<div class="level-name">
|
<div class="level-name">
|
||||||
<span class="level-medal" :class="levelMedalClass(level)">{{ levelMedal(level) }}</span>
|
<span class="level-medal">
|
||||||
|
<fs-icon :icon="levelIcon(level)" />
|
||||||
|
</span>
|
||||||
{{ level.name }}
|
{{ level.name }}
|
||||||
<a-tag v-if="level.isHidden" color="orange">专属</a-tag>
|
<a-tag v-if="level.levelType === 'exclusive'" color="orange">专属</a-tag>
|
||||||
</div>
|
</div>
|
||||||
<div class="level-rate-label">佣金比例</div>
|
<div class="level-rate-label">佣金比例</div>
|
||||||
<div class="level-rate">{{ level.commissionRate }}%</div>
|
<div class="level-rate">{{ level.commissionRate }}%</div>
|
||||||
@@ -86,7 +90,8 @@
|
|||||||
@ok="handleAgreementOk"
|
@ok="handleAgreementOk"
|
||||||
@cancel="closeAgreementDialog"
|
@cancel="closeAgreementDialog"
|
||||||
>
|
>
|
||||||
<div class="invite-agreement-content">{{ agreementText }}</div>
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
|
<div class="invite-agreement-content editor-content-view" v-html="agreementText"></div>
|
||||||
<div v-if="agreementDialogNeedOpen" class="invite-agreement-confirm">
|
<div v-if="agreementDialogNeedOpen" class="invite-agreement-confirm">
|
||||||
<a-checkbox v-model:checked="agreementAgree">我已阅读并同意推广协议</a-checkbox>
|
<a-checkbox v-model:checked="agreementAgree">我已阅读并同意推广协议</a-checkbox>
|
||||||
</div>
|
</div>
|
||||||
@@ -119,7 +124,7 @@ const agreementDialogOpen = ref(false);
|
|||||||
const agreementDialogNeedOpen = ref(false);
|
const agreementDialogNeedOpen = ref(false);
|
||||||
const agreementAgree = ref(false);
|
const agreementAgree = ref(false);
|
||||||
const agreementSubmitting = ref(false);
|
const agreementSubmitting = ref(false);
|
||||||
const defaultAgreementContent = "请遵守平台推广规则,不得通过虚假注册、刷单、恶意诱导等方式获取收益。平台有权对异常推广行为进行核查,并根据实际情况暂停结算或关闭激励计划资格。";
|
const defaultAgreementContent = "<p>请遵守平台推广规则,不得通过虚假注册、刷单、恶意诱导等方式获取收益。平台有权对异常推广行为进行核查,并根据实际情况暂停结算或关闭激励计划资格。</p>";
|
||||||
|
|
||||||
const inviteInfo = reactive<any>({
|
const inviteInfo = reactive<any>({
|
||||||
enabled: false,
|
enabled: false,
|
||||||
@@ -170,38 +175,8 @@ const visibleLevels = computed(() => {
|
|||||||
|
|
||||||
const agreementText = computed(() => inviteInfo.agreementContent?.trim() || defaultAgreementContent);
|
const agreementText = computed(() => inviteInfo.agreementContent?.trim() || defaultAgreementContent);
|
||||||
|
|
||||||
function levelMedal(level: any) {
|
function levelIcon(level: any) {
|
||||||
const name = `${level?.name || ""}`;
|
return level?.icon || "ion:ribbon-outline";
|
||||||
if (name.includes("青铜")) {
|
|
||||||
return "铜";
|
|
||||||
}
|
|
||||||
if (name.includes("白银")) {
|
|
||||||
return "银";
|
|
||||||
}
|
|
||||||
if (name.includes("黄金")) {
|
|
||||||
return "金";
|
|
||||||
}
|
|
||||||
if (name.includes("钻石")) {
|
|
||||||
return "钻";
|
|
||||||
}
|
|
||||||
return name.slice(0, 1) || "L";
|
|
||||||
}
|
|
||||||
|
|
||||||
function levelMedalClass(level: any) {
|
|
||||||
const name = `${level?.name || ""}`;
|
|
||||||
if (name.includes("青铜")) {
|
|
||||||
return "bronze";
|
|
||||||
}
|
|
||||||
if (name.includes("白银")) {
|
|
||||||
return "silver";
|
|
||||||
}
|
|
||||||
if (name.includes("黄金")) {
|
|
||||||
return "gold";
|
|
||||||
}
|
|
||||||
if (name.includes("钻石")) {
|
|
||||||
return "diamond";
|
|
||||||
}
|
|
||||||
return "default";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function openAgreementDialog(needOpenPlan: boolean) {
|
function openAgreementDialog(needOpenPlan: boolean) {
|
||||||
@@ -419,33 +394,10 @@ onActivated(async () => {
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 20px;
|
width: 22px;
|
||||||
height: 20px;
|
height: 22px;
|
||||||
border-radius: 50%;
|
|
||||||
background: #f4d7a1;
|
|
||||||
color: #8a5a16;
|
color: #8a5a16;
|
||||||
font-size: 12px;
|
font-size: 20px;
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.level-medal.bronze {
|
|
||||||
background: #f5d6b7;
|
|
||||||
color: #9a5b22;
|
|
||||||
}
|
|
||||||
|
|
||||||
.level-medal.silver {
|
|
||||||
background: #e5e7eb;
|
|
||||||
color: #4b5563;
|
|
||||||
}
|
|
||||||
|
|
||||||
.level-medal.gold {
|
|
||||||
background: #f8df9b;
|
|
||||||
color: #926c15;
|
|
||||||
}
|
|
||||||
|
|
||||||
.level-medal.diamond {
|
|
||||||
background: #dbeafe;
|
|
||||||
color: #2563eb;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.invite-tabs {
|
.invite-tabs {
|
||||||
@@ -520,33 +472,10 @@ onActivated(async () => {
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 20px;
|
width: 22px;
|
||||||
height: 20px;
|
height: 22px;
|
||||||
border-radius: 50%;
|
|
||||||
background: #f4d7a1;
|
|
||||||
color: #8a5a16;
|
color: #8a5a16;
|
||||||
font-size: 12px;
|
font-size: 20px;
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.level-medal.bronze {
|
|
||||||
background: #f5d6b7;
|
|
||||||
color: #9a5b22;
|
|
||||||
}
|
|
||||||
|
|
||||||
.level-medal.silver {
|
|
||||||
background: #e5e7eb;
|
|
||||||
color: #4b5563;
|
|
||||||
}
|
|
||||||
|
|
||||||
.level-medal.gold {
|
|
||||||
background: #f8df9b;
|
|
||||||
color: #926c15;
|
|
||||||
}
|
|
||||||
|
|
||||||
.level-medal.diamond {
|
|
||||||
background: #dbeafe;
|
|
||||||
color: #2563eb;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.level-rate-label {
|
.level-rate-label {
|
||||||
@@ -591,11 +520,19 @@ onActivated(async () => {
|
|||||||
max-height: 360px;
|
max-height: 360px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
white-space: pre-wrap;
|
|
||||||
border: 1px solid #eee;
|
border: 1px solid #eee;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background: hsl(var(--card));
|
background: hsl(var(--card));
|
||||||
line-height: 1.7;
|
line-height: 1.7;
|
||||||
|
|
||||||
|
:deep(img) {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(p) {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.invite-agreement-confirm {
|
.invite-agreement-confirm {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<a-col :span="24">
|
<a-col :span="24">
|
||||||
<a-card>
|
<a-card>
|
||||||
<div class="suite-intro-box">
|
<div class="suite-intro-box">
|
||||||
<div>说明:① 同一时间只有最新购买的一个套餐生效;② 可以购买多个加量包,加量包立即生效;③ 套餐和加量包内的数量可以叠加</div>
|
<div>{{ buyHelperText }}</div>
|
||||||
<div v-if="suiteIntro" v-html="suiteIntro"></div>
|
<div v-if="suiteIntro" v-html="suiteIntro"></div>
|
||||||
</div>
|
</div>
|
||||||
</a-card>
|
</a-card>
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import * as api from "./api";
|
import * as api from "./api";
|
||||||
import ProductInfo from "/@/views/certd/suite/product-info.vue";
|
import ProductInfo from "/@/views/certd/suite/product-info.vue";
|
||||||
import OrderModal from "/@/views/certd/suite/order-modal.vue";
|
import OrderModal from "/@/views/certd/suite/order-modal.vue";
|
||||||
@@ -55,9 +55,17 @@ async function doOrder(req: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const suiteIntro = ref("");
|
const suiteIntro = ref("");
|
||||||
|
const allowSuiteStack = ref(false);
|
||||||
|
const buyHelperText = computed(() => {
|
||||||
|
if (allowSuiteStack.value) {
|
||||||
|
return "说明:可以购买多个套餐和加量包,套餐和加量包内的数量可以叠加";
|
||||||
|
}
|
||||||
|
return "说明:① 同一时间只有最新购买的一个套餐生效;② 可以购买多个加量包,加量包立即生效;③ 套餐和加量包内的数量可以叠加";
|
||||||
|
});
|
||||||
async function loadSuiteIntro() {
|
async function loadSuiteIntro() {
|
||||||
const res = await api.GetSuiteSetting();
|
const res = await api.GetSuiteSetting();
|
||||||
suiteIntro.value = res.intro;
|
suiteIntro.value = res.intro;
|
||||||
|
allowSuiteStack.value = !!res.allowSuiteStack;
|
||||||
}
|
}
|
||||||
loadSuiteIntro();
|
loadSuiteIntro();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ export async function GetWithdrawSetting() {
|
|||||||
return await request({ url: "/wallet/withdraw/setting/get", method: "post" });
|
return await request({ url: "/wallet/withdraw/setting/get", method: "post" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function GetWalletSetting() {
|
||||||
|
return await request({ url: "/wallet/settings/get", method: "post" });
|
||||||
|
}
|
||||||
|
|
||||||
export async function SaveWithdrawSetting(data: any) {
|
export async function SaveWithdrawSetting(data: any) {
|
||||||
return await request({ url: "/wallet/withdraw/setting/save", method: "post", data });
|
return await request({ url: "/wallet/withdraw/setting/save", method: "post", data });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,22 +47,6 @@ export default function (): CreateCrudOptionsRet {
|
|||||||
}),
|
}),
|
||||||
column: { width: 110 },
|
column: { width: 110 },
|
||||||
},
|
},
|
||||||
realName: { title: "真实姓名", type: "text", column: { width: 120 } },
|
|
||||||
account: { title: "收款账号", type: "text", column: { width: 180 } },
|
|
||||||
bankName: { title: "开户银行", type: "text", column: { width: 160 } },
|
|
||||||
qrCode: {
|
|
||||||
title: "收款二维码",
|
|
||||||
type: "text",
|
|
||||||
column: {
|
|
||||||
width: 120,
|
|
||||||
cellRender({ value }) {
|
|
||||||
if (!value) {
|
|
||||||
return "-";
|
|
||||||
}
|
|
||||||
return <a-image src={`/api/basic/file/download?key=${value}`} width={48} />;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
auditRemark: { title: "审核备注", type: "text", column: { minWidth: 180 } },
|
auditRemark: { title: "审核备注", type: "text", column: { minWidth: 180 } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,17 +6,11 @@
|
|||||||
<div class="wallet-body">
|
<div class="wallet-body">
|
||||||
<div class="wallet-summary-grid">
|
<div class="wallet-summary-grid">
|
||||||
<div v-for="item in summaryCards" :key="item.key" class="summary-card">
|
<div v-for="item in summaryCards" :key="item.key" class="summary-card">
|
||||||
<div class="summary-title">{{ item.title }}</div>
|
<div class="summary-card-main">
|
||||||
<div class="summary-value" :class="item.className">{{ item.value }}</div>
|
<div class="summary-title">{{ item.title }}</div>
|
||||||
</div>
|
<div class="summary-value" :class="item.className">{{ item.value }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<a-button v-if="item.key === 'availableAmount'" class="summary-action-button" type="primary" @click="openWithdrawDialog">申请提现</a-button>
|
||||||
<div class="wallet-action-panel">
|
|
||||||
<div class="wallet-action-title">提现操作</div>
|
|
||||||
<div class="wallet-action-content">
|
|
||||||
<a-button type="primary" @click="openWithdrawSetting">提现设置</a-button>
|
|
||||||
<a-input-number v-model:value="withdrawAmountYuan" class="withdraw-amount-input" :min="0" addon-before="提现金额" addon-after="元" />
|
|
||||||
<a-button @click="applyWithdraw">申请提现</a-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -33,9 +27,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onActivated, onMounted, reactive, ref } from "vue";
|
import { computed, h, onActivated, onMounted, reactive, ref } from "vue";
|
||||||
import { compute, dict, useFs } from "@fast-crud/fast-crud";
|
import { compute, dict, useFs } from "@fast-crud/fast-crud";
|
||||||
import { notification } from "ant-design-vue";
|
import { Button, notification } from "ant-design-vue";
|
||||||
import * as api from "./api";
|
import * as api from "./api";
|
||||||
import createLogsCrudOptions from "./crud-logs";
|
import createLogsCrudOptions from "./crud-logs";
|
||||||
import createWithdrawCrudOptions from "./crud-withdraw";
|
import createWithdrawCrudOptions from "./crud-withdraw";
|
||||||
@@ -46,7 +40,6 @@ import { useUserStore } from "/@/store/user";
|
|||||||
defineOptions({ name: "MyWallet" });
|
defineOptions({ name: "MyWallet" });
|
||||||
|
|
||||||
const summary = reactive<any>({ availableAmount: 0, frozenAmount: 0, totalIncomeAmount: 0, totalWithdrawAmount: 0 });
|
const summary = reactive<any>({ availableAmount: 0, frozenAmount: 0, totalIncomeAmount: 0, totalWithdrawAmount: 0 });
|
||||||
const withdrawAmountYuan = ref(0);
|
|
||||||
const loaded = ref(false);
|
const loaded = ref(false);
|
||||||
const activeTab = ref("withdraw");
|
const activeTab = ref("withdraw");
|
||||||
const { openFormDialog } = useFormDialog();
|
const { openFormDialog } = useFormDialog();
|
||||||
@@ -62,6 +55,10 @@ function moneyText(amount: number) {
|
|||||||
return `¥ ${amountToYuan(amount)}`;
|
return `¥ ${amountToYuan(amount)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildPrivateFileUrl(key: string) {
|
||||||
|
return `/api/basic/file/download?token=${userStore.getToken}&key=${encodeURIComponent(key)}`;
|
||||||
|
}
|
||||||
|
|
||||||
const summaryCards = computed(() => [
|
const summaryCards = computed(() => [
|
||||||
{
|
{
|
||||||
key: "availableAmount",
|
key: "availableAmount",
|
||||||
@@ -95,8 +92,18 @@ async function loadWalletSummary() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function openWithdrawSetting() {
|
async function openWithdrawSetting() {
|
||||||
const setting: any = await api.GetWithdrawSetting();
|
const [setting, walletSetting]: any[] = await Promise.all([api.GetWithdrawSetting(), api.GetWalletSetting()]);
|
||||||
|
const enabledChannels = walletSetting?.withdrawChannels?.length ? walletSetting.withdrawChannels : ["alipay", "bank"];
|
||||||
|
const enabledBanks = walletSetting?.withdrawBanks?.length ? walletSetting.withdrawBanks : [];
|
||||||
|
const channelOptions = [
|
||||||
|
{ label: "支付宝", value: "alipay" },
|
||||||
|
{ label: "银行卡", value: "bank" },
|
||||||
|
].filter(item => enabledChannels.includes(item.value));
|
||||||
|
const bankOptions = enabledBanks.map((item: string) => ({ label: item, value: item }));
|
||||||
const initialForm = Object.assign({ channel: "alipay", realName: "", account: "", bankName: "" }, setting || {});
|
const initialForm = Object.assign({ channel: "alipay", realName: "", account: "", bankName: "" }, setting || {});
|
||||||
|
if (!enabledChannels.includes(initialForm.channel)) {
|
||||||
|
initialForm.channel = enabledChannels[0] || "alipay";
|
||||||
|
}
|
||||||
await openFormDialog({
|
await openFormDialog({
|
||||||
title: "提现设置",
|
title: "提现设置",
|
||||||
wrapper: {
|
wrapper: {
|
||||||
@@ -108,10 +115,7 @@ async function openWithdrawSetting() {
|
|||||||
title: "提现渠道",
|
title: "提现渠道",
|
||||||
type: "dict-radio",
|
type: "dict-radio",
|
||||||
dict: dict({
|
dict: dict({
|
||||||
data: [
|
data: channelOptions,
|
||||||
{ label: "支付宝", value: "alipay" },
|
|
||||||
{ label: "银行卡", value: "bank" },
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
form: {
|
form: {
|
||||||
col: { span: 24 },
|
col: { span: 24 },
|
||||||
@@ -136,18 +140,13 @@ async function openWithdrawSetting() {
|
|||||||
},
|
},
|
||||||
qrCode: {
|
qrCode: {
|
||||||
title: "收款二维码",
|
title: "收款二维码",
|
||||||
type: "cropper-uploader",
|
type: "avatar-uploader",
|
||||||
form: {
|
form: {
|
||||||
col: { span: 24 },
|
col: { span: 24 },
|
||||||
|
helper: "上传支付宝收款二维码图片",
|
||||||
|
show: compute(({ form }) => form.channel !== "bank"),
|
||||||
component: {
|
component: {
|
||||||
vModel: "modelValue",
|
|
||||||
valueType: "key",
|
valueType: "key",
|
||||||
cropper: {
|
|
||||||
aspectRatio: 1,
|
|
||||||
autoCropArea: 1,
|
|
||||||
viewMode: 0,
|
|
||||||
},
|
|
||||||
onReady: null,
|
|
||||||
uploader: {
|
uploader: {
|
||||||
type: "form",
|
type: "form",
|
||||||
action: "/basic/file/upload?token=" + userStore.getToken,
|
action: "/basic/file/upload?token=" + userStore.getToken,
|
||||||
@@ -160,31 +159,85 @@ async function openWithdrawSetting() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
buildUrl(key: string) {
|
buildUrl(key: string) {
|
||||||
return `/api/basic/file/download?key=` + key;
|
return buildPrivateFileUrl(key);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
bankName: {
|
bankName: {
|
||||||
title: "开户银行",
|
title: "开户银行",
|
||||||
type: "text",
|
|
||||||
form: {
|
form: {
|
||||||
col: { span: 24 },
|
col: { span: 24 },
|
||||||
show: compute(({ form }) => form.channel === "bank"),
|
show: compute(({ form }) => form.channel === "bank"),
|
||||||
|
component: {
|
||||||
|
name: "a-select",
|
||||||
|
vModel: "value",
|
||||||
|
options: bankOptions,
|
||||||
|
showSearch: true,
|
||||||
|
placeholder: "请选择开户银行",
|
||||||
|
},
|
||||||
rules: [{ required: compute(({ form }) => form.channel === "bank"), message: "请输入开户银行" }],
|
rules: [{ required: compute(({ form }) => form.channel === "bank"), message: "请输入开户银行" }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async onSubmit(form: any) {
|
async onSubmit(form: any) {
|
||||||
|
if (form.channel === "bank") {
|
||||||
|
form.qrCode = "";
|
||||||
|
}
|
||||||
await api.SaveWithdrawSetting(form);
|
await api.SaveWithdrawSetting(form);
|
||||||
notification.success({ message: "保存成功" });
|
notification.success({ message: "保存成功" });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function applyWithdraw() {
|
async function openWithdrawDialog() {
|
||||||
await api.ApplyWithdraw(util.amount.toCent(withdrawAmountYuan.value || 0));
|
await openFormDialog({
|
||||||
withdrawAmountYuan.value = 0;
|
title: "申请提现",
|
||||||
|
wrapper: {
|
||||||
|
width: 520,
|
||||||
|
},
|
||||||
|
initialForm: {
|
||||||
|
amountYuan: null,
|
||||||
|
},
|
||||||
|
body: () =>
|
||||||
|
h("div", { class: "withdraw-dialog-tip" }, [
|
||||||
|
h("span", "提现前需要先设置提现账号。"),
|
||||||
|
h(
|
||||||
|
Button,
|
||||||
|
{
|
||||||
|
size: "small",
|
||||||
|
type: "link",
|
||||||
|
onClick: openWithdrawSetting,
|
||||||
|
},
|
||||||
|
() => "提现设置"
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
columns: {
|
||||||
|
amountYuan: {
|
||||||
|
title: "提现金额",
|
||||||
|
form: {
|
||||||
|
col: { span: 24 },
|
||||||
|
component: {
|
||||||
|
name: "a-input-number",
|
||||||
|
vModel: "value",
|
||||||
|
min: 0,
|
||||||
|
precision: 2,
|
||||||
|
addonAfter: "元",
|
||||||
|
style: { width: "100%" },
|
||||||
|
},
|
||||||
|
rules: [{ required: true, message: "请输入提现金额" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async onSubmit(form: any) {
|
||||||
|
await applyWithdraw(form.amountYuan);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function applyWithdraw(amountYuan: number) {
|
||||||
|
await api.ApplyWithdraw(util.amount.toCent(amountYuan || 0));
|
||||||
|
activeTab.value = "withdraw";
|
||||||
await loadWalletSummary();
|
await loadWalletSummary();
|
||||||
await Promise.all([withdrawCrudExpose.doRefresh(), logsCrudExpose.doRefresh()]);
|
await Promise.all([withdrawCrudExpose.doRefresh(), logsCrudExpose.doRefresh()]);
|
||||||
notification.success({ message: "提现申请已提交" });
|
notification.success({ message: "提现申请已提交" });
|
||||||
@@ -233,8 +286,7 @@ onActivated(async () => {
|
|||||||
background: hsl(var(--background-deep));
|
background: hsl(var(--background-deep));
|
||||||
}
|
}
|
||||||
|
|
||||||
.wallet-summary-grid,
|
.wallet-summary-grid {
|
||||||
.wallet-action-panel {
|
|
||||||
flex: none;
|
flex: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,7 +298,6 @@ onActivated(async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.summary-card,
|
.summary-card,
|
||||||
.wallet-action-panel,
|
|
||||||
.wallet-tabs {
|
.wallet-tabs {
|
||||||
border: 1px solid hsl(var(--border));
|
border: 1px solid hsl(var(--border));
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -255,10 +306,18 @@ onActivated(async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.summary-card {
|
.summary-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
min-height: 112px;
|
min-height: 112px;
|
||||||
padding: 22px;
|
padding: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.summary-card-main {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.summary-title {
|
.summary-title {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
color: hsl(var(--muted-foreground));
|
color: hsl(var(--muted-foreground));
|
||||||
@@ -284,32 +343,8 @@ onActivated(async () => {
|
|||||||
color: #3478f6;
|
color: #3478f6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wallet-action-panel {
|
.summary-action-button {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-start;
|
|
||||||
gap: 16px;
|
|
||||||
padding: 14px 18px;
|
|
||||||
margin-bottom: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wallet-action-title {
|
|
||||||
flex: none;
|
flex: none;
|
||||||
color: hsl(var(--foreground));
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wallet-action-content {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: flex-start;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.withdraw-amount-input {
|
|
||||||
width: 240px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.wallet-tabs {
|
.wallet-tabs {
|
||||||
@@ -350,18 +385,23 @@ onActivated(async () => {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wallet-action-panel {
|
.summary-card {
|
||||||
align-items: stretch;
|
align-items: flex-start;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wallet-action-content {
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.withdraw-amount-input {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.withdraw-dialog-tip {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
border: 1px solid #d9e8ff;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #f5f9ff;
|
||||||
|
color: #315174;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -4,8 +4,14 @@
|
|||||||
<a-popover>
|
<a-popover>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div style="width: 300px">
|
<div style="width: 300px">
|
||||||
<div v-if="detail.addonList.length > 0" class="flex flex-wrap">
|
<div v-if="hiddenSuiteList.length > 0 || detail.addonList.length > 0" class="flex flex-wrap">
|
||||||
<a-tag v-for="(item, index) of detail.addonList" :key="index" color="green" class="pointer flex-o m-1">
|
<a-tag v-for="(item, index) of hiddenSuiteList" :key="`suite-${index}`" color="green" class="pointer flex-o m-1">
|
||||||
|
<span class="mr-5">
|
||||||
|
{{ item.title }}
|
||||||
|
</span>
|
||||||
|
<span>(<expires-time-text :value="item.expiresTime" />)</span>
|
||||||
|
</a-tag>
|
||||||
|
<a-tag v-for="(item, index) of detail.addonList" :key="`addon-${index}`" color="green" class="pointer flex-o m-1">
|
||||||
<span class="mr-5">
|
<span class="mr-5">
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
</span>
|
</span>
|
||||||
@@ -43,13 +49,13 @@
|
|||||||
</template>
|
</template>
|
||||||
<div class="flex-o">
|
<div class="flex-o">
|
||||||
<fs-icon icon="ant-design:gift-outlined" class="color-green mr-5" />
|
<fs-icon icon="ant-design:gift-outlined" class="color-green mr-5" />
|
||||||
<a-tag v-for="(item, index) of detail.suiteList" :key="index" color="green" class="pointer flex-o">
|
<a-tag v-if="currentSuite" color="green" class="pointer flex-o">
|
||||||
<span class="mr-5">
|
<span class="mr-5">
|
||||||
{{ item.title }}
|
{{ currentSuite.title }}
|
||||||
</span>
|
</span>
|
||||||
<span>(<expires-time-text :value="item.expiresTime" />)</span>
|
<span>(<expires-time-text :value="currentSuite.expiresTime" />)</span>
|
||||||
|
<span v-if="hiddenCount > 0" class="ml-5">+{{ hiddenCount }}</span>
|
||||||
</a-tag>
|
</a-tag>
|
||||||
<a-tag v-if="detail.addonList.length > 0" color="green" class="pointer flex-o">加量包+{{ detail.addonList.length }}</a-tag>
|
|
||||||
<div v-if="detail.suites?.length === 0" class="flex-o ml-5">暂无套餐 <a-button class="ml-5" type="primary" size="small" @click="goBuy">去购买</a-button></div>
|
<div v-if="detail.suites?.length === 0" class="flex-o ml-5">暂无套餐 <a-button class="ml-5" type="primary" size="small" @click="goBuy">去购买</a-button></div>
|
||||||
</div>
|
</div>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
@@ -59,7 +65,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import SuiteValue from "/@/views/sys/suite/product/suite-value.vue";
|
import SuiteValue from "/@/views/sys/suite/product/suite-value.vue";
|
||||||
import { ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import ExpiresTimeText from "/@/components/expires-time-text.vue";
|
import ExpiresTimeText from "/@/components/expires-time-text.vue";
|
||||||
import { mySuiteApi, SuiteDetail } from "/@/views/certd/suite/mine/api";
|
import { mySuiteApi, SuiteDetail } from "/@/views/certd/suite/mine/api";
|
||||||
import { FsIcon } from "@fast-crud/fast-crud";
|
import { FsIcon } from "@fast-crud/fast-crud";
|
||||||
@@ -71,6 +77,10 @@ defineOptions({
|
|||||||
|
|
||||||
const detail = ref<SuiteDetail>({});
|
const detail = ref<SuiteDetail>({});
|
||||||
|
|
||||||
|
const currentSuite = computed(() => detail.value.suiteList?.[0]);
|
||||||
|
const hiddenSuiteList = computed(() => detail.value.suiteList?.slice(1) || []);
|
||||||
|
const hiddenCount = computed(() => hiddenSuiteList.value.length + (detail.value.addonList?.length || 0));
|
||||||
|
|
||||||
async function loadSuiteDetail() {
|
async function loadSuiteDetail() {
|
||||||
detail.value = await mySuiteApi.SuiteDetailGet();
|
detail.value = await mySuiteApi.SuiteDetailGet();
|
||||||
const suites = detail.value.suites.filter(item => item.productType === "suite");
|
const suites = detail.value.suites.filter(item => item.productType === "suite");
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ export async function UpdateLevel(data: any) {
|
|||||||
return await request({ url: "/sys/invite/level/update", method: "post", data });
|
return await request({ url: "/sys/invite/level/update", method: "post", data });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function DeleteLevel(id: number) {
|
||||||
|
return await request({ url: "/sys/invite/level/delete", method: "post", params: { id } });
|
||||||
|
}
|
||||||
|
|
||||||
export async function GetUserLevels(query: any) {
|
export async function GetUserLevels(query: any) {
|
||||||
return await request({ url: "/sys/invite/user/page", method: "post", data: query });
|
return await request({ url: "/sys/invite/user/page", method: "post", data: query });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import PriceInput from "/@/views/sys/suite/product/price-input.vue";
|
|||||||
|
|
||||||
export default function (): CreateCrudOptionsRet {
|
export default function (): CreateCrudOptionsRet {
|
||||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||||
|
query.sort = { prop: "sort", asc: true };
|
||||||
return await api.GetLevels(query);
|
return await api.GetLevels(query);
|
||||||
};
|
};
|
||||||
const addRequest = async ({ form }: AddReq) => {
|
const addRequest = async ({ form }: AddReq) => {
|
||||||
@@ -14,8 +15,7 @@ export default function (): CreateCrudOptionsRet {
|
|||||||
return await api.UpdateLevel(form);
|
return await api.UpdateLevel(form);
|
||||||
};
|
};
|
||||||
const delRequest = async ({ row }: DelReq) => {
|
const delRequest = async ({ row }: DelReq) => {
|
||||||
row.disabled = true;
|
return await api.DeleteLevel(row.id);
|
||||||
return await api.UpdateLevel(row);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -49,6 +49,25 @@ export default function (): CreateCrudOptionsRet {
|
|||||||
},
|
},
|
||||||
column: { width: 140 },
|
column: { width: 140 },
|
||||||
},
|
},
|
||||||
|
icon: {
|
||||||
|
title: "等级图标",
|
||||||
|
type: "icon",
|
||||||
|
form: {
|
||||||
|
value: "ion:ribbon-outline",
|
||||||
|
rules: [{ required: true, message: "请选择等级图标" }],
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 90,
|
||||||
|
align: "center",
|
||||||
|
component: {
|
||||||
|
name: "fs-icon",
|
||||||
|
vModel: "icon",
|
||||||
|
style: {
|
||||||
|
fontSize: "22px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
minAmount: {
|
minAmount: {
|
||||||
title: "升级金额",
|
title: "升级金额",
|
||||||
type: "number",
|
type: "number",
|
||||||
@@ -70,23 +89,29 @@ export default function (): CreateCrudOptionsRet {
|
|||||||
},
|
},
|
||||||
column: { width: 110, align: "center", cellRender: ({ value }) => `${value || 0}%` },
|
column: { width: 110, align: "center", cellRender: ({ value }) => `${value || 0}%` },
|
||||||
},
|
},
|
||||||
isHidden: {
|
levelType: {
|
||||||
title: "隐藏等级",
|
title: "等级类型",
|
||||||
type: "dict-switch",
|
type: "dict-radio",
|
||||||
dict: dict({
|
dict: dict({
|
||||||
data: [
|
data: [
|
||||||
{ label: "普通等级", value: false, color: "success" },
|
{ label: "普通等级", value: "normal", color: "success" },
|
||||||
{ label: "隐藏等级", value: true, color: "warning" },
|
{ label: "专属等级", value: "exclusive", color: "warning" },
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
form: { value: false },
|
form: {
|
||||||
|
value: "normal",
|
||||||
|
helper: "专属等级可由管理员手动指定,不参与普通用户自动升级。",
|
||||||
|
},
|
||||||
column: { width: 120, align: "center" },
|
column: { width: 120, align: "center" },
|
||||||
},
|
},
|
||||||
sort: {
|
sort: {
|
||||||
title: "排序",
|
title: "排序",
|
||||||
type: "number",
|
type: "number",
|
||||||
form: { value: 10 },
|
form: {
|
||||||
column: { width: 90, align: "center" },
|
value: 10,
|
||||||
|
helper: "排序号越小越靠前。",
|
||||||
|
},
|
||||||
|
column: { width: 90, align: "center", sorter: true },
|
||||||
},
|
},
|
||||||
disabled: {
|
disabled: {
|
||||||
title: "状态",
|
title: "状态",
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
|||||||
}),
|
}),
|
||||||
form: {
|
form: {
|
||||||
col: { span: 24 },
|
col: { span: 24 },
|
||||||
helper: "隐藏等级会自动锁定,不参与自动升级。",
|
helper: "专属等级会自动锁定,不参与自动升级。",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -111,13 +111,13 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
|||||||
}),
|
}),
|
||||||
column: { width: 110, align: "center" },
|
column: { width: 110, align: "center" },
|
||||||
},
|
},
|
||||||
isHidden: {
|
levelType: {
|
||||||
title: "隐藏等级",
|
title: "等级类型",
|
||||||
type: "dict-switch",
|
type: "dict-select",
|
||||||
dict: dict({
|
dict: dict({
|
||||||
data: [
|
data: [
|
||||||
{ label: "否", value: false, color: "default" },
|
{ label: "普通等级", value: "normal", color: "success" },
|
||||||
{ label: "是", value: true, color: "warning" },
|
{ label: "专属等级", value: "exclusive", color: "warning" },
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
column: { width: 100, align: "center", show: compute(({ row }) => row.levelId) },
|
column: { width: 100, align: "center", show: compute(({ row }) => row.levelId) },
|
||||||
|
|||||||
@@ -3,6 +3,12 @@ import { notification } from "ant-design-vue";
|
|||||||
import * as api from "./api";
|
import * as api from "./api";
|
||||||
import PriceInput from "/@/views/sys/suite/product/price-input.vue";
|
import PriceInput from "/@/views/sys/suite/product/price-input.vue";
|
||||||
import { useFormDialog } from "/@/use/use-dialog";
|
import { useFormDialog } from "/@/use/use-dialog";
|
||||||
|
import { useUserStore } from "/@/store/user";
|
||||||
|
|
||||||
|
function buildPrivateFileUrl(key: string) {
|
||||||
|
const userStore = useUserStore();
|
||||||
|
return `/api/basic/file/download?token=${userStore.getToken}&key=${encodeURIComponent(key)}`;
|
||||||
|
}
|
||||||
|
|
||||||
export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
const { openFormDialog } = useFormDialog();
|
const { openFormDialog } = useFormDialog();
|
||||||
@@ -12,16 +18,17 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
|||||||
};
|
};
|
||||||
|
|
||||||
function renderWithdrawDetail(row: any) {
|
function renderWithdrawDetail(row: any) {
|
||||||
|
const isBank = row.channel === "bank";
|
||||||
return (
|
return (
|
||||||
<a-descriptions class={"w-full"} bordered column={2} size={"small"}>
|
<a-descriptions class={"w-full"} bordered column={1} size={"small"}>
|
||||||
|
<a-descriptions-item label="提现金额">
|
||||||
|
<span class={"text-red-500"}>{row.amount / 100} 元</span>
|
||||||
|
</a-descriptions-item>
|
||||||
<a-descriptions-item label="渠道类型">{row.channel === "bank" ? "银行卡" : "支付宝"}</a-descriptions-item>
|
<a-descriptions-item label="渠道类型">{row.channel === "bank" ? "银行卡" : "支付宝"}</a-descriptions-item>
|
||||||
<a-descriptions-item label="用户名">{row.userDisplay || row.userId}</a-descriptions-item>
|
<a-descriptions-item label="用户名">{row.userDisplay || row.userId}</a-descriptions-item>
|
||||||
<a-descriptions-item label="账号">{row.account || "-"}</a-descriptions-item>
|
<a-descriptions-item label="账号">{row.account || "-"}</a-descriptions-item>
|
||||||
<a-descriptions-item label="开户行名称">{row.bankName || "-"}</a-descriptions-item>
|
{isBank ? <a-descriptions-item label="开户行名称">{row.bankName || "-"}</a-descriptions-item> : null}
|
||||||
<a-descriptions-item label="收款二维码" span={2}>
|
{!isBank ? <a-descriptions-item label="收款二维码">{row.qrCode ? <a-image src={buildPrivateFileUrl(row.qrCode)} width={160} /> : <span>-</span>}</a-descriptions-item> : null}
|
||||||
{row.qrCode ? <a-image src={`/api/basic/file/download?key=${row.qrCode}`} width={160} /> : <span>-</span>}
|
|
||||||
</a-descriptions-item>
|
|
||||||
<a-descriptions-item label="提现金额">{row.amount / 100} 元</a-descriptions-item>
|
|
||||||
</a-descriptions>
|
</a-descriptions>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -31,6 +38,11 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
|||||||
title: "提现审核",
|
title: "提现审核",
|
||||||
wrapper: {
|
wrapper: {
|
||||||
width: 760,
|
width: 760,
|
||||||
|
buttons: {
|
||||||
|
ok: {
|
||||||
|
text: "确认已转账完成",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
body: () => renderWithdrawDetail(row),
|
body: () => renderWithdrawDetail(row),
|
||||||
onSubmit: async () => {
|
onSubmit: async () => {
|
||||||
@@ -147,17 +159,16 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
|||||||
},
|
},
|
||||||
realName: { title: "真实姓名", type: "text", search: { show: true }, column: { width: 120 } },
|
realName: { title: "真实姓名", type: "text", search: { show: true }, column: { width: 120 } },
|
||||||
account: { title: "收款账号", type: "text", column: { width: 180 } },
|
account: { title: "收款账号", type: "text", column: { width: 180 } },
|
||||||
bankName: { title: "开户银行", type: "text", column: { width: 160 } },
|
bankName: {
|
||||||
qrCode: {
|
title: "开户银行",
|
||||||
title: "收款二维码",
|
|
||||||
type: "text",
|
type: "text",
|
||||||
column: {
|
column: {
|
||||||
width: 120,
|
width: 160,
|
||||||
cellRender({ value }) {
|
cellRender({ row, value }) {
|
||||||
if (!value) {
|
if (row.channel !== "bank") {
|
||||||
return "-";
|
return "-";
|
||||||
}
|
}
|
||||||
return <a-image src={`/api/basic/file/download?key=${value}`} width={48} />;
|
return value || "-";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,18 +3,94 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<div class="title">推广等级</div>
|
<div class="title">推广等级</div>
|
||||||
</template>
|
</template>
|
||||||
<fs-crud ref="crudRef" v-bind="crudBinding" />
|
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||||
|
<a-empty v-if="levelList.length === 0" class="level-empty" />
|
||||||
|
<div v-else class="level-card-grid">
|
||||||
|
<div v-for="(item, index) of levelList" :key="item.id" class="level-card" :class="{ disabled: item.disabled }">
|
||||||
|
<div class="level-card-actions">
|
||||||
|
<a-tooltip title="编辑">
|
||||||
|
<a-button type="text" size="small" @click="openEdit({ index, row: item })">
|
||||||
|
<template #icon><fs-icon icon="ion:create-outline" /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip :title="item.disabled ? '启用' : '禁用'">
|
||||||
|
<a-button type="text" size="small" @click="toggleDisabled(item)">
|
||||||
|
<template #icon><fs-icon :icon="item.disabled ? 'ion:play-outline' : 'ion:pause-outline'" /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip title="删除">
|
||||||
|
<a-button type="text" danger size="small" @click="confirmRemove({ index, row: item })">
|
||||||
|
<template #icon><fs-icon icon="ion:trash-outline" /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="level-name">
|
||||||
|
<span class="level-medal">
|
||||||
|
<fs-icon :icon="levelIcon(item)" />
|
||||||
|
</span>
|
||||||
|
{{ item.name }}
|
||||||
|
<a-tag v-if="item.levelType === 'exclusive'" color="orange">专属</a-tag>
|
||||||
|
</div>
|
||||||
|
<div class="level-rate-label">佣金比例</div>
|
||||||
|
<div class="level-rate">{{ item.commissionRate || 0 }}%</div>
|
||||||
|
<div class="level-threshold">累计推广 ≥ {{ amountToYuan(item.minAmount) }} 元</div>
|
||||||
|
<div class="level-meta">
|
||||||
|
<a-tag :color="item.disabled ? 'default' : 'success'">{{ item.disabled ? "已禁用" : "已启用" }}</a-tag>
|
||||||
|
<span>排序 {{ item.sort || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fs-crud>
|
||||||
</fs-page>
|
</fs-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onActivated, onMounted } from "vue";
|
import { computed, onActivated, onMounted } from "vue";
|
||||||
|
import { Modal, notification } from "ant-design-vue";
|
||||||
import { useFs } from "@fast-crud/fast-crud";
|
import { useFs } from "@fast-crud/fast-crud";
|
||||||
import createCrudOptions from "./crud-level";
|
import createCrudOptions from "./crud-level";
|
||||||
|
import * as api from "./api";
|
||||||
|
import { util } from "/@/utils";
|
||||||
|
|
||||||
defineOptions({ name: "SysInviteLevel" });
|
defineOptions({ name: "SysInviteLevel" });
|
||||||
|
|
||||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions });
|
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions });
|
||||||
|
const levelList = computed(() => crudBinding.value?.data || []);
|
||||||
|
|
||||||
|
function amountToYuan(amount: number) {
|
||||||
|
return util.amount.toYuan(amount || 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function levelIcon(level: any) {
|
||||||
|
return level?.icon || "ion:ribbon-outline";
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEdit(opts: any) {
|
||||||
|
crudExpose.openEdit(opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleDisabled(row: any) {
|
||||||
|
await api.UpdateLevel({
|
||||||
|
...row,
|
||||||
|
disabled: !row.disabled,
|
||||||
|
});
|
||||||
|
notification.success({ message: row.disabled ? "已启用" : "已禁用" });
|
||||||
|
await crudExpose.doRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmRemove(opts: any) {
|
||||||
|
Modal.confirm({
|
||||||
|
title: "确认删除推广等级?",
|
||||||
|
content: "删除后不可恢复。如果该等级已被用户使用,可能会出现异常,请确认已完成数据处理。",
|
||||||
|
okText: "确认删除",
|
||||||
|
okType: "danger",
|
||||||
|
onOk: async () => {
|
||||||
|
await api.DeleteLevel(opts.row.id);
|
||||||
|
notification.success({ message: "已删除" });
|
||||||
|
await crudExpose.doRefresh();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
crudExpose.doRefresh();
|
crudExpose.doRefresh();
|
||||||
@@ -23,3 +99,130 @@ onActivated(() => {
|
|||||||
crudExpose.doRefresh();
|
crudExpose.doRefresh();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.page-sys-invite-level {
|
||||||
|
.fs-crud-table {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.level-card-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.level-empty {
|
||||||
|
padding: 64px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.level-card {
|
||||||
|
position: relative;
|
||||||
|
min-height: 156px;
|
||||||
|
padding: 16px;
|
||||||
|
border: 1px solid hsl(var(--border));
|
||||||
|
border-radius: 8px;
|
||||||
|
background: hsl(var(--card));
|
||||||
|
transition:
|
||||||
|
border-color 0.2s,
|
||||||
|
background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.level-card.disabled {
|
||||||
|
opacity: 0.66;
|
||||||
|
}
|
||||||
|
|
||||||
|
.level-card-actions {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
display: flex;
|
||||||
|
gap: 2px;
|
||||||
|
|
||||||
|
.ant-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.level-name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 26px;
|
||||||
|
padding: 0 72px;
|
||||||
|
gap: 6px;
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
font-weight: 700;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.level-medal {
|
||||||
|
display: inline-flex;
|
||||||
|
flex: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
color: #8a5a16;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.level-rate-label {
|
||||||
|
margin-top: 12px;
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.level-rate {
|
||||||
|
margin-top: 2px;
|
||||||
|
color: #c58a35;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 30px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.level-threshold {
|
||||||
|
margin-top: 6px;
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.level-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 10px;
|
||||||
|
color: hsl(var(--muted-foreground));
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.page-sys-invite-level {
|
||||||
|
.level-card-grid {
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.page-sys-invite-level {
|
||||||
|
.level-card-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -8,16 +8,26 @@
|
|||||||
<a-form-item label="开启激励计划" name="enabled">
|
<a-form-item label="开启激励计划" name="enabled">
|
||||||
<a-switch v-model:checked="settings.enabled" />
|
<a-switch v-model:checked="settings.enabled" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="推广协议" name="agreementContent">
|
|
||||||
<a-textarea v-model:value="settings.agreementContent" :rows="10" placeholder="请输入用户开通激励计划前需要确认的推广协议内容" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="最低提现金额" name="minWithdrawAmountYuan">
|
<a-form-item label="最低提现金额" name="minWithdrawAmountYuan">
|
||||||
<a-input-number v-model:value="settings.minWithdrawAmountYuan" :min="0" addon-after="元" />
|
<a-input-number v-model:value="settings.minWithdrawAmountYuan" :min="0" addon-after="元" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="提现渠道" name="withdrawChannels">
|
<a-form-item label="提现渠道" name="withdrawChannels">
|
||||||
<a-checkbox-group v-model:value="settings.withdrawChannels" :options="withdrawChannelOptions" />
|
<a-checkbox-group v-model:value="settings.withdrawChannels" :options="withdrawChannelOptions" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label=" ">
|
<a-form-item v-if="bankChannelEnabled" label="开户银行" name="withdrawBanks">
|
||||||
|
<a-select v-model:value="settings.withdrawBanks" mode="tags" :options="bankOptions" placeholder="请选择或输入支持的开户银行" :token-separators="[',', ',', '、']" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="推广协议" name="agreementContent">
|
||||||
|
<fs-editor-wang5
|
||||||
|
v-model="settings.agreementContent"
|
||||||
|
:toolbar-config="{}"
|
||||||
|
:editor-config="{ placeholder: '请输入用户开通激励计划前需要确认的推广协议内容' }"
|
||||||
|
:uploader="editorUploader"
|
||||||
|
:container="{ class: 'agreement-editor' }"
|
||||||
|
style="height: 400px"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label=" " :colon="false">
|
||||||
<a-button type="primary" @click="saveSettings">保存设置</a-button>
|
<a-button type="primary" @click="saveSettings">保存设置</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
@@ -26,20 +36,55 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, reactive } from "vue";
|
import { computed, onMounted, reactive } from "vue";
|
||||||
import { notification } from "ant-design-vue";
|
import { notification } from "ant-design-vue";
|
||||||
import * as api from "./api";
|
import * as api from "./api";
|
||||||
import { util } from "/@/utils";
|
import { util } from "/@/utils";
|
||||||
import { useSettingStore } from "/@/store/settings";
|
import { useSettingStore } from "/@/store/settings";
|
||||||
|
import { useUserStore } from "/@/store/user";
|
||||||
|
|
||||||
defineOptions({ name: "SysInviteCommissionSetting" });
|
defineOptions({ name: "SysInviteCommissionSetting" });
|
||||||
|
|
||||||
const defaultAgreement = "请遵守平台推广规则,不得通过虚假注册、刷单、恶意诱导等方式获取收益。平台有权对异常推广行为进行核查,并根据实际情况暂停结算或关闭激励计划资格。";
|
const defaultAgreement = "<p>请遵守平台推广规则,不得通过虚假注册、刷单、恶意诱导等方式获取收益。平台有权对异常推广行为进行核查,并根据实际情况暂停结算或关闭激励计划资格。</p>";
|
||||||
const settings = reactive<any>({ enabled: false, agreementContent: "", minWithdrawAmountYuan: 0, withdrawChannels: ["alipay", "bank"] });
|
const defaultWithdrawBanks = [
|
||||||
|
"中国工商银行",
|
||||||
|
"中国农业银行",
|
||||||
|
"中国银行",
|
||||||
|
"中国建设银行",
|
||||||
|
"交通银行",
|
||||||
|
"招商银行",
|
||||||
|
"中国邮政储蓄银行",
|
||||||
|
"中信银行",
|
||||||
|
"中国光大银行",
|
||||||
|
"华夏银行",
|
||||||
|
"中国民生银行",
|
||||||
|
"广发银行",
|
||||||
|
"平安银行",
|
||||||
|
"兴业银行",
|
||||||
|
"浦发银行",
|
||||||
|
];
|
||||||
|
const settings = reactive<any>({ enabled: false, agreementContent: "", minWithdrawAmountYuan: 0, withdrawChannels: ["alipay", "bank"], withdrawBanks: defaultWithdrawBanks });
|
||||||
const withdrawChannelOptions = [
|
const withdrawChannelOptions = [
|
||||||
{ label: "支付宝", value: "alipay" },
|
{ label: "支付宝", value: "alipay" },
|
||||||
{ label: "银行卡", value: "bank" },
|
{ label: "银行卡", value: "bank" },
|
||||||
];
|
];
|
||||||
|
const bankOptions = computed(() => defaultWithdrawBanks.map(item => ({ label: item, value: item })));
|
||||||
|
const bankChannelEnabled = computed(() => settings.withdrawChannels?.includes("bank"));
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const editorUploader = {
|
||||||
|
type: "form",
|
||||||
|
action: "/basic/file/upload?autoSave=true&token=" + userStore.getToken,
|
||||||
|
name: "file",
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer " + userStore.getToken,
|
||||||
|
},
|
||||||
|
successHandle(res: any) {
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
buildUrl(res: any) {
|
||||||
|
return res.url || `/api/basic/file/download?key=${encodeURIComponent(res.key)}`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
async function loadSettings() {
|
async function loadSettings() {
|
||||||
const data: any = await api.GetSettings();
|
const data: any = await api.GetSettings();
|
||||||
@@ -47,19 +92,34 @@ async function loadSettings() {
|
|||||||
settings.agreementContent = data?.agreementContent || defaultAgreement;
|
settings.agreementContent = data?.agreementContent || defaultAgreement;
|
||||||
settings.minWithdrawAmountYuan = util.amount.toYuan(data?.minWithdrawAmount || 0);
|
settings.minWithdrawAmountYuan = util.amount.toYuan(data?.minWithdrawAmount || 0);
|
||||||
settings.withdrawChannels = data?.withdrawChannels?.length ? data.withdrawChannels : ["alipay", "bank"];
|
settings.withdrawChannels = data?.withdrawChannels?.length ? data.withdrawChannels : ["alipay", "bank"];
|
||||||
|
settings.withdrawBanks = data?.withdrawBanks?.length ? data.withdrawBanks : defaultWithdrawBanks;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveSettings() {
|
async function saveSettings() {
|
||||||
|
const withdrawBanks = bankChannelEnabled.value ? (settings.withdrawBanks || []).map((item: string) => item?.trim()).filter(Boolean) : [];
|
||||||
|
if (isBlankAgreement(settings.agreementContent)) {
|
||||||
|
notification.warning({ message: "请填写推广协议内容" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
await api.SaveSettings({
|
await api.SaveSettings({
|
||||||
enabled: settings.enabled,
|
enabled: settings.enabled,
|
||||||
agreementContent: settings.agreementContent || "",
|
agreementContent: settings.agreementContent || "",
|
||||||
minWithdrawAmount: util.amount.toCent(settings.minWithdrawAmountYuan || 0),
|
minWithdrawAmount: util.amount.toCent(settings.minWithdrawAmountYuan || 0),
|
||||||
withdrawChannels: settings.withdrawChannels || [],
|
withdrawChannels: settings.withdrawChannels || [],
|
||||||
|
withdrawBanks,
|
||||||
});
|
});
|
||||||
await useSettingStore().loadSysSettings();
|
await useSettingStore().loadSysSettings();
|
||||||
notification.success({ message: "保存成功" });
|
notification.success({ message: "保存成功" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isBlankAgreement(content: string) {
|
||||||
|
const text = `${content || ""}`
|
||||||
|
.replace(/<[^>]*>/g, "")
|
||||||
|
.replace(/ /g, "")
|
||||||
|
.trim();
|
||||||
|
return !text;
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(loadSettings);
|
onMounted(loadSettings);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -71,5 +131,8 @@ onMounted(loadSettings);
|
|||||||
.settings-form {
|
.settings-form {
|
||||||
max-width: 860px;
|
max-width: 860px;
|
||||||
}
|
}
|
||||||
|
.agreement-editor {
|
||||||
|
min-height: 420px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex-o price-input">
|
<div class="flex-o price-input">
|
||||||
<a-input-number v-if="edit" prefix="¥" :value="priceValue" :precision="2" class="ml-5" @update:value="onPriceChange"> </a-input-number>
|
<a-input-number v-if="edit" prefix="¥" :value="priceValue" :precision="2" class="price-input-number" @update:value="onPriceChange"> </a-input-number>
|
||||||
<span v-else class="price-text" :style="style">{{ priceLabel }}</span>
|
<span v-else class="price-text" :style="style">{{ priceLabel }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -55,6 +55,12 @@ const onPriceChange = (price: number) => {
|
|||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
.price-input {
|
.price-input {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.price-input-number {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.price-text {
|
.price-text {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,11 @@
|
|||||||
<div class="helper">不建议设置免费套餐,可以在下方配置注册赠送套餐,或者在用户套餐管理中手动赠送套餐</div>
|
<div class="helper">不建议设置免费套餐,可以在下方配置注册赠送套餐,或者在用户套餐管理中手动赠送套餐</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="套餐支持叠加" name="allowSuiteStack">
|
||||||
|
<a-switch v-model:checked="formState.allowSuiteStack" />
|
||||||
|
<div class="helper">默认只有一个套餐生效。开启此开关,可以让多个套餐可以叠加;加量包无论是否开启此开关都可以叠加。</div>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="注册赠送套餐" name="registerGift">
|
<a-form-item label="注册赠送套餐" name="registerGift">
|
||||||
<suite-duration-selector ref="suiteDurationSelectedRef" v-model="formState.registerGift"></suite-duration-selector>
|
<suite-duration-selector ref="suiteDurationSelectedRef" v-model="formState.registerGift"></suite-duration-selector>
|
||||||
<div class="helper">添加套餐后再选择</div>
|
<div class="helper">添加套餐后再选择</div>
|
||||||
@@ -73,6 +78,7 @@ const formRef = ref<any>(null);
|
|||||||
const formState = reactive<
|
const formState = reactive<
|
||||||
Partial<{
|
Partial<{
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
allowSuiteStack: boolean;
|
||||||
registerGift?: {
|
registerGift?: {
|
||||||
productId?: number;
|
productId?: number;
|
||||||
duration?: number;
|
duration?: number;
|
||||||
|
|||||||
@@ -3,6 +3,16 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.40.4](https://github.com/certd/certd/compare/v1.40.3...v1.40.4) (2026-05-24)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **pipeline-service:** 修复流水线运行时超过套餐部署次数仍然能够正常运行的bug ([5e59651](https://github.com/certd/certd/commit/5e59651d45bc91919629e35995ff1b3cff6b87ea))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 新增阿里云直播证书部署插件 ([8edb6f8](https://github.com/certd/certd/commit/8edb6f8727bd148f106801bef25567880fd35e9e))
|
||||||
|
|
||||||
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ CREATE TABLE `cd_invite_level`
|
|||||||
(
|
(
|
||||||
`id` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL,
|
`id` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL,
|
||||||
`name` varchar(100),
|
`name` varchar(100),
|
||||||
|
`icon` varchar(100) NOT NULL DEFAULT 'fluent-emoji-flat:2nd-place-medal',
|
||||||
`sort` bigint NOT NULL DEFAULT 0,
|
`sort` bigint NOT NULL DEFAULT 0,
|
||||||
`min_amount` bigint NOT NULL DEFAULT 0,
|
`min_amount` bigint NOT NULL DEFAULT 0,
|
||||||
`commission_rate` bigint NOT NULL DEFAULT 0,
|
`commission_rate` bigint NOT NULL DEFAULT 0,
|
||||||
`is_hidden` boolean NOT NULL DEFAULT false,
|
`level_type` varchar(30) NOT NULL DEFAULT 'normal',
|
||||||
`disabled` boolean NOT NULL DEFAULT false,
|
`disabled` boolean NOT NULL DEFAULT false,
|
||||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
|
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
@@ -28,8 +29,8 @@ CREATE TABLE `cd_invite_user_plan`
|
|||||||
);
|
);
|
||||||
CREATE UNIQUE INDEX `index_invite_user_plan_user_id` ON `cd_invite_user_plan` (`user_id`);
|
CREATE UNIQUE INDEX `index_invite_user_plan_user_id` ON `cd_invite_user_plan` (`user_id`);
|
||||||
|
|
||||||
INSERT INTO `cd_invite_level` (`name`, `sort`, `min_amount`, `commission_rate`, `is_hidden`, `disabled`)
|
INSERT INTO `cd_invite_level` (`name`, `icon`, `sort`, `min_amount`, `commission_rate`, `level_type`, `disabled`)
|
||||||
VALUES ('青铜', 10, 0, 10, false, false),
|
VALUES ('青铜', 'fluent-emoji-flat:2nd-place-medal', 10, 0, 10, 'normal', false),
|
||||||
('白银', 20, 100000, 15, false, false),
|
('白银', 'fluent-emoji-flat:1st-place-medal', 20, 100000, 15, 'normal', false),
|
||||||
('黄金', 30, 500000, 20, false, false),
|
('黄金', 'fluent-emoji-flat:3rd-place-medal', 30, 500000, 20, 'normal', false),
|
||||||
('钻石', 40, 1000000, 30, false, false);
|
('钻石', 'streamline-color:diamond-2', 40, 1000000, 30, 'normal', false);
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ CREATE TABLE "cd_invite_level"
|
|||||||
(
|
(
|
||||||
"id" bigint PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
"id" bigint PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||||
"name" varchar(100),
|
"name" varchar(100),
|
||||||
|
"icon" varchar(100) NOT NULL DEFAULT 'fluent-emoji-flat:2nd-place-medal',
|
||||||
"sort" bigint NOT NULL DEFAULT 0,
|
"sort" bigint NOT NULL DEFAULT 0,
|
||||||
"min_amount" bigint NOT NULL DEFAULT 0,
|
"min_amount" bigint NOT NULL DEFAULT 0,
|
||||||
"commission_rate" bigint NOT NULL DEFAULT 0,
|
"commission_rate" bigint NOT NULL DEFAULT 0,
|
||||||
"is_hidden" boolean NOT NULL DEFAULT (false),
|
"level_type" varchar(30) NOT NULL DEFAULT 'normal',
|
||||||
"disabled" boolean NOT NULL DEFAULT (false),
|
"disabled" boolean NOT NULL DEFAULT (false),
|
||||||
"create_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
"create_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||||
"update_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
"update_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
||||||
@@ -28,8 +29,8 @@ CREATE TABLE "cd_invite_user_plan"
|
|||||||
);
|
);
|
||||||
CREATE UNIQUE INDEX "index_invite_user_plan_user_id" ON "cd_invite_user_plan" ("user_id");
|
CREATE UNIQUE INDEX "index_invite_user_plan_user_id" ON "cd_invite_user_plan" ("user_id");
|
||||||
|
|
||||||
INSERT INTO "cd_invite_level" ("name", "sort", "min_amount", "commission_rate", "is_hidden", "disabled")
|
INSERT INTO "cd_invite_level" ("name", "icon", "sort", "min_amount", "commission_rate", "level_type", "disabled")
|
||||||
VALUES ('青铜', 10, 0, 10, false, false),
|
VALUES ('青铜', 'fluent-emoji-flat:2nd-place-medal', 10, 0, 10, 'normal', false),
|
||||||
('白银', 20, 100000, 15, false, false),
|
('白银', 'fluent-emoji-flat:1st-place-medal', 20, 100000, 15, 'normal', false),
|
||||||
('黄金', 30, 500000, 20, false, false),
|
('黄金', 'fluent-emoji-flat:3rd-place-medal', 30, 500000, 20, 'normal', false),
|
||||||
('钻石', 40, 1000000, 30, false, false);
|
('钻石', 'streamline-color:diamond-2', 40, 1000000, 30, 'normal', false);
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ CREATE TABLE "cd_invite_level"
|
|||||||
(
|
(
|
||||||
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
"name" varchar(100),
|
"name" varchar(100),
|
||||||
|
"icon" varchar(100) NOT NULL DEFAULT 'fluent-emoji-flat:2nd-place-medal',
|
||||||
"sort" integer NOT NULL DEFAULT 0,
|
"sort" integer NOT NULL DEFAULT 0,
|
||||||
"min_amount" integer NOT NULL DEFAULT 0,
|
"min_amount" integer NOT NULL DEFAULT 0,
|
||||||
"commission_rate" integer NOT NULL DEFAULT 0,
|
"commission_rate" integer NOT NULL DEFAULT 0,
|
||||||
"is_hidden" boolean NOT NULL DEFAULT (false),
|
"level_type" varchar(30) NOT NULL DEFAULT 'normal',
|
||||||
"disabled" boolean NOT NULL DEFAULT (false),
|
"disabled" boolean NOT NULL DEFAULT (false),
|
||||||
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||||
"update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
"update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
||||||
@@ -28,8 +29,8 @@ CREATE TABLE "cd_invite_user_plan"
|
|||||||
);
|
);
|
||||||
CREATE UNIQUE INDEX "index_invite_user_plan_user_id" ON "cd_invite_user_plan" ("user_id");
|
CREATE UNIQUE INDEX "index_invite_user_plan_user_id" ON "cd_invite_user_plan" ("user_id");
|
||||||
|
|
||||||
INSERT INTO "cd_invite_level" ("name", "sort", "min_amount", "commission_rate", "is_hidden", "disabled")
|
INSERT INTO "cd_invite_level" ("name", "icon", "sort", "min_amount", "commission_rate", "level_type", "disabled")
|
||||||
VALUES ('青铜', 10, 0, 10, false, false),
|
VALUES ('青铜', 'fluent-emoji-flat:2nd-place-medal', 10, 0, 10, 'normal', false),
|
||||||
('白银', 20, 100000, 15, false, false),
|
('白银', 'fluent-emoji-flat:1st-place-medal', 20, 100000, 15, 'normal', false),
|
||||||
('黄金', 30, 500000, 20, false, false),
|
('黄金', 'fluent-emoji-flat:3rd-place-medal', 30, 500000, 20, 'normal', false),
|
||||||
('钻石', 40, 1000000, 30, false, false);
|
('钻石', 'streamline-color:diamond-2', 40, 1000000, 30, 'normal', false);
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
showRunStrategy: false
|
||||||
|
default:
|
||||||
|
strategy:
|
||||||
|
runStrategy: 1
|
||||||
|
name: DeployCertToAliyunLive
|
||||||
|
title: 阿里云-部署至直播(Live)
|
||||||
|
icon: svg:icon-aliyun
|
||||||
|
group: aliyun
|
||||||
|
desc: 部署证书到阿里云视频直播(Live)域名
|
||||||
|
needPlus: false
|
||||||
|
input:
|
||||||
|
cert:
|
||||||
|
title: 域名证书
|
||||||
|
helper: 请选择前置任务输出的域名证书
|
||||||
|
component:
|
||||||
|
name: output-selector
|
||||||
|
from:
|
||||||
|
- ':cert:'
|
||||||
|
- uploadCertToAliyun
|
||||||
|
template: false
|
||||||
|
required: true
|
||||||
|
order: 0
|
||||||
|
certDomains:
|
||||||
|
title: 当前证书域名
|
||||||
|
component:
|
||||||
|
name: cert-domains-getter
|
||||||
|
mergeScript: |2-
|
||||||
|
|
||||||
|
return {
|
||||||
|
component:{
|
||||||
|
inputKey: ctx.compute(({form})=>{
|
||||||
|
return form.cert
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template: false
|
||||||
|
required: false
|
||||||
|
order: 0
|
||||||
|
accessId:
|
||||||
|
title: Access授权
|
||||||
|
helper: 阿里云授权AccessKeyId、AccessKeySecret
|
||||||
|
component:
|
||||||
|
name: access-selector
|
||||||
|
type: aliyun
|
||||||
|
required: true
|
||||||
|
order: 0
|
||||||
|
endpoint:
|
||||||
|
title: 证书服务接入点
|
||||||
|
helper: 不会选就按默认
|
||||||
|
value: cas.aliyuncs.com
|
||||||
|
component:
|
||||||
|
name: a-select
|
||||||
|
options:
|
||||||
|
- value: cas.aliyuncs.com
|
||||||
|
label: 中国大陆
|
||||||
|
- value: cas.ap-southeast-1.aliyuncs.com
|
||||||
|
label: 新加坡
|
||||||
|
- value: cas.eu-central-1.aliyuncs.com
|
||||||
|
label: 德国(法兰克福)
|
||||||
|
required: true
|
||||||
|
order: 0
|
||||||
|
domainList:
|
||||||
|
title: 直播域名
|
||||||
|
component:
|
||||||
|
name: remote-select
|
||||||
|
vModel: value
|
||||||
|
mode: tags
|
||||||
|
type: plugin
|
||||||
|
typeName: DeployCertToAliyunLive
|
||||||
|
action: onGetDomainList
|
||||||
|
search: true
|
||||||
|
pager: true
|
||||||
|
multi: true
|
||||||
|
watches:
|
||||||
|
- certDomains
|
||||||
|
- accessId
|
||||||
|
- certDomains
|
||||||
|
- accessId
|
||||||
|
required: true
|
||||||
|
mergeScript: |2-
|
||||||
|
|
||||||
|
return {
|
||||||
|
component:{
|
||||||
|
form: ctx.compute(({form})=>{
|
||||||
|
return form
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
helper: 请选择要部署证书的直播域名
|
||||||
|
order: 0
|
||||||
|
output: {}
|
||||||
|
pluginType: deploy
|
||||||
|
type: builtIn
|
||||||
|
scriptFilePath: /plugins/plugin-aliyun/plugin/deploy-to-live/index.js
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/ui-server",
|
"name": "@certd/ui-server",
|
||||||
"version": "1.40.3",
|
"version": "1.40.4",
|
||||||
"description": "fast-server base midway",
|
"description": "fast-server base midway",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -53,20 +53,20 @@
|
|||||||
"@aws-sdk/client-sts": "^3.990.0",
|
"@aws-sdk/client-sts": "^3.990.0",
|
||||||
"@azure/arm-dns": "^5.1.0",
|
"@azure/arm-dns": "^5.1.0",
|
||||||
"@azure/identity": "^4.13.1",
|
"@azure/identity": "^4.13.1",
|
||||||
"@certd/acme-client": "^1.40.3",
|
"@certd/acme-client": "^1.40.4",
|
||||||
"@certd/basic": "^1.40.3",
|
"@certd/basic": "^1.40.4",
|
||||||
"@certd/commercial-core": "^1.40.3",
|
"@certd/commercial-core": "^1.40.4",
|
||||||
"@certd/cv4pve-api-javascript": "^8.4.2",
|
"@certd/cv4pve-api-javascript": "^8.4.2",
|
||||||
"@certd/jdcloud": "^1.40.3",
|
"@certd/jdcloud": "^1.40.4",
|
||||||
"@certd/lib-huawei": "^1.40.3",
|
"@certd/lib-huawei": "^1.40.4",
|
||||||
"@certd/lib-k8s": "^1.40.3",
|
"@certd/lib-k8s": "^1.40.4",
|
||||||
"@certd/lib-server": "^1.40.3",
|
"@certd/lib-server": "^1.40.4",
|
||||||
"@certd/midway-flyway-js": "^1.40.3",
|
"@certd/midway-flyway-js": "^1.40.4",
|
||||||
"@certd/pipeline": "^1.40.3",
|
"@certd/pipeline": "^1.40.4",
|
||||||
"@certd/plugin-cert": "^1.40.3",
|
"@certd/plugin-cert": "^1.40.4",
|
||||||
"@certd/plugin-lib": "^1.40.3",
|
"@certd/plugin-lib": "^1.40.4",
|
||||||
"@certd/plugin-plus": "^1.40.3",
|
"@certd/plugin-plus": "^1.40.4",
|
||||||
"@certd/plus-core": "^1.40.3",
|
"@certd/plus-core": "^1.40.4",
|
||||||
"@google-cloud/dns": "^5.3.1",
|
"@google-cloud/dns": "^5.3.1",
|
||||||
"@google-cloud/publicca": "^1.3.0",
|
"@google-cloud/publicca": "^1.3.0",
|
||||||
"@huaweicloud/huaweicloud-sdk-cdn": "3.1.185",
|
"@huaweicloud/huaweicloud-sdk-cdn": "3.1.185",
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ export class MainConfiguration {
|
|||||||
});
|
});
|
||||||
|
|
||||||
logger.info('当前环境:', this.app.getEnv()); // prod
|
logger.info('当前环境:', this.app.getEnv()); // prod
|
||||||
// throw new Error("address family not supported")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
import { getImageDownloadOptions, isImageFile } from "./file-controller.js";
|
import { FileController, getImageDownloadOptions, isImageFile } from "./file-controller.js";
|
||||||
|
|
||||||
describe("FileController.isImageFile", () => {
|
describe("FileController.isImageFile", () => {
|
||||||
it("detects uploaded logo image files", () => {
|
it("detects uploaded logo image files", () => {
|
||||||
@@ -37,3 +37,23 @@ describe("FileController.isImageFile", () => {
|
|||||||
assert.equal(getImageDownloadOptions("data/upload/private/user/cert.pem"), undefined);
|
assert.equal(getImageDownloadOptions("data/upload/private/user/cert.pem"), undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("FileController.upload", () => {
|
||||||
|
it("auto saves uploaded file to public directory when autoSave is true", async () => {
|
||||||
|
const controller = new FileController();
|
||||||
|
controller.fileService = {
|
||||||
|
async saveFile(userId: number, key: string, permission: string) {
|
||||||
|
assert.equal(userId, 1);
|
||||||
|
assert.equal(permission, "public");
|
||||||
|
assert.equal(key.startsWith("tmpfile_key_"), true);
|
||||||
|
return "/public/1/logo.png";
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
controller.ctx = { user: { id: 1 } } as any;
|
||||||
|
|
||||||
|
const res = await controller.upload([{ filename: "logo.png", data: "tmp/logo.png" }] as any, {}, "true");
|
||||||
|
|
||||||
|
assert.equal(res.data.key, "/public/1/logo.png");
|
||||||
|
assert.equal(res.data.url, "/api/basic/file/download?key=%2Fpublic%2F1%2Flogo.png");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Controller, Fields, Files, Get, Inject, Post, Provide, Query } from '@midwayjs/core';
|
import { Controller, Fields, Files, Get, Inject, Post, Provide, Query } from '@midwayjs/core';
|
||||||
import { BaseController, Constants, FileService, UploadFileItem, uploadTmpFileCacheKey } from '@certd/lib-server';
|
import { BaseController, Constants, FileService, PermissionException, UploadFileItem, uploadTmpFileCacheKey } from '@certd/lib-server';
|
||||||
import send from 'koa-send';
|
import send from 'koa-send';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { cache } from '@certd/basic';
|
import { cache } from '@certd/basic';
|
||||||
import { UploadFileInfo } from '@midwayjs/upload';
|
import { UploadFileInfo } from '@midwayjs/upload';
|
||||||
|
import { AuthService } from '../../modules/sys/authority/service/auth-service.js';
|
||||||
|
|
||||||
const imageExtSet = new Set(['.apng', '.avif', '.bmp', '.gif', '.ico', '.jpeg', '.jpg', '.png', '.svg', '.webp']);
|
const imageExtSet = new Set(['.apng', '.avif', '.bmp', '.gif', '.ico', '.jpeg', '.jpg', '.png', '.svg', '.webp']);
|
||||||
const imageCacheSeconds = 3 * 24 * 60 * 60;
|
const imageCacheSeconds = 3 * 24 * 60 * 60;
|
||||||
@@ -32,8 +33,11 @@ export class FileController extends BaseController {
|
|||||||
@Inject()
|
@Inject()
|
||||||
fileService: FileService;
|
fileService: FileService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
authService: AuthService;
|
||||||
|
|
||||||
@Post('/upload', { description: Constants.per.authOnly })
|
@Post('/upload', { description: Constants.per.authOnly })
|
||||||
async upload(@Files() files: UploadFileInfo<string>[], @Fields() fields: any) {
|
async upload(@Files() files: UploadFileInfo<string>[], @Fields() fields: any, @Query('autoSave') autoSave: string) {
|
||||||
console.log('files', files, fields);
|
console.log('files', files, fields);
|
||||||
const cacheKey = uploadTmpFileCacheKey + nanoid();
|
const cacheKey = uploadTmpFileCacheKey + nanoid();
|
||||||
const file = files[0];
|
const file = files[0];
|
||||||
@@ -47,22 +51,42 @@ export class FileController extends BaseController {
|
|||||||
ttl: 1000 * 60 * 60,
|
ttl: 1000 * 60 * 60,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
if (autoSave === 'true') {
|
||||||
|
const key = await this.fileService.saveFile(this.getUserId(), cacheKey, 'public');
|
||||||
|
return this.ok({
|
||||||
|
key,
|
||||||
|
url: `/api/basic/file/download?key=${encodeURIComponent(key)}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
return this.ok({
|
return this.ok({
|
||||||
key: cacheKey,
|
key: cacheKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/download', { description: Constants.per.guest })
|
@Get('/download', { description: Constants.per.guestOptionalAuth })
|
||||||
async download(@Query('key') key: string) {
|
async download(@Query('key') key: string) {
|
||||||
let userId: any = null;
|
const filePath = this.getDownloadFilePath(key);
|
||||||
if (!key.startsWith('/public')) {
|
|
||||||
userId = this.getUserId();
|
|
||||||
}
|
|
||||||
const filePath = this.fileService.getFile(key, userId);
|
|
||||||
const sendOptions = getImageDownloadOptions(filePath);
|
const sendOptions = getImageDownloadOptions(filePath);
|
||||||
if (!sendOptions) {
|
if (!sendOptions) {
|
||||||
this.ctx.response.attachment(filePath);
|
this.ctx.response.attachment(filePath);
|
||||||
}
|
}
|
||||||
await send(this.ctx, filePath, sendOptions);
|
await send(this.ctx, filePath, sendOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getDownloadFilePath(key: string) {
|
||||||
|
const isPrivateFile = !key.startsWith('/public');
|
||||||
|
const userId = isPrivateFile ? this.getUserId() : null;
|
||||||
|
try {
|
||||||
|
return this.fileService.getFile(key, userId);
|
||||||
|
} catch (e) {
|
||||||
|
if (!(e instanceof PermissionException) || !isPrivateFile || !this.authService.isAdmin(this.ctx)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
const adminFilePath = this.fileService.getFile(key, userId, true);
|
||||||
|
if (!isImageFile(adminFilePath)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
return adminFilePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/// <reference types="mocha" />
|
||||||
|
/// <reference types="node" />
|
||||||
|
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
import { Constants } from "@certd/lib-server";
|
||||||
|
import { AuthorityMiddleware } from "./authority.js";
|
||||||
|
|
||||||
|
function createMiddleware(permission: string) {
|
||||||
|
const middleware = new AuthorityMiddleware();
|
||||||
|
middleware.secret = "test-secret";
|
||||||
|
middleware.webRouterService = {
|
||||||
|
async getMatchedRouterInfo() {
|
||||||
|
return { description: permission };
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
return middleware;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCtx(token?: string) {
|
||||||
|
return {
|
||||||
|
path: "/api/basic/file/download",
|
||||||
|
method: "GET",
|
||||||
|
query: token ? { token } : {},
|
||||||
|
headers: {},
|
||||||
|
get() {
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("AuthorityMiddleware guestOptionalAuth", () => {
|
||||||
|
it("continues without user when token is not provided", async () => {
|
||||||
|
const middleware = createMiddleware(Constants.per.guestOptionalAuth);
|
||||||
|
const ctx = createCtx();
|
||||||
|
let called = false;
|
||||||
|
|
||||||
|
await middleware.resolve()(ctx, async () => {
|
||||||
|
called = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(called, true);
|
||||||
|
assert.equal(ctx.user, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets user when token is provided", async () => {
|
||||||
|
const middleware = createMiddleware(Constants.per.guestOptionalAuth);
|
||||||
|
const token = jwt.sign({ id: 1, roles: [1] }, middleware.secret);
|
||||||
|
const ctx = createCtx(token);
|
||||||
|
|
||||||
|
await middleware.resolve()(ctx, async () => {});
|
||||||
|
|
||||||
|
assert.equal(ctx.user.id, 1);
|
||||||
|
assert.deepEqual(ctx.user.roles, [1]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -52,29 +52,7 @@ export class AuthorityMiddleware implements IWebMiddleware {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let token = ctx.get('Authorization') || '';
|
const token = this.getTokenFromRequest(ctx);
|
||||||
token = token.replace('Bearer ', '').trim();
|
|
||||||
if (!token) {
|
|
||||||
//尝试从cookie中获取token
|
|
||||||
const cookie = ctx.headers.cookie;
|
|
||||||
if (cookie) {
|
|
||||||
const items = cookie.split(';');
|
|
||||||
for (const item of items) {
|
|
||||||
if (!item || !item.trim()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const [key, value] = item.split('=');
|
|
||||||
if (key.trim() === 'certd_token') {
|
|
||||||
token = value.trim();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!token) {
|
|
||||||
//尝试从query中获取token
|
|
||||||
token = (ctx.query.token as string) || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
try {
|
try {
|
||||||
@@ -84,6 +62,10 @@ export class AuthorityMiddleware implements IWebMiddleware {
|
|||||||
return this.notAuth(ctx);
|
return this.notAuth(ctx);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (permission === Constants.per.guestOptionalAuth) {
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
//找找openKey
|
//找找openKey
|
||||||
const openKey = await this.doOpenHandler(ctx);
|
const openKey = await this.doOpenHandler(ctx);
|
||||||
if (!openKey) {
|
if (!openKey) {
|
||||||
@@ -101,6 +83,10 @@ export class AuthorityMiddleware implements IWebMiddleware {
|
|||||||
await next();
|
await next();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (permission === Constants.per.guestOptionalAuth) {
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const pass = await this.authService.checkPermission(ctx, permission);
|
const pass = await this.authService.checkPermission(ctx, permission);
|
||||||
if (!pass) {
|
if (!pass) {
|
||||||
@@ -123,6 +109,30 @@ export class AuthorityMiddleware implements IWebMiddleware {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getTokenFromRequest(ctx: IMidwayKoaContext) {
|
||||||
|
let token = ctx.get('Authorization') || '';
|
||||||
|
token = token.replace('Bearer ', '').trim();
|
||||||
|
if (token) {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cookie = ctx.headers.cookie;
|
||||||
|
if (cookie) {
|
||||||
|
const items = cookie.split(';');
|
||||||
|
for (const item of items) {
|
||||||
|
if (!item || !item.trim()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const [key, value] = item.split('=');
|
||||||
|
if (key.trim() === 'certd_token') {
|
||||||
|
return value.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ctx.query.token as string) || '';
|
||||||
|
}
|
||||||
|
|
||||||
async doOpenHandler(ctx: IMidwayKoaContext) {
|
async doOpenHandler(ctx: IMidwayKoaContext) {
|
||||||
//开放接口
|
//开放接口
|
||||||
const openKey = ctx.get('x-certd-token') || '';
|
const openKey = ctx.get('x-certd-token') || '';
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import assert from "assert";
|
||||||
|
import { PipelineEntity } from "../entity/pipeline.js";
|
||||||
|
import { PipelineService } from "./pipeline-service.js";
|
||||||
|
|
||||||
|
describe("PipelineService", () => {
|
||||||
|
it("does not start a pipeline run when beforeCheck fails", async () => {
|
||||||
|
const service = new PipelineService();
|
||||||
|
let historyStarted = false;
|
||||||
|
|
||||||
|
service.beforeCheck = async () => {
|
||||||
|
throw new Error("部署次数不足");
|
||||||
|
};
|
||||||
|
service.userService = {
|
||||||
|
async isAdmin() {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
service.historyService = {
|
||||||
|
async start() {
|
||||||
|
historyStarted = true;
|
||||||
|
throw new Error("history should not start");
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const entity = new PipelineEntity();
|
||||||
|
entity.id = 1;
|
||||||
|
entity.userId = 1;
|
||||||
|
entity.projectId = 0;
|
||||||
|
entity.content = JSON.stringify({
|
||||||
|
stages: [{ id: "stage1", tasks: [] }],
|
||||||
|
triggers: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await service.doRun(entity, null, "ALL");
|
||||||
|
|
||||||
|
assert.equal(historyStarted, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -659,6 +659,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||||||
suite = res.suite
|
suite = res.suite
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(`流水线${entity.id}触发失败(${triggerId}):${e.message}`);
|
logger.error(`流水线${entity.id}触发失败(${triggerId}):${e.message}`);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = entity.id;
|
const id = entity.id;
|
||||||
|
|||||||
+1
-2
@@ -55,9 +55,8 @@ export class CertApplyGetFormAliyunPlugin extends CertApplyBasePlugin {
|
|||||||
@TaskInput(
|
@TaskInput(
|
||||||
createRemoteSelectInputDefine({
|
createRemoteSelectInputDefine({
|
||||||
title: "证书订单 ID",
|
title: "证书订单 ID",
|
||||||
helper: "订阅模式的证书订单 Id(在新建流水线时暂时无法获取,可以先随便填个数字,先创建,进入流水线编辑页面再获取选择即可)",
|
helper: "订阅模式的证书订单 Id",
|
||||||
typeName: "CertApplyGetFormAliyun",
|
typeName: "CertApplyGetFormAliyun",
|
||||||
pageSize: 50,
|
|
||||||
component: {
|
component: {
|
||||||
name: "RemoteSelect",
|
name: "RemoteSelect",
|
||||||
vModel: "value",
|
vModel: "value",
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
23:26
|
00:23
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
23:48
|
00:32
|
||||||
|
|||||||
Reference in New Issue
Block a user