mirror of https://github.com/certd/certd
feat: 用户套餐,用户支付功能
parent
d70e2b66a3
commit
a019956698
13
README.md
13
README.md
|
@ -202,12 +202,13 @@ https://afdian.com/a/greper
|
||||||
|
|
||||||
专业版特权对比
|
专业版特权对比
|
||||||
|
|
||||||
| 功能 | 免费版 | 专业版 |
|
| 功能 | 基础版 | 专业版 |
|
||||||
|---------|-------------------|-----------------------|
|
|---------|-----------------|-------------------|
|
||||||
| 免费证书申请 | 免费无限制 | 免费无限制 |
|
| 免费证书申请 | 免费无限制 | 无限制 |
|
||||||
| 自动部署插件 | 阿里云、腾讯云、七牛云、主机部署等 | 支持群晖、宝塔、1Panel等,持续开发中 |
|
| 证书流水线条数 | 免费无限制 | 无限制 |
|
||||||
| 发邮件功能 | 需要配置 | 免配置 |
|
| 站点证书监控 | 1条 | 无限制 |
|
||||||
| 证书流水线条数 | 10条 | 无限制 |
|
| 自动部署插件 | 阿里云、腾讯云、七牛云、SSH | 支持群晖、宝塔、1Panel等,持续开发中 |
|
||||||
|
| 通知 | 邮件、webhook | server酱、企微、anpush、钉钉等 |
|
||||||
|
|
||||||
************************
|
************************
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"log4js": "^6.9.1",
|
"log4js": "^6.9.1",
|
||||||
"lru-cache": "^10.0.0",
|
"lru-cache": "^10.0.0",
|
||||||
|
"mitt": "^3.0.1",
|
||||||
"nanoid": "^5.0.7",
|
"nanoid": "^5.0.7",
|
||||||
"node-forge": "^1.3.1",
|
"node-forge": "^1.3.1",
|
||||||
"nodemailer": "^6.9.3"
|
"nodemailer": "^6.9.3"
|
||||||
|
|
|
@ -8,6 +8,7 @@ export * from './util.hash.js';
|
||||||
export * from './util.merge.js';
|
export * from './util.merge.js';
|
||||||
export * from './util.cache.js';
|
export * from './util.cache.js';
|
||||||
export * from './util.string.js';
|
export * from './util.string.js';
|
||||||
|
export * from './util.lock.js';
|
||||||
import { stringUtils } from './util.string.js';
|
import { stringUtils } from './util.string.js';
|
||||||
import sleep from './util.sleep.js';
|
import sleep from './util.sleep.js';
|
||||||
import { http, download } from './util.request.js';
|
import { http, download } from './util.request.js';
|
||||||
|
@ -24,6 +25,8 @@ import { domainUtils } from './util.domain.js';
|
||||||
import { optionsUtils } from './util.options.js';
|
import { optionsUtils } from './util.options.js';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import * as id from './util.id.js';
|
import * as id from './util.id.js';
|
||||||
|
import { locker } from './util.lock.js';
|
||||||
|
import { mitter } from './util.mitter.js';
|
||||||
export const utils = {
|
export const utils = {
|
||||||
sleep,
|
sleep,
|
||||||
http,
|
http,
|
||||||
|
@ -41,4 +44,6 @@ export const utils = {
|
||||||
domain: domainUtils,
|
domain: domainUtils,
|
||||||
options: optionsUtils,
|
options: optionsUtils,
|
||||||
string: stringUtils,
|
string: stringUtils,
|
||||||
|
locker,
|
||||||
|
mitter,
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,7 +3,10 @@ import crypto from 'crypto';
|
||||||
function md5(data: string) {
|
function md5(data: string) {
|
||||||
return crypto.createHash('md5').update(data).digest('hex');
|
return crypto.createHash('md5').update(data).digest('hex');
|
||||||
}
|
}
|
||||||
|
function sha256(data: string) {
|
||||||
|
return crypto.createHash('sha256').update(data).digest('hex');
|
||||||
|
}
|
||||||
export const hashUtils = {
|
export const hashUtils = {
|
||||||
md5,
|
md5,
|
||||||
|
sha256,
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { logger, utils } from './index.js';
|
||||||
|
|
||||||
|
export class Locker {
|
||||||
|
locked: Record<string, any> = {};
|
||||||
|
|
||||||
|
async execute(lockStr: string, callback: any) {
|
||||||
|
await this.lock(lockStr);
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
logger.warn('Lock timeout,自动解锁', lockStr);
|
||||||
|
this.unlock(lockStr);
|
||||||
|
}, 20000);
|
||||||
|
try {
|
||||||
|
return await callback();
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
this.unlock(lockStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async lock(str: string) {
|
||||||
|
const isLocked = this.isLocked(str);
|
||||||
|
if (isLocked) {
|
||||||
|
let count = 0;
|
||||||
|
while (true) {
|
||||||
|
await utils.sleep(100);
|
||||||
|
if (!this.isLocked(str)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
if (count > 20) {
|
||||||
|
throw new Error('Lock timeout');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.locked[str] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
unlock(str: string) {
|
||||||
|
const isLocked = this.locked[str];
|
||||||
|
if (isLocked != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
delete this.locked[str];
|
||||||
|
}
|
||||||
|
|
||||||
|
isLocked(str: string) {
|
||||||
|
return this.locked[str] ?? false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const locker = new Locker();
|
|
@ -0,0 +1,2 @@
|
||||||
|
import mitt from 'mitt';
|
||||||
|
export const mitter = mitt();
|
|
@ -99,7 +99,7 @@ export abstract class BaseService<T> {
|
||||||
* 新增|修改
|
* 新增|修改
|
||||||
* @param param 数据
|
* @param param 数据
|
||||||
*/
|
*/
|
||||||
async addOrUpdate(param) {
|
async addOrUpdate(param: any) {
|
||||||
await this.getRepository().save(param);
|
await this.getRepository().save(param);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ export abstract class BaseService<T> {
|
||||||
* 新增
|
* 新增
|
||||||
* @param param 数据
|
* @param param 数据
|
||||||
*/
|
*/
|
||||||
async add(param) {
|
async add(param: any) {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
param.createTime = now;
|
param.createTime = now;
|
||||||
param.updateTime = now;
|
param.updateTime = now;
|
||||||
|
@ -122,7 +122,7 @@ export abstract class BaseService<T> {
|
||||||
* 修改
|
* 修改
|
||||||
* @param param 数据
|
* @param param 数据
|
||||||
*/
|
*/
|
||||||
async update(param) {
|
async update(param: any) {
|
||||||
if (!param.id) throw new ValidateException('id 不能为空');
|
if (!param.id) throw new ValidateException('id 不能为空');
|
||||||
param.updateTime = new Date();
|
param.updateTime = new Date();
|
||||||
await this.addOrUpdate(param);
|
await this.addOrUpdate(param);
|
||||||
|
|
|
@ -36,6 +36,10 @@ export const Constants = {
|
||||||
code: 88,
|
code: 88,
|
||||||
message: '需要VIP',
|
message: '需要VIP',
|
||||||
},
|
},
|
||||||
|
needsuite: {
|
||||||
|
code: 89,
|
||||||
|
message: '需要购买或升级套餐',
|
||||||
|
},
|
||||||
loginError: {
|
loginError: {
|
||||||
code: 2,
|
code: 2,
|
||||||
message: '登录失败',
|
message: '登录失败',
|
||||||
|
|
|
@ -8,3 +8,9 @@ export class NeedVIPException extends BaseException {
|
||||||
super('NeedVIPException', Constants.res.needvip.code, message ? message : Constants.res.needvip.message);
|
super('NeedVIPException', Constants.res.needvip.code, message ? message : Constants.res.needvip.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class NeedSuiteException extends BaseException {
|
||||||
|
constructor(message) {
|
||||||
|
super('NeedSuiteException', Constants.res.needsuite.code, message ? message : Constants.res.needsuite.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { SysSettingsEntity } from './system/index.js';
|
import { SysSettingsEntity } from './system/index.js';
|
||||||
|
import { AccessEntity } from './user/access/entity/access.js';
|
||||||
export * from './basic/index.js';
|
export * from './basic/index.js';
|
||||||
export * from './system/index.js';
|
export * from './system/index.js';
|
||||||
|
export * from './user/index.js';
|
||||||
export { LibServerConfiguration as Configuration } from './configuration.js';
|
export { LibServerConfiguration as Configuration } from './configuration.js';
|
||||||
|
|
||||||
export const libServerEntities = [SysSettingsEntity];
|
export const libServerEntities = [SysSettingsEntity, AccessEntity];
|
||||||
|
|
|
@ -136,3 +136,35 @@ export class SysHeaderMenus extends BaseSettings {
|
||||||
|
|
||||||
menus: MenuItem[];
|
menus: MenuItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PaymentItem = {
|
||||||
|
enabled: boolean;
|
||||||
|
accessId?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class SysPaymentSetting extends BaseSettings {
|
||||||
|
static __title__ = '支付设置';
|
||||||
|
static __key__ = 'sys.payment';
|
||||||
|
static __access__ = 'private';
|
||||||
|
|
||||||
|
yizhifu?: PaymentItem = { enabled: false };
|
||||||
|
|
||||||
|
alipay?: PaymentItem = { enabled: false };
|
||||||
|
|
||||||
|
wxpay?: PaymentItem = { enabled: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SysSuiteSetting extends BaseSettings {
|
||||||
|
static __title__ = '套餐设置';
|
||||||
|
static __key__ = 'sys.suite';
|
||||||
|
static __access__ = 'private';
|
||||||
|
|
||||||
|
enabled = false;
|
||||||
|
|
||||||
|
registerGift?: {
|
||||||
|
productId: number;
|
||||||
|
duration: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
intro?: string;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
export * from './entity/access.js';
|
||||||
|
export * from './service/access-service.js';
|
||||||
|
export * from './service/access-sys-getter.js';
|
||||||
|
export * from './service/access-getter.js';
|
||||||
|
export * from './service/encrypt-service.js';
|
|
@ -1,7 +1,7 @@
|
||||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { BaseService, PageReq, PermissionException, ValidateException } from '@certd/lib-server';
|
import { BaseService, PageReq, PermissionException, ValidateException } from '../../../index.js';
|
||||||
import { AccessEntity } from '../entity/access.js';
|
import { AccessEntity } from '../entity/access.js';
|
||||||
import { AccessDefine, accessRegistry, newAccess } from '@certd/pipeline';
|
import { AccessDefine, accessRegistry, newAccess } from '@certd/pipeline';
|
||||||
import { EncryptService } from './encrypt-service.js';
|
import { EncryptService } from './encrypt-service.js';
|
||||||
|
@ -18,6 +18,7 @@ export class AccessService extends BaseService<AccessEntity> {
|
||||||
@Inject()
|
@Inject()
|
||||||
encryptService: EncryptService;
|
encryptService: EncryptService;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
getRepository() {
|
getRepository() {
|
||||||
return this.repository;
|
return this.repository;
|
|
@ -1,7 +1,6 @@
|
||||||
import { Init, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
import { Init, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import { SysSettingsService } from '@certd/lib-server';
|
import { SysPrivateSettings, SysSettingsService } from '../../../system/index.js';
|
||||||
import { SysPrivateSettings } from '@certd/lib-server';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 授权
|
* 授权
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './access/index.js';
|
|
@ -0,0 +1,33 @@
|
||||||
|
<template>
|
||||||
|
<span class="cd-expires-time-text">
|
||||||
|
<template v-if="label != null">
|
||||||
|
{{ label }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<FsTimeHumanize :model-value="value" :use-format-greater="1000000000000" :options="{ units: ['d'] }"></FsTimeHumanize>
|
||||||
|
</template>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "ExpiresTimeText"
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
value?: number;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const label = computed(() => {
|
||||||
|
if (props.value == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (props.value === -1) {
|
||||||
|
return "永久";
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -9,6 +9,7 @@ import "@vue-js-cron/light/dist/light.css";
|
||||||
import Plugins from "./plugins/index";
|
import Plugins from "./plugins/index";
|
||||||
import LoadingButton from "./loading-button.vue";
|
import LoadingButton from "./loading-button.vue";
|
||||||
import IconSelect from "./icon-select.vue";
|
import IconSelect from "./icon-select.vue";
|
||||||
|
import ExpiresTimeText from "./expires-time-text.vue";
|
||||||
export default {
|
export default {
|
||||||
install(app: any) {
|
install(app: any) {
|
||||||
app.component("PiContainer", PiContainer);
|
app.component("PiContainer", PiContainer);
|
||||||
|
@ -25,7 +26,7 @@ export default {
|
||||||
|
|
||||||
app.component("LoadingButton", LoadingButton);
|
app.component("LoadingButton", LoadingButton);
|
||||||
app.component("IconSelect", IconSelect);
|
app.component("IconSelect", IconSelect);
|
||||||
|
app.component("ExpiresTimeText", ExpiresTimeText);
|
||||||
app.use(vip);
|
app.use(vip);
|
||||||
app.use(Plugins);
|
app.use(Plugins);
|
||||||
}
|
}
|
||||||
|
|
|
@ -231,13 +231,13 @@ function openUpgrade() {
|
||||||
title: "基础版",
|
title: "基础版",
|
||||||
desc: "免费使用",
|
desc: "免费使用",
|
||||||
type: "free",
|
type: "free",
|
||||||
privilege: ["证书申请功能无限制", "证书流水线数量10条", "常用的主机、cdn等部署插件"]
|
privilege: ["证书申请功能无限制", "证书流水线数量无限制", "常用的主机、云平台、cdn等部署插件"]
|
||||||
},
|
},
|
||||||
plus: {
|
plus: {
|
||||||
title: "专业版",
|
title: "专业版",
|
||||||
desc: "功能增强,适用于个人企业内部使用",
|
desc: "功能增强,适用于个人企业内部使用",
|
||||||
type: "plus",
|
type: "plus",
|
||||||
privilege: ["可加VIP群,需求优先实现", "证书流水线数量无限制", "免配置发邮件功能", "支持宝塔、易盾、群晖、1Panel、cdnfly等部署插件"],
|
privilege: ["可加VIP群,需求优先实现", "宝塔、群晖、1Panel、易盾等部署插件", "站点证书监控", "更多通知种类"],
|
||||||
trial: {
|
trial: {
|
||||||
title: "7天试用",
|
title: "7天试用",
|
||||||
click: () => {
|
click: () => {
|
||||||
|
|
|
@ -71,7 +71,7 @@ export const certdResources = [
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "分组管理",
|
title: "流水线分组管理",
|
||||||
name: "PipelineGroupManager",
|
name: "PipelineGroupManager",
|
||||||
path: "/certd/pipeline/group",
|
path: "/certd/pipeline/group",
|
||||||
component: "/certd/pipeline/group/index.vue",
|
component: "/certd/pipeline/group/index.vue",
|
||||||
|
@ -90,6 +90,47 @@ export const certdResources = [
|
||||||
auth: true
|
auth: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "我的订单",
|
||||||
|
name: "MyTrade",
|
||||||
|
path: "/certd/trade",
|
||||||
|
component: "/certd/trade/index.vue",
|
||||||
|
meta: {
|
||||||
|
icon: "ion:person-outline",
|
||||||
|
auth: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "支付返回",
|
||||||
|
name: "PaymentReturn",
|
||||||
|
path: "/certd/payment/return/:type",
|
||||||
|
component: "/certd/payment/return.vue",
|
||||||
|
meta: {
|
||||||
|
icon: "ion:person-outline",
|
||||||
|
auth: false,
|
||||||
|
isMenu: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "站点证书监控",
|
||||||
|
name: "SiteCertMonitor",
|
||||||
|
path: "/certd/monitor/site",
|
||||||
|
component: "/certd/monitor/site/index.vue",
|
||||||
|
meta: {
|
||||||
|
icon: "ion:person-outline",
|
||||||
|
auth: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "证书仓库",
|
||||||
|
name: "CertStore",
|
||||||
|
path: "/certd/monitor/cert",
|
||||||
|
component: "/certd/monitor/cert/index.vue",
|
||||||
|
meta: {
|
||||||
|
icon: "ion:person-outline",
|
||||||
|
auth: true
|
||||||
|
}
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
// title: "邮箱设置",
|
// title: "邮箱设置",
|
||||||
// name: "EmailSetting",
|
// name: "EmailSetting",
|
||||||
|
|
|
@ -171,46 +171,24 @@ export const sysResources = [
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "套餐支付",
|
title: "套餐设置",
|
||||||
name: "SuiteManager",
|
name: "SuiteSetting",
|
||||||
path: "/sys/suite",
|
path: "/sys/suite/setting",
|
||||||
redirect: "/sys/suite/product",
|
component: "/sys/suite/setting/index.vue",
|
||||||
meta: {
|
meta: {
|
||||||
icon: "ion:golf-outline",
|
icon: "ion:person-outline",
|
||||||
permission: "sys:settings:view"
|
permission: "sys:settings:edit"
|
||||||
},
|
}
|
||||||
children: [
|
},
|
||||||
{
|
{
|
||||||
title: "套餐管理",
|
title: "订单管理",
|
||||||
name: "ProductManager",
|
name: "OrderManager",
|
||||||
path: "/sys/suite/product",
|
path: "/sys/suite/trade",
|
||||||
component: "/sys/suite/product/index.vue",
|
component: "/sys/suite/trade/index.vue",
|
||||||
meta: {
|
meta: {
|
||||||
icon: "ion:person-outline",
|
icon: "ion:person-outline",
|
||||||
permission: "sys:settings:edit"
|
permission: "sys:settings:edit"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "支付管理",
|
|
||||||
name: "PaymentManager",
|
|
||||||
path: "/sys/suite/payment",
|
|
||||||
component: "/sys/suite/payment/index.vue",
|
|
||||||
meta: {
|
|
||||||
icon: "ion:person-outline",
|
|
||||||
permission: "sys:settings:edit"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "订单管理",
|
|
||||||
name: "OrderManager",
|
|
||||||
path: "/sys/suite/order",
|
|
||||||
component: "/sys/suite/order/index.vue",
|
|
||||||
meta: {
|
|
||||||
icon: "ion:person-outline",
|
|
||||||
permission: "sys:settings:edit"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// {
|
// {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { request } from "/src/api/service";
|
import { request } from "/src/api/service";
|
||||||
|
|
||||||
export function createPaymentApi() {
|
export function createApi() {
|
||||||
const apiPrefix = "/sys/suite/payment";
|
const apiPrefix = "/pi/pipeline/group";
|
||||||
return {
|
return {
|
||||||
async GetList(query: any) {
|
async GetList(query: any) {
|
||||||
return await request({
|
return await request({
|
||||||
|
@ -42,35 +42,13 @@ export function createPaymentApi() {
|
||||||
params: { id }
|
params: { id }
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
async ListAll() {
|
||||||
async GetOptions(id: number) {
|
|
||||||
return await request({
|
return await request({
|
||||||
url: apiPrefix + "/options",
|
url: apiPrefix + "/all",
|
||||||
method: "post"
|
method: "post"
|
||||||
});
|
});
|
||||||
},
|
|
||||||
|
|
||||||
async GetSimpleInfo(id: number) {
|
|
||||||
return await request({
|
|
||||||
url: apiPrefix + "/simpleInfo",
|
|
||||||
method: "post",
|
|
||||||
params: { id }
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
async GetDefineTypes() {
|
|
||||||
return await request({
|
|
||||||
url: apiPrefix + "/getTypeDict",
|
|
||||||
method: "post"
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
async GetProviderDefine(type: string) {
|
|
||||||
return await request({
|
|
||||||
url: apiPrefix + "/define",
|
|
||||||
method: "post",
|
|
||||||
params: { type }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const pipelineGroupApi = createApi();
|
|
@ -0,0 +1,178 @@
|
||||||
|
// @ts-ignore
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||||
|
import { pipelineGroupApi } from "./api";
|
||||||
|
|
||||||
|
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
|
const { t } = useI18n();
|
||||||
|
const api = pipelineGroupApi;
|
||||||
|
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||||
|
return await api.GetList(query);
|
||||||
|
};
|
||||||
|
const editRequest = async (req: EditReq) => {
|
||||||
|
const { form, row } = req;
|
||||||
|
form.id = row.id;
|
||||||
|
const res = await api.UpdateObj(form);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
const delRequest = async (req: DelReq) => {
|
||||||
|
const { row } = req;
|
||||||
|
return await api.DelObj(row.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addRequest = async (req: AddReq) => {
|
||||||
|
const { form } = req;
|
||||||
|
const res = await api.AddObj(form);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
crudOptions: {
|
||||||
|
request: {
|
||||||
|
pageRequest,
|
||||||
|
addRequest,
|
||||||
|
editRequest,
|
||||||
|
delRequest
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
labelCol: {
|
||||||
|
//固定label宽度
|
||||||
|
span: null,
|
||||||
|
style: {
|
||||||
|
width: "100px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
col: {
|
||||||
|
span: 22
|
||||||
|
},
|
||||||
|
wrapper: {
|
||||||
|
width: 600
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rowHandle: {
|
||||||
|
width: 200
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
id: {
|
||||||
|
title: "ID",
|
||||||
|
key: "id",
|
||||||
|
type: "number",
|
||||||
|
search: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 100,
|
||||||
|
editable: {
|
||||||
|
disabled: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
domain: {
|
||||||
|
title: "主域名",
|
||||||
|
search: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 100,
|
||||||
|
sorter: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
domains: {
|
||||||
|
title: "全部域名",
|
||||||
|
search: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
rules: [{ required: true, message: "请输入域名" }]
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 300,
|
||||||
|
sorter: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
domainCount: {
|
||||||
|
title: "域名数量",
|
||||||
|
type: "number",
|
||||||
|
form: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 120,
|
||||||
|
sorter: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pipelineId: {
|
||||||
|
title: "已关联流水线",
|
||||||
|
search: { show: false },
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 200,
|
||||||
|
sorter: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
applyTime: {
|
||||||
|
title: "申请时间",
|
||||||
|
search: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
type: "datetime",
|
||||||
|
form: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
sorter: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expiresTime: {
|
||||||
|
title: "过期时间",
|
||||||
|
search: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
type: "date",
|
||||||
|
form: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
sorter: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fromType: {
|
||||||
|
title: "来源",
|
||||||
|
search: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
type: "text",
|
||||||
|
form: { show: false },
|
||||||
|
column: {
|
||||||
|
width: 100,
|
||||||
|
sorter: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
certProvider: {
|
||||||
|
title: "证书颁发机构",
|
||||||
|
search: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 400
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
<template>
|
||||||
|
<fs-page>
|
||||||
|
<template #header>
|
||||||
|
<div class="title">
|
||||||
|
证书仓库
|
||||||
|
<span class="sub">管理证书,支持手动上传证书并部署</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||||
|
</fs-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { defineComponent, onActivated, onMounted } from "vue";
|
||||||
|
import { useFs } from "@fast-crud/fast-crud";
|
||||||
|
import createCrudOptions from "./crud";
|
||||||
|
import { createApi } from "./api";
|
||||||
|
defineOptions({
|
||||||
|
name: "CertStore"
|
||||||
|
});
|
||||||
|
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} });
|
||||||
|
|
||||||
|
// 页面打开后获取列表数据
|
||||||
|
onMounted(() => {
|
||||||
|
crudExpose.doRefresh();
|
||||||
|
});
|
||||||
|
onActivated(() => {
|
||||||
|
crudExpose.doRefresh();
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { request } from "/src/api/service";
|
||||||
|
|
||||||
|
export function createApi() {
|
||||||
|
const apiPrefix = "/pi/pipeline/group";
|
||||||
|
return {
|
||||||
|
async GetList(query: any) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/page",
|
||||||
|
method: "post",
|
||||||
|
data: query
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async AddObj(obj: any) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/add",
|
||||||
|
method: "post",
|
||||||
|
data: obj
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async UpdateObj(obj: any) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/update",
|
||||||
|
method: "post",
|
||||||
|
data: obj
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async DelObj(id: number) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/delete",
|
||||||
|
method: "post",
|
||||||
|
params: { id }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async GetObj(id: number) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/info",
|
||||||
|
method: "post",
|
||||||
|
params: { id }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async ListAll() {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/all",
|
||||||
|
method: "post"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const pipelineGroupApi = createApi();
|
|
@ -0,0 +1,228 @@
|
||||||
|
// @ts-ignore
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||||
|
import { pipelineGroupApi } from "./api";
|
||||||
|
|
||||||
|
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
|
const { t } = useI18n();
|
||||||
|
const api = pipelineGroupApi;
|
||||||
|
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||||
|
return await api.GetList(query);
|
||||||
|
};
|
||||||
|
const editRequest = async (req: EditReq) => {
|
||||||
|
const { form, row } = req;
|
||||||
|
form.id = row.id;
|
||||||
|
const res = await api.UpdateObj(form);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
const delRequest = async (req: DelReq) => {
|
||||||
|
const { row } = req;
|
||||||
|
return await api.DelObj(row.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addRequest = async (req: AddReq) => {
|
||||||
|
const { form } = req;
|
||||||
|
const res = await api.AddObj(form);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
crudOptions: {
|
||||||
|
request: {
|
||||||
|
pageRequest,
|
||||||
|
addRequest,
|
||||||
|
editRequest,
|
||||||
|
delRequest
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
labelCol: {
|
||||||
|
//固定label宽度
|
||||||
|
span: null,
|
||||||
|
style: {
|
||||||
|
width: "100px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
col: {
|
||||||
|
span: 22
|
||||||
|
},
|
||||||
|
wrapper: {
|
||||||
|
width: 600
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rowHandle: {
|
||||||
|
width: 200
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
id: {
|
||||||
|
title: "ID",
|
||||||
|
key: "id",
|
||||||
|
type: "number",
|
||||||
|
search: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 100,
|
||||||
|
editable: {
|
||||||
|
disabled: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
name: {
|
||||||
|
title: "站点名称",
|
||||||
|
search: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
rules: [{ required: true, message: "请输入站点名称" }]
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
domain: {
|
||||||
|
title: "主域名",
|
||||||
|
search: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
rules: [{ required: true, message: "请输入域名" }]
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 100,
|
||||||
|
sorter: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
domains: {
|
||||||
|
title: "其他域名",
|
||||||
|
search: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
rules: [{ required: true, message: "请输入其他域名" }]
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 300,
|
||||||
|
sorter: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
certInfo: {
|
||||||
|
title: "证书详情",
|
||||||
|
search: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
certStatus: {
|
||||||
|
title: "证书状态",
|
||||||
|
search: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 100,
|
||||||
|
sorter: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
certExpiresTime: {
|
||||||
|
title: "证书到期时间",
|
||||||
|
search: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
type: "date",
|
||||||
|
form: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
sorter: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lastCheckTime: {
|
||||||
|
title: "上次检查时间",
|
||||||
|
search: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
type: "datetime",
|
||||||
|
form: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
sorter: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
checkStatus: {
|
||||||
|
title: "检查状态",
|
||||||
|
search: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 100,
|
||||||
|
sorter: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pipelineId: {
|
||||||
|
title: "关联流水线id",
|
||||||
|
search: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
type: "number",
|
||||||
|
column: {
|
||||||
|
width: 200,
|
||||||
|
sorter: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
certInfoId: {
|
||||||
|
title: "证书id",
|
||||||
|
search: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
type: "number",
|
||||||
|
form: {},
|
||||||
|
column: {
|
||||||
|
width: 100,
|
||||||
|
sorter: true,
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
title: "禁用启用",
|
||||||
|
search: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
type: "dict-switch",
|
||||||
|
dict: dict({
|
||||||
|
data: [
|
||||||
|
{ label: "禁用", value: true, color: "red" },
|
||||||
|
{ label: "启用", value: false, color: "green" }
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
form: {},
|
||||||
|
column: {
|
||||||
|
width: 100,
|
||||||
|
sorter: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
<template>
|
||||||
|
<fs-page>
|
||||||
|
<template #header>
|
||||||
|
<div class="title">
|
||||||
|
站点证书监控
|
||||||
|
<span class="sub">监控网站证书的过期时间,并发出提醒</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||||
|
</fs-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { defineComponent, onActivated, onMounted } from "vue";
|
||||||
|
import { useFs } from "@fast-crud/fast-crud";
|
||||||
|
import createCrudOptions from "./crud";
|
||||||
|
import { createApi } from "./api";
|
||||||
|
defineOptions({
|
||||||
|
name: "SiteCertMonitor"
|
||||||
|
});
|
||||||
|
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} });
|
||||||
|
|
||||||
|
// 页面打开后获取列表数据
|
||||||
|
onMounted(() => {
|
||||||
|
crudExpose.doRefresh();
|
||||||
|
});
|
||||||
|
onActivated(() => {
|
||||||
|
crudExpose.doRefresh();
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { request } from "/src/api/service";
|
||||||
|
|
||||||
|
const apiPrefix = "/pay";
|
||||||
|
export async function Notify(type: string, query: any) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + `/notify/${type}`,
|
||||||
|
method: "post",
|
||||||
|
data: query,
|
||||||
|
returnResponse: true
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
<template>
|
||||||
|
<div class="cd-payment-return">
|
||||||
|
<a-card title="支付结果" class="mt-10">
|
||||||
|
<div class="flex-o">
|
||||||
|
<div class="flex-1">
|
||||||
|
<a-tag v-if="payResult" color="green" class="m-0">支付成功</a-tag>
|
||||||
|
<a-tag v-else color="red" class="m-0">支付失败</a-tag>
|
||||||
|
</div>
|
||||||
|
<div class="m-10">
|
||||||
|
<a-button type="primary" @click="goHome">回首页</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Ref, ref } from "vue";
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
import * as api from "./api";
|
||||||
|
const route = useRoute();
|
||||||
|
const type = route.params.type as string;
|
||||||
|
|
||||||
|
const query = route.query;
|
||||||
|
|
||||||
|
async function checkNotify() {
|
||||||
|
const res = await api.Notify(type, query);
|
||||||
|
if (res === "success") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payResult: Ref = ref(null);
|
||||||
|
async function check() {
|
||||||
|
const pass = await checkNotify();
|
||||||
|
if (!pass) {
|
||||||
|
payResult.value = false;
|
||||||
|
} else {
|
||||||
|
payResult.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
function goHome() {
|
||||||
|
router.push({
|
||||||
|
path: "/"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -2,7 +2,7 @@
|
||||||
<fs-page>
|
<fs-page>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
分组管理
|
流水线分组管理
|
||||||
<span class="sub">管理流水线分组</span>
|
<span class="sub">管理流水线分组</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -51,3 +51,17 @@ export async function TradeCreate(form: TradeCreateReq) {
|
||||||
data: form
|
data: form
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function GetPaymentTypes() {
|
||||||
|
return await request({
|
||||||
|
url: "/suite/trade/payments",
|
||||||
|
method: "POST"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GetSuiteSetting() {
|
||||||
|
return await request({
|
||||||
|
url: "/suite/settings/get",
|
||||||
|
method: "POST"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -4,8 +4,14 @@
|
||||||
<div class="title">套餐购买</div>
|
<div class="title">套餐购买</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="suite-buy-content">
|
<div class="suite-buy-content">
|
||||||
<a-row :gutter="12">
|
<div class="mb-10">
|
||||||
<a-col v-for="item of products" :key="item.id" class="mb-10" :xs="12" :sm="12" :md="8" :lg="6" :xl="6" :xxl="4">
|
<a-card>
|
||||||
|
<div>套餐说明:多个套餐内的数量可以叠加</div>
|
||||||
|
<div v-if="suiteIntro" v-html="suiteIntro"></div>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
<a-row :gutter="8">
|
||||||
|
<a-col v-for="item of products" :key="item.id" class="mb-10 suite-card-col">
|
||||||
<product-info :product="item" @order="doOrder" />
|
<product-info :product="item" @order="doOrder" />
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
@ -34,6 +40,13 @@ async function doOrder(req: any) {
|
||||||
...req
|
...req
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const suiteIntro = ref("");
|
||||||
|
async function loadSuiteIntro() {
|
||||||
|
const res = await api.GetSuiteSetting();
|
||||||
|
suiteIntro.value = res.intro;
|
||||||
|
}
|
||||||
|
loadSuiteIntro();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
|
@ -76,6 +89,11 @@ async function doOrder(req: any) {
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.suite-card-col {
|
||||||
|
width: 20% !important;
|
||||||
|
min-width: 360px !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
<div>
|
|
||||||
|
|
||||||
</div>
|
|
|
@ -12,17 +12,13 @@
|
||||||
|
|
||||||
<div class="flex-o mt-5">
|
<div class="flex-o mt-5">
|
||||||
时长:
|
时长:
|
||||||
<a-tag color="green"> {{ durationDict.dataMap[formRef.duration]?.label }}</a-tag>
|
<duration-value v-model="formRef.duration"></duration-value>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-o mt-5">价格: <price-input :edit="false" :model-value="durationSelected.price"></price-input></div>
|
<div class="flex-o mt-5">价格: <price-input :edit="false" :model-value="durationSelected.price"></price-input></div>
|
||||||
|
|
||||||
<div class="flex-o mt-5">
|
<div class="flex-o mt-5">
|
||||||
支付方式:
|
支付方式:
|
||||||
<a-select v-model:value="formRef.payType">
|
<fs-dict-select v-model:value="formRef.payType" :dict="paymentsDictRef" style="width: 200px"> </fs-dict-select>
|
||||||
<a-select-option value="yizhifu">易支付</a-select-option>
|
|
||||||
<a-select-option value="alipay">支付宝</a-select-option>
|
|
||||||
<a-select-option value="wxpay">微信支付</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
@ -30,11 +26,13 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { durationDict, OrderModalOpenReq, TradeCreate } from "/@/views/certd/suite/api";
|
import { GetPaymentTypes, OrderModalOpenReq, TradeCreate } from "/@/views/certd/suite/api";
|
||||||
import SuiteValue from "/@/views/sys/suite/product/suite-value.vue";
|
import SuiteValue from "/@/views/sys/suite/product/suite-value.vue";
|
||||||
import PriceInput from "/@/views/sys/suite/product/price-input.vue";
|
import PriceInput from "/@/views/sys/suite/product/price-input.vue";
|
||||||
import { notification } from "ant-design-vue";
|
|
||||||
import modal from "/@/views/certd/notification/notification-selector/modal/index.vue";
|
import modal from "/@/views/certd/notification/notification-selector/modal/index.vue";
|
||||||
|
import { dict } from "@fast-crud/fast-crud";
|
||||||
|
import { notification } from "ant-design-vue";
|
||||||
|
import DurationValue from "/@/views/sys/suite/product/duration-value.vue";
|
||||||
|
|
||||||
const openRef = ref(false);
|
const openRef = ref(false);
|
||||||
|
|
||||||
|
@ -50,11 +48,26 @@ async function open(opts: OrderModalOpenReq) {
|
||||||
formRef.value.productId = opts.product.id;
|
formRef.value.productId = opts.product.id;
|
||||||
formRef.value.duration = opts.duration;
|
formRef.value.duration = opts.duration;
|
||||||
formRef.value.num = opts.num ?? 1;
|
formRef.value.num = opts.num ?? 1;
|
||||||
formRef.value.payType = "alipay";
|
|
||||||
}
|
}
|
||||||
|
const paymentsDictRef = dict({
|
||||||
|
async getData() {
|
||||||
|
return await GetPaymentTypes();
|
||||||
|
},
|
||||||
|
onReady: ({ dict }) => {
|
||||||
|
if (dict.data.length > 0) {
|
||||||
|
formRef.value.payType = dict.data[0].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function orderCreate() {
|
async function orderCreate() {
|
||||||
console.log("orderCreate", formRef.value);
|
console.log("orderCreate", formRef.value);
|
||||||
|
if (!formRef.value.payType) {
|
||||||
|
notification.error({
|
||||||
|
message: "请选择支付方式"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
const paymentReq = await TradeCreate({
|
const paymentReq = await TradeCreate({
|
||||||
productId: formRef.value.productId,
|
productId: formRef.value.productId,
|
||||||
duration: formRef.value.duration,
|
duration: formRef.value.duration,
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<a-card :title="product.title" class="product-card">
|
<a-card :title="product.title" class="product-card">
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<a-tag>{{ product.type }}</a-tag>
|
<fs-values-format v-model="product.type" :dict="productTypeDictRef"></fs-values-format>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div>{{ product.intro }}</div>
|
<div class="product-intro">{{ product.intro || "暂无介绍" }}</div>
|
||||||
<div class="hr">
|
<a-divider />
|
||||||
|
<div>
|
||||||
<div class="flex-between mt-5">
|
<div class="flex-between mt-5">
|
||||||
<div class="flex-o"><fs-icon icon="ant-design:check-outlined" class="color-green mr-5" /> 流水线条数:</div>
|
<div class="flex-o"><fs-icon icon="ant-design:check-outlined" class="color-green mr-5" /> 流水线条数:</div>
|
||||||
<suite-value :model-value="product.content.maxPipelineCount" unit="条" />
|
<suite-value :model-value="product.content.maxPipelineCount" unit="条" />
|
||||||
|
@ -20,13 +21,12 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-between mt-5">
|
<div class="flex-between mt-5">
|
||||||
<div class="flex-o"><fs-icon icon="ant-design:check-outlined" class="color-green mr-5" /> 证书监控:</div>
|
<div class="flex-o"><fs-icon icon="ant-design:check-outlined" class="color-green mr-5" /> 证书监控:</div>
|
||||||
<a-tag v-if="product.content.sproductonitor" color="green" class="m-0">支持</a-tag>
|
<suite-value :model-value="product.content.maxMonitorCount" unit="个" />
|
||||||
<a-tag v-else color="gray" class="m-0">不支持</a-tag>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<a-divider />
|
||||||
<div class="duration flex-between mt-5 hr">
|
<div class="duration flex-between mt-5">
|
||||||
<div class="flex-o">时长</div>
|
<div class="flex-o duration-label">时长</div>
|
||||||
<div class="duration-list">
|
<div class="duration-list">
|
||||||
<div
|
<div
|
||||||
v-for="dp of product.durationPrices"
|
v-for="dp of product.durationPrices"
|
||||||
|
@ -39,8 +39,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<a-divider />
|
||||||
<div class="price flex-between mt-5 hr">
|
<div class="price flex-between mt-5">
|
||||||
<div class="flex-o">价格</div>
|
<div class="flex-o">价格</div>
|
||||||
<div class="flex-o price-text">
|
<div class="flex-o price-text">
|
||||||
<price-input style="font-size: 18px; color: red" :model-value="selected?.price" :edit="false" />
|
<price-input style="font-size: 18px; color: red" :model-value="selected?.price" :edit="false" />
|
||||||
|
@ -58,12 +58,20 @@ import { durationDict } from "/@/views/certd/suite/api";
|
||||||
import SuiteValue from "/@/views/sys/suite/product/suite-value.vue";
|
import SuiteValue from "/@/views/sys/suite/product/suite-value.vue";
|
||||||
import PriceInput from "/@/views/sys/suite/product/price-input.vue";
|
import PriceInput from "/@/views/sys/suite/product/price-input.vue";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
import { dict } from "@fast-crud/fast-crud";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
product: any;
|
product: any;
|
||||||
}>();
|
}>();
|
||||||
const selected = ref(props.product.durationPrices[0]);
|
const selected = ref(props.product.durationPrices[0]);
|
||||||
|
|
||||||
|
const productTypeDictRef = dict({
|
||||||
|
data: [
|
||||||
|
{ value: "suite", label: "套餐", color: "green" },
|
||||||
|
{ value: "addon", label: "加量包", color: "blue" }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
const emit = defineEmits(["order"]);
|
const emit = defineEmits(["order"]);
|
||||||
async function doOrder() {
|
async function doOrder() {
|
||||||
emit("order", { product: props.product, productId: props.product.id, duration: selected.value.duration });
|
emit("order", { product: props.product, productId: props.product.id, duration: selected.value.duration });
|
||||||
|
@ -72,8 +80,30 @@ async function doOrder() {
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
.product-card {
|
.product-card {
|
||||||
|
.ant-card-body {
|
||||||
|
padding: 20px;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
|
||||||
|
.product-intro {
|
||||||
|
font-size: 13px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
line-height: 28px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-divider {
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.duration-label {
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
.duration-list {
|
.duration-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
.duration-item {
|
.duration-item {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
border: 1px solid #cdcdcd;
|
border: 1px solid #cdcdcd;
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { request } from "/src/api/service";
|
||||||
|
|
||||||
|
const apiPrefix = "/suite/trade";
|
||||||
|
|
||||||
|
export async function GetList(query: any) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/page",
|
||||||
|
method: "post",
|
||||||
|
data: query
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function AddObj(obj: any) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/add",
|
||||||
|
method: "post",
|
||||||
|
data: obj
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function UpdateObj(obj: any) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/update",
|
||||||
|
method: "post",
|
||||||
|
data: obj
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function DelObj(id: any) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/delete",
|
||||||
|
method: "post",
|
||||||
|
params: { id }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GetObj(id: any) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/info",
|
||||||
|
method: "post",
|
||||||
|
params: { id }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GetDetail(id: any) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/detail",
|
||||||
|
method: "post",
|
||||||
|
params: { id }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function DeleteBatch(ids: any[]) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/deleteByIds",
|
||||||
|
method: "post",
|
||||||
|
data: { ids }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function SetDefault(id: any) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/setDefault",
|
||||||
|
method: "post",
|
||||||
|
data: { id }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function SetDisabled(id: any, disabled: boolean) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/setDisabled",
|
||||||
|
method: "post",
|
||||||
|
data: { id, disabled }
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,194 @@
|
||||||
|
import * as api from "./api";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { computed, Ref, ref } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes, utils } from "@fast-crud/fast-crud";
|
||||||
|
import { useUserStore } from "/@/store/modules/user";
|
||||||
|
import { useSettingStore } from "/@/store/modules/settings";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import PriceInput from "/@/views/sys/suite/product/price-input.vue";
|
||||||
|
import SuiteValue from "/@/views/sys/suite/product/suite-value.vue";
|
||||||
|
import DurationValue from "/@/views/sys/suite/product/duration-value.vue";
|
||||||
|
|
||||||
|
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
|
const router = useRouter();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||||
|
return await api.GetList(query);
|
||||||
|
};
|
||||||
|
const editRequest = async ({ form, row }: EditReq) => {
|
||||||
|
form.id = row.id;
|
||||||
|
const res = await api.UpdateObj(form);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
const delRequest = async ({ row }: DelReq) => {
|
||||||
|
return await api.DelObj(row.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addRequest = async ({ form }: AddReq) => {
|
||||||
|
const res = await api.AddObj(form);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const settingStore = useSettingStore();
|
||||||
|
const selectedRowKeys: Ref<any[]> = ref([]);
|
||||||
|
context.selectedRowKeys = selectedRowKeys;
|
||||||
|
|
||||||
|
return {
|
||||||
|
crudOptions: {
|
||||||
|
settings: {
|
||||||
|
plugins: {
|
||||||
|
//这里使用行选择插件,生成行选择crudOptions配置,最终会与crudOptions合并
|
||||||
|
rowSelection: {
|
||||||
|
enabled: true,
|
||||||
|
order: -2,
|
||||||
|
before: true,
|
||||||
|
// handle: (pluginProps,useCrudProps)=>CrudOptions,
|
||||||
|
props: {
|
||||||
|
multiple: true,
|
||||||
|
crossPage: true,
|
||||||
|
selectedRowKeys
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
request: {
|
||||||
|
pageRequest,
|
||||||
|
addRequest,
|
||||||
|
editRequest,
|
||||||
|
delRequest
|
||||||
|
},
|
||||||
|
rowHandle: {
|
||||||
|
width: 100,
|
||||||
|
fixed: "right",
|
||||||
|
buttons: {
|
||||||
|
edit: { show: false },
|
||||||
|
copy: { show: false }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tabs: {
|
||||||
|
name: "status",
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
id: {
|
||||||
|
title: "ID",
|
||||||
|
key: "id",
|
||||||
|
type: "number",
|
||||||
|
column: {
|
||||||
|
width: 100
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tradeNo: {
|
||||||
|
title: "订单号",
|
||||||
|
type: "text",
|
||||||
|
search: { show: true },
|
||||||
|
form: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 250
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
title: "商品名称",
|
||||||
|
type: "text",
|
||||||
|
search: { show: true },
|
||||||
|
column: {
|
||||||
|
width: 150
|
||||||
|
}
|
||||||
|
},
|
||||||
|
duration: {
|
||||||
|
title: "时长",
|
||||||
|
type: "number",
|
||||||
|
column: {
|
||||||
|
width: 100,
|
||||||
|
component: {
|
||||||
|
name: DurationValue,
|
||||||
|
vModel: "modelValue"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
title: "金额",
|
||||||
|
type: "number",
|
||||||
|
column: {
|
||||||
|
width: 100,
|
||||||
|
component: {
|
||||||
|
name: PriceInput,
|
||||||
|
vModel: "modelValue",
|
||||||
|
edit: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
title: "状态",
|
||||||
|
search: { show: true },
|
||||||
|
type: "dict-select",
|
||||||
|
dict: dict({
|
||||||
|
data: [
|
||||||
|
{ label: "待支付", value: "wait_pay", color: "warning" },
|
||||||
|
{ label: "已支付", value: "paid", color: "success" },
|
||||||
|
{ label: "已取消", value: "canceled", color: "error" }
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
column: {
|
||||||
|
width: 100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
payType: {
|
||||||
|
title: "支付方式",
|
||||||
|
search: { show: true },
|
||||||
|
type: "dict-select",
|
||||||
|
dict: dict({
|
||||||
|
data: [
|
||||||
|
{ label: "聚合支付", value: "yizhifu" },
|
||||||
|
{ label: "支付宝", value: "alipay" },
|
||||||
|
{ label: "微信", value: "wxpay" }
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
column: {
|
||||||
|
width: 100,
|
||||||
|
component: {
|
||||||
|
color: "auto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
payTime: {
|
||||||
|
title: "支付时间",
|
||||||
|
type: "datetime",
|
||||||
|
column: {
|
||||||
|
width: 160
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createTime: {
|
||||||
|
title: "创建时间",
|
||||||
|
type: "datetime",
|
||||||
|
form: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
sorter: true,
|
||||||
|
width: 160,
|
||||||
|
align: "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateTime: {
|
||||||
|
title: "更新时间",
|
||||||
|
type: "datetime",
|
||||||
|
form: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
show: true,
|
||||||
|
width: 160
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
<template>
|
||||||
|
<fs-page class="page-cert">
|
||||||
|
<template #header>
|
||||||
|
<div class="title">我的订单</div>
|
||||||
|
</template>
|
||||||
|
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||||
|
<template #pagination-left>
|
||||||
|
<a-tooltip title="批量删除">
|
||||||
|
<fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
</fs-crud>
|
||||||
|
</fs-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted } from "vue";
|
||||||
|
import { useFs } from "@fast-crud/fast-crud";
|
||||||
|
import createCrudOptions from "./crud";
|
||||||
|
import { message, Modal } from "ant-design-vue";
|
||||||
|
import { DeleteBatch } from "./api";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "MyTrade"
|
||||||
|
});
|
||||||
|
const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions });
|
||||||
|
|
||||||
|
const selectedRowKeys = context.selectedRowKeys;
|
||||||
|
const handleBatchDelete = () => {
|
||||||
|
if (selectedRowKeys.value?.length > 0) {
|
||||||
|
Modal.confirm({
|
||||||
|
title: "确认",
|
||||||
|
content: `确定要批量删除这${selectedRowKeys.value.length}条记录吗`,
|
||||||
|
async onOk() {
|
||||||
|
await DeleteBatch(selectedRowKeys.value);
|
||||||
|
message.info("删除成功");
|
||||||
|
crudExpose.doRefresh();
|
||||||
|
selectedRowKeys.value = [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
message.error("请先勾选记录");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面打开后获取列表数据
|
||||||
|
onMounted(() => {
|
||||||
|
crudExpose.doRefresh();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="less"></style>
|
|
@ -12,14 +12,19 @@
|
||||||
<div>
|
<div>
|
||||||
<span>您好,{{ userInfo.nickName || userInfo.username }}, 欢迎使用 【{{ siteInfo.title }}】</span>
|
<span>您好,{{ userInfo.nickName || userInfo.username }}, 欢迎使用 【{{ siteInfo.title }}】</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="flex-o">
|
||||||
<a-tag color="green" class="flex-inline pointer"> <fs-icon icon="ion:time-outline" class="mr-5"></fs-icon> {{ now }}</a-tag>
|
<a-tag color="green" class="flex-inline pointer m-0"> <fs-icon icon="ion:time-outline"></fs-icon> {{ now }}</a-tag>
|
||||||
<a-badge v-if="userStore.isAdmin" :dot="hasNewVersion">
|
<template v-if="userStore.isAdmin">
|
||||||
<a-tag color="blue" class="flex-inline pointer" :title="'最新版本:' + latestVersion" @click="openUpgradeUrl()">
|
<a-divider type="vertical" />
|
||||||
<fs-icon icon="ion:rocket-outline" class="mr-5"></fs-icon>
|
<a-badge :dot="hasNewVersion">
|
||||||
v{{ version }}
|
<a-tag color="blue" class="flex-inline pointer m-0" :title="'最新版本:' + latestVersion" @click="openUpgradeUrl()">
|
||||||
</a-tag>
|
<fs-icon icon="ion:rocket-outline" class="mr-5"></fs-icon>
|
||||||
</a-badge>
|
v{{ version }}
|
||||||
|
</a-tag>
|
||||||
|
</a-badge>
|
||||||
|
</template>
|
||||||
|
<a-divider type="vertical" />
|
||||||
|
<suite-card class="m-0"></suite-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -82,7 +87,7 @@
|
||||||
<div v-if="pluginGroups" class="plugin-list">
|
<div v-if="pluginGroups" class="plugin-list">
|
||||||
<a-card>
|
<a-card>
|
||||||
<template #title>
|
<template #title>
|
||||||
支持的部署任务列表 <a-tag color="green">{{ pluginGroups.groups.all.plugins.length }}</a-tag>
|
已支持的部署任务总览 <a-tag color="green">{{ pluginGroups.groups.all.plugins.length }}</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<a-row :gutter="10">
|
<a-row :gutter="10">
|
||||||
<a-col v-for="item of pluginGroups.groups.all.plugins" :key="item.name" class="plugin-item-col" :span="4">
|
<a-col v-for="item of pluginGroups.groups.all.plugins" :key="item.name" class="plugin-item-col" :span="4">
|
||||||
|
@ -118,7 +123,7 @@ import TutorialButton from "/@/components/tutorial/index.vue";
|
||||||
import DayCount from "./charts/day-count.vue";
|
import DayCount from "./charts/day-count.vue";
|
||||||
import PieCount from "./charts/pie-count.vue";
|
import PieCount from "./charts/pie-count.vue";
|
||||||
import ExpiringList from "./charts/expiring-list.vue";
|
import ExpiringList from "./charts/expiring-list.vue";
|
||||||
|
import SuiteCard from "./suite-card.vue";
|
||||||
import { useSettingStore } from "/@/store/modules/settings";
|
import { useSettingStore } from "/@/store/modules/settings";
|
||||||
import { SiteInfo } from "/@/api/modules/api.basic";
|
import { SiteInfo } from "/@/api/modules/api.basic";
|
||||||
import { UserInfoRes } from "/@/api/modules/api.user";
|
import { UserInfoRes } from "/@/api/modules/api.user";
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
<template>
|
||||||
|
<div class="my-suite-card">
|
||||||
|
<div class="flex-o">
|
||||||
|
<a-popover>
|
||||||
|
<template #content>
|
||||||
|
<div>
|
||||||
|
<div class="flex-between mt-5">
|
||||||
|
<div class="flex-o"><fs-icon icon="ant-design:check-outlined" class="color-green mr-5" /> 流水线条数:</div>
|
||||||
|
<suite-value :model-value="detail.pipelineCount.max" :used="detail.pipelineCount.used" unit="条" />
|
||||||
|
</div>
|
||||||
|
<div class="flex-between mt-5">
|
||||||
|
<div class="flex-o"><fs-icon icon="ant-design:check-outlined" class="color-green mr-5" />域名数量:</div>
|
||||||
|
<suite-value :model-value="detail.domainCount.max" :used="detail.domainCount.used" unit="个" />
|
||||||
|
</div>
|
||||||
|
<div class="flex-between mt-5">
|
||||||
|
<div class="flex-o"><fs-icon icon="ant-design:check-outlined" class="color-green mr-5" /> 部署次数:</div>
|
||||||
|
<suite-value :model-value="detail.deployCount.max" :used="detail.deployCount.used" unit="次" />
|
||||||
|
</div>
|
||||||
|
<div class="flex-between mt-5">
|
||||||
|
<div class="flex-o"><fs-icon icon="ant-design:check-outlined" class="color-green mr-5" /> 监控站点数:</div>
|
||||||
|
<suite-value :model-value="detail.monitorCount.max" :used="detail.monitorCount.used" unit="次" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="flex-o">
|
||||||
|
<fs-icon icon="ant-design:gift-outlined" class="color-green mr-5" />
|
||||||
|
<a-tag v-for="(item, index) of detail.suites" :key="index" color="green" class="pointer flex-o">
|
||||||
|
<span class="mr-5">
|
||||||
|
{{ item.title }}
|
||||||
|
</span>
|
||||||
|
<span>(<expires-time-text :value="item.expiresTime" />)</span>
|
||||||
|
</a-tag>
|
||||||
|
</div>
|
||||||
|
</a-popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import SuiteValue from "/@/views/sys/suite/product/suite-value.vue";
|
||||||
|
import { computed, ref } from "vue";
|
||||||
|
import { request } from "/@/api/service";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import ExpiresTimeText from "/@/components/expires-time-text.vue";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "SuiteCard"
|
||||||
|
});
|
||||||
|
|
||||||
|
type SuiteValue = {
|
||||||
|
max: number;
|
||||||
|
used: number;
|
||||||
|
};
|
||||||
|
type SuiteDetail = {
|
||||||
|
suites?: any[];
|
||||||
|
expiresTime?: number;
|
||||||
|
pipelineCount?: SuiteValue;
|
||||||
|
domainCount?: SuiteValue;
|
||||||
|
deployCount?: SuiteValue;
|
||||||
|
monitorCount?: SuiteValue;
|
||||||
|
};
|
||||||
|
const detail = ref<SuiteDetail>({});
|
||||||
|
|
||||||
|
const api = {
|
||||||
|
async SuiteDetailGet() {
|
||||||
|
return await request({
|
||||||
|
url: "/mine/suite/detail",
|
||||||
|
method: "post"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
async function loadSuiteDetail() {
|
||||||
|
detail.value = await api.SuiteDetailGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSuiteDetail();
|
||||||
|
</script>
|
|
@ -11,6 +11,9 @@
|
||||||
<a-tab-pane key="register" tab="注册设置">
|
<a-tab-pane key="register" tab="注册设置">
|
||||||
<SettingRegister v-if="activeKey === 'register'" />
|
<SettingRegister v-if="activeKey === 'register'" />
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="payment" tab="支付设置">
|
||||||
|
<SettingPayment v-if="activeKey === 'payment'" />
|
||||||
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</div>
|
</div>
|
||||||
</fs-page>
|
</fs-page>
|
||||||
|
@ -19,6 +22,7 @@
|
||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import SettingBase from "/@/views/sys/settings/tabs/base.vue";
|
import SettingBase from "/@/views/sys/settings/tabs/base.vue";
|
||||||
import SettingRegister from "/@/views/sys/settings/tabs/register.vue";
|
import SettingRegister from "/@/views/sys/settings/tabs/register.vue";
|
||||||
|
import SettingPayment from "/@/views/sys/settings/tabs/payment.vue";
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
defineOptions({
|
defineOptions({
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
<template>
|
||||||
|
<div class="sys-settings-form sys-settings-payment">
|
||||||
|
<a-form ref="formRef" :model="formState" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off">
|
||||||
|
<div>支付方式</div>
|
||||||
|
<a-form-item label="易支付" :name="['yizhifu', 'enabled']" :required="true">
|
||||||
|
<a-switch v-model:checked="formState.yizhifu.enabled" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="formState.yizhifu.enabled" label="易支付配置" :name="['yizhifu', 'accessId']" :required="true">
|
||||||
|
<access-selector v-model="formState.yizhifu.accessId" type="yizhifu" from="sys" />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="支付宝" :name="['alipay', 'enabled']" :required="true">
|
||||||
|
<a-switch v-model:checked="formState.alipay.enabled" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="formState.alipay.enabled" label="支付宝配置" :name="['alipay', 'accessId']" :required="true">
|
||||||
|
<access-selector v-model="formState.alipay.accessId" type="alipay" from="sys" />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="微信支付" :name="['wxpay', 'enabled']" :required="true">
|
||||||
|
<a-switch v-model:checked="formState.wxpay.enabled" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="formState.wxpay.enabled" label="微信支付配置" :name="['wxpay', 'accessId']" :required="true">
|
||||||
|
<access-selector v-model="formState.wxpay.accessId" type="wxpay" from="sys" />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
|
||||||
|
<loading-button type="primary" html-type="button" :click="onClick">保存</loading-button>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="tsx">
|
||||||
|
import { reactive, ref } from "vue";
|
||||||
|
import { merge } from "lodash-es";
|
||||||
|
import { notification } from "ant-design-vue";
|
||||||
|
import { request } from "/@/api/service";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "SettingPayment"
|
||||||
|
});
|
||||||
|
|
||||||
|
const api = {
|
||||||
|
async SettingGet() {
|
||||||
|
return await request({
|
||||||
|
url: "/sys/settings/payment/get",
|
||||||
|
method: "post"
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async SettingSave(data: any) {
|
||||||
|
return await request({
|
||||||
|
url: "/sys/settings/payment/save",
|
||||||
|
method: "post",
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formRef = ref<any>(null);
|
||||||
|
type PaymentItem = {
|
||||||
|
enabled: boolean;
|
||||||
|
accessId?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formState = reactive<
|
||||||
|
Partial<{
|
||||||
|
yizhifu: PaymentItem;
|
||||||
|
alipay: PaymentItem;
|
||||||
|
wxpay: PaymentItem;
|
||||||
|
}>
|
||||||
|
>({});
|
||||||
|
|
||||||
|
async function loadSettings() {
|
||||||
|
const data: any = await api.SettingGet();
|
||||||
|
merge(formState, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSettings();
|
||||||
|
const onClick = async () => {
|
||||||
|
const form = await formRef.value.validateFields();
|
||||||
|
await api.SettingSave(form);
|
||||||
|
await loadSettings();
|
||||||
|
notification.success({
|
||||||
|
message: "保存成功"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="less">
|
||||||
|
.sys-settings-base {
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,194 +0,0 @@
|
||||||
import { ColumnCompositionProps, compute, dict } from "@fast-crud/fast-crud";
|
|
||||||
import { computed, provide, ref, toRef } from "vue";
|
|
||||||
import { useReference } from "/@/use/use-refrence";
|
|
||||||
import { forEach, get, merge, set } from "lodash-es";
|
|
||||||
import { Modal } from "ant-design-vue";
|
|
||||||
import * as api from "/@/views/sys/cname/provider/api";
|
|
||||||
import { mitter } from "/@/utils/util.mitt";
|
|
||||||
|
|
||||||
export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
|
|
||||||
const notificationTypeDictRef = dict({
|
|
||||||
url: "/sys/suite/payment/getList"
|
|
||||||
});
|
|
||||||
const defaultPluginConfig = {
|
|
||||||
component: {
|
|
||||||
name: "a-input",
|
|
||||||
vModel: "value"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function buildDefineFields(define: any, form: any, mode: string) {
|
|
||||||
const formWrapperRef = crudExpose.getFormWrapperRef();
|
|
||||||
const columnsRef = toRef(formWrapperRef.formOptions, "columns");
|
|
||||||
|
|
||||||
for (const key in columnsRef.value) {
|
|
||||||
if (key.indexOf(".") >= 0) {
|
|
||||||
delete columnsRef.value[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log('crudBinding.value[mode + "Form"].columns', columnsRef.value);
|
|
||||||
forEach(define.input, (value: any, mapKey: any) => {
|
|
||||||
const key = "body." + mapKey;
|
|
||||||
const field = {
|
|
||||||
...value,
|
|
||||||
key
|
|
||||||
};
|
|
||||||
const column = merge({ title: key }, defaultPluginConfig, field);
|
|
||||||
//eval
|
|
||||||
useReference(column);
|
|
||||||
|
|
||||||
if (column.required) {
|
|
||||||
if (!column.rules) {
|
|
||||||
column.rules = [];
|
|
||||||
}
|
|
||||||
column.rules.push({ required: true, message: "此项必填" });
|
|
||||||
}
|
|
||||||
|
|
||||||
//设置默认值
|
|
||||||
if (column.value != null && get(form, key) == null) {
|
|
||||||
set(form, key, column.value);
|
|
||||||
}
|
|
||||||
//字段配置赋值
|
|
||||||
columnsRef.value[key] = column;
|
|
||||||
console.log("form", columnsRef.value, form);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentDefine = ref();
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: {
|
|
||||||
title: "ID",
|
|
||||||
key: "id",
|
|
||||||
type: "number",
|
|
||||||
column: {
|
|
||||||
width: 100
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
show: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
title: "支付类型",
|
|
||||||
type: "dict-select",
|
|
||||||
dict: notificationTypeDictRef,
|
|
||||||
search: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
column: {
|
|
||||||
width: 200,
|
|
||||||
component: {
|
|
||||||
color: "auto"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
editForm: {
|
|
||||||
component: {
|
|
||||||
disabled: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
component: {
|
|
||||||
disabled: false,
|
|
||||||
showSearch: true,
|
|
||||||
filterOption: (input: string, option: any) => {
|
|
||||||
input = input?.toLowerCase();
|
|
||||||
return option.value.toLowerCase().indexOf(input) >= 0 || option.label.toLowerCase().indexOf(input) >= 0;
|
|
||||||
},
|
|
||||||
renderLabel(item: any) {
|
|
||||||
return (
|
|
||||||
<span class={"flex-o flex-between"}>
|
|
||||||
{item.label}
|
|
||||||
{item.needPlus && <fs-icon icon={"mingcute:vip-1-line"} className={"color-plus"}></fs-icon>}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rules: [{ required: true, message: "请选择通知类型" }],
|
|
||||||
valueChange: {
|
|
||||||
immediate: true,
|
|
||||||
async handle({ value, mode, form, immediate }) {
|
|
||||||
if (value == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const lastTitle = currentDefine.value?.title;
|
|
||||||
const define = await api.GetProviderDefine(value);
|
|
||||||
currentDefine.value = define;
|
|
||||||
console.log("define", define);
|
|
||||||
|
|
||||||
if (!immediate) {
|
|
||||||
form.body = {};
|
|
||||||
if (define.needPlus) {
|
|
||||||
mitter.emit("openVipModal");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!form.name || form.name === lastTitle) {
|
|
||||||
form.name = define.title;
|
|
||||||
}
|
|
||||||
buildDefineFields(define, form, mode);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
helper: computed(() => {
|
|
||||||
const define = currentDefine.value;
|
|
||||||
if (define == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return define.desc;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} as ColumnCompositionProps,
|
|
||||||
name: {
|
|
||||||
title: "通知名称",
|
|
||||||
search: {
|
|
||||||
show: true
|
|
||||||
},
|
|
||||||
type: ["text"],
|
|
||||||
form: {
|
|
||||||
rules: [{ required: true, message: "请填写名称" }],
|
|
||||||
helper: "随便填,当多个相同类型的通知时,便于区分"
|
|
||||||
},
|
|
||||||
column: {
|
|
||||||
width: 200
|
|
||||||
}
|
|
||||||
},
|
|
||||||
test: {
|
|
||||||
title: "测试",
|
|
||||||
form: {
|
|
||||||
show: compute(({ form }) => {
|
|
||||||
return !!form.type;
|
|
||||||
}),
|
|
||||||
component: {
|
|
||||||
name: "api-test",
|
|
||||||
action: "TestRequest"
|
|
||||||
},
|
|
||||||
order: 990,
|
|
||||||
col: {
|
|
||||||
span: 24
|
|
||||||
}
|
|
||||||
},
|
|
||||||
column: {
|
|
||||||
show: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setting: {
|
|
||||||
column: { show: false },
|
|
||||||
form: {
|
|
||||||
show: false,
|
|
||||||
valueBuilder({ value, form }) {
|
|
||||||
form.body = {};
|
|
||||||
if (!value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const setting = JSON.parse(value);
|
|
||||||
for (const key in setting) {
|
|
||||||
form.body[key] = setting[key];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
valueResolve({ form }) {
|
|
||||||
const setting = form.body;
|
|
||||||
form.setting = JSON.stringify(setting);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} as ColumnCompositionProps
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
import { ref } from "vue";
|
|
||||||
import { getCommonColumnDefine } from "./common";
|
|
||||||
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
|
||||||
import { createNotificationApi } from "/@/views/certd/notification/api";
|
|
||||||
const api = createNotificationApi();
|
|
||||||
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
|
||||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
|
||||||
return await api.GetList(query);
|
|
||||||
};
|
|
||||||
const editRequest = async (req: EditReq) => {
|
|
||||||
const { form, row } = req;
|
|
||||||
form.id = row.id;
|
|
||||||
const res = await api.UpdateObj(form);
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
const delRequest = async (req: DelReq) => {
|
|
||||||
const { row } = req;
|
|
||||||
return await api.DelObj(row.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const addRequest = async (req: AddReq) => {
|
|
||||||
const { form } = req;
|
|
||||||
const res = await api.AddObj(form);
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
const typeRef = ref();
|
|
||||||
const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api);
|
|
||||||
return {
|
|
||||||
crudOptions: {
|
|
||||||
request: {
|
|
||||||
pageRequest,
|
|
||||||
addRequest,
|
|
||||||
editRequest,
|
|
||||||
delRequest
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
labelCol: {
|
|
||||||
//固定label宽度
|
|
||||||
span: null,
|
|
||||||
style: {
|
|
||||||
width: "145px"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rowHandle: {
|
|
||||||
width: 200
|
|
||||||
},
|
|
||||||
columns: {
|
|
||||||
...commonColumnsDefine
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
<template>
|
|
||||||
<fs-page>
|
|
||||||
<template #header>
|
|
||||||
<div class="title">
|
|
||||||
支付方式管理
|
|
||||||
<span class="sub">管理支付方式</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
|
||||||
</fs-page>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, onActivated, onMounted } from "vue";
|
|
||||||
import { useFs } from "@fast-crud/fast-crud";
|
|
||||||
import createCrudOptions from "./crud";
|
|
||||||
import { createPaymentApi } from "./api";
|
|
||||||
import { notificationProvide } from "/@/views/certd/notification/common";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "PaymentManager",
|
|
||||||
setup() {
|
|
||||||
const api = createPaymentApi();
|
|
||||||
notificationProvide(api);
|
|
||||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { api } });
|
|
||||||
|
|
||||||
// 页面打开后获取列表数据
|
|
||||||
onMounted(() => {
|
|
||||||
crudExpose.doRefresh();
|
|
||||||
});
|
|
||||||
onActivated(() => {
|
|
||||||
crudExpose.doRefresh();
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
crudBinding,
|
|
||||||
crudRef
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -1,163 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="notification-selector">
|
|
||||||
<div class="flex-o w-100">
|
|
||||||
<fs-dict-select
|
|
||||||
class="flex-1"
|
|
||||||
:value="modelValue"
|
|
||||||
:dict="optionsDictRef"
|
|
||||||
:disabled="disabled"
|
|
||||||
:render-label="renderLabel"
|
|
||||||
:slots="selectSlots"
|
|
||||||
:allow-clear="true"
|
|
||||||
@update:value="onChange"
|
|
||||||
/>
|
|
||||||
<fs-table-select
|
|
||||||
ref="tableSelectRef"
|
|
||||||
class="flex-0"
|
|
||||||
:model-value="modelValue"
|
|
||||||
:dict="optionsDictRef"
|
|
||||||
:create-crud-options="createCrudOptions"
|
|
||||||
:crud-options-override="{
|
|
||||||
search: { show: false },
|
|
||||||
table: {
|
|
||||||
scroll: {
|
|
||||||
x: 540
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
:show-current="false"
|
|
||||||
:show-select="false"
|
|
||||||
:dialog="{ width: 960 }"
|
|
||||||
:destroy-on-close="false"
|
|
||||||
height="400px"
|
|
||||||
@update:model-value="onChange"
|
|
||||||
@dialog-closed="doRefresh"
|
|
||||||
>
|
|
||||||
<template #default="scope">
|
|
||||||
<fs-button class="ml-5" :disabled="disabled" :size="size" type="primary" icon="ant-design:edit-outlined" @click="scope.open"></fs-button>
|
|
||||||
</template>
|
|
||||||
</fs-table-select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="tsx" setup>
|
|
||||||
import { inject, ref, Ref, watch } from "vue";
|
|
||||||
import { createNotificationApi } from "../api";
|
|
||||||
import { message } from "ant-design-vue";
|
|
||||||
import { dict } from "@fast-crud/fast-crud";
|
|
||||||
import createCrudOptions from "../crud";
|
|
||||||
import { notificationProvide } from "/@/views/certd/notification/common";
|
|
||||||
|
|
||||||
defineOptions({
|
|
||||||
name: "NotificationSelector"
|
|
||||||
});
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
modelValue?: number | string;
|
|
||||||
type?: string;
|
|
||||||
placeholder?: string;
|
|
||||||
size?: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const onChange = async (value: number) => {
|
|
||||||
await emitValue(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const emit = defineEmits(["update:modelValue", "selectedChange", "change"]);
|
|
||||||
|
|
||||||
const api = createNotificationApi();
|
|
||||||
notificationProvide(api);
|
|
||||||
// const types = ref({});
|
|
||||||
// async function loadNotificationTypes() {
|
|
||||||
// const types = await api.GetDefineTypes();
|
|
||||||
// const map: any = {};
|
|
||||||
// for (const item of types) {
|
|
||||||
// map[item.type] = item;
|
|
||||||
// }
|
|
||||||
// types.value = map;
|
|
||||||
// }
|
|
||||||
// loadNotificationTypes();
|
|
||||||
const tableSelectRef = ref();
|
|
||||||
const optionsDictRef = dict({
|
|
||||||
url: "/pi/notification/options",
|
|
||||||
value: "id",
|
|
||||||
label: "name",
|
|
||||||
onReady: ({ dict }) => {
|
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
id: 0,
|
|
||||||
name: "使用默认通知",
|
|
||||||
icon: "ion:notifications"
|
|
||||||
},
|
|
||||||
...dict.data
|
|
||||||
];
|
|
||||||
dict.setData(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const renderLabel = (option: any) => {
|
|
||||||
return <span>{option.name}</span>;
|
|
||||||
};
|
|
||||||
|
|
||||||
async function openTableSelectDialog(e: any) {
|
|
||||||
e.preventDefault();
|
|
||||||
await tableSelectRef.value.open();
|
|
||||||
await tableSelectRef.value.crudExpose.openAdd({});
|
|
||||||
}
|
|
||||||
const selectSlots = ref({
|
|
||||||
dropdownRender({ menuNode }: any) {
|
|
||||||
const res = [];
|
|
||||||
res.push(menuNode);
|
|
||||||
res.push(<a-divider style="margin: 4px 0" />);
|
|
||||||
res.push(<a-space style="padding: 4px 8px" />);
|
|
||||||
res.push(<fs-button class="w-100" type="text" icon="plus-outlined" text="新建通知渠道" onClick={openTableSelectDialog}></fs-button>);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const target: Ref<any> = ref({});
|
|
||||||
|
|
||||||
function clear() {
|
|
||||||
if (props.disabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
emitValue(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function emitValue(value: any) {
|
|
||||||
target.value = optionsDictRef.dataMap[value];
|
|
||||||
if (value !== 0 && pipeline?.value && target && pipeline.value.userId !== target.value.userId) {
|
|
||||||
message.error("对不起,您不能修改他人流水线的通知");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
emit("change", value);
|
|
||||||
emit("update:modelValue", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => {
|
|
||||||
return props.modelValue;
|
|
||||||
},
|
|
||||||
async (value) => {
|
|
||||||
await optionsDictRef.loadDict();
|
|
||||||
target.value = optionsDictRef.dataMap[value];
|
|
||||||
emit("selectedChange", target.value);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
immediate: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
//当不在pipeline中编辑时,可能为空
|
|
||||||
const pipeline = inject("pipeline", null);
|
|
||||||
|
|
||||||
async function doRefresh() {
|
|
||||||
await optionsDictRef.reloadDict();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang="less">
|
|
||||||
.notification-selector {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,17 +1,12 @@
|
||||||
import * as api from "./api";
|
import * as api from "./api";
|
||||||
import { useI18n } from "vue-i18n";
|
|
||||||
import { Ref, ref } from "vue";
|
|
||||||
import { useRouter } from "vue-router";
|
|
||||||
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||||
import { useUserStore } from "/@/store/modules/user";
|
|
||||||
import { useSettingStore } from "/@/store/modules/settings";
|
|
||||||
import SuiteValue from "./suite-value.vue";
|
import SuiteValue from "./suite-value.vue";
|
||||||
import SuiteValueEdit from "./suite-value-edit.vue";
|
import SuiteValueEdit from "./suite-value-edit.vue";
|
||||||
import PriceEdit from "./price-edit.vue";
|
import PriceEdit from "./price-edit.vue";
|
||||||
|
import DurationPriceValue from "/@/views/sys/suite/product/duration-price-value.vue";
|
||||||
|
|
||||||
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
const router = useRouter();
|
const emit = context.emit;
|
||||||
const { t } = useI18n();
|
|
||||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||||
return await api.GetList(query);
|
return await api.GetList(query);
|
||||||
};
|
};
|
||||||
|
@ -29,35 +24,26 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
const userStore = useUserStore();
|
|
||||||
const settingStore = useSettingStore();
|
|
||||||
const selectedRowKeys: Ref<any[]> = ref([]);
|
|
||||||
context.selectedRowKeys = selectedRowKeys;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
crudOptions: {
|
crudOptions: {
|
||||||
settings: {
|
table: {
|
||||||
plugins: {
|
onRefreshed: () => {
|
||||||
//这里使用行选择插件,生成行选择crudOptions配置,最终会与crudOptions合并
|
emit("refreshed");
|
||||||
rowSelection: {
|
|
||||||
enabled: true,
|
|
||||||
order: -2,
|
|
||||||
before: true,
|
|
||||||
// handle: (pluginProps,useCrudProps)=>CrudOptions,
|
|
||||||
props: {
|
|
||||||
multiple: true,
|
|
||||||
crossPage: true,
|
|
||||||
selectedRowKeys
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
search: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
request: {
|
request: {
|
||||||
pageRequest,
|
pageRequest,
|
||||||
addRequest,
|
addRequest,
|
||||||
editRequest,
|
editRequest,
|
||||||
delRequest
|
delRequest
|
||||||
},
|
},
|
||||||
|
pagination: {
|
||||||
|
show: false,
|
||||||
|
pageSize: 999999
|
||||||
|
},
|
||||||
rowHandle: {
|
rowHandle: {
|
||||||
minWidth: 200,
|
minWidth: 200,
|
||||||
fixed: "right"
|
fixed: "right"
|
||||||
|
@ -71,7 +57,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
header: "套餐内容",
|
header: "套餐内容",
|
||||||
columns: ["content.maxDomainCount", "content.maxPipelineCount", "content.maxDeployCount", "content.siteMonitor"]
|
columns: ["content.maxDomainCount", "content.maxPipelineCount", "content.maxDeployCount", "content.maxMonitorCount"]
|
||||||
},
|
},
|
||||||
price: {
|
price: {
|
||||||
header: "价格",
|
header: "价格",
|
||||||
|
@ -81,17 +67,17 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
columns: {
|
columns: {
|
||||||
id: {
|
// id: {
|
||||||
title: "ID",
|
// title: "ID",
|
||||||
key: "id",
|
// key: "id",
|
||||||
type: "number",
|
// type: "number",
|
||||||
column: {
|
// column: {
|
||||||
width: 100
|
// width: 100
|
||||||
},
|
// },
|
||||||
form: {
|
// form: {
|
||||||
show: false
|
// show: false
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
title: {
|
title: {
|
||||||
title: "套餐名称",
|
title: "套餐名称",
|
||||||
type: "text",
|
type: "text",
|
||||||
|
@ -123,7 +109,8 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
rules: [{ required: true, message: "此项必填" }]
|
rules: [{ required: true, message: "此项必填" }]
|
||||||
},
|
},
|
||||||
column: {
|
column: {
|
||||||
width: 100
|
width: 80,
|
||||||
|
align: "center"
|
||||||
},
|
},
|
||||||
valueBuilder: ({ row }) => {
|
valueBuilder: ({ row }) => {
|
||||||
if (row.content) {
|
if (row.content) {
|
||||||
|
@ -205,37 +192,25 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"content.siteMonitor": {
|
"content.maxMonitorCount": {
|
||||||
title: "支持证书监控",
|
title: "证书监控数量",
|
||||||
type: "dict-switch",
|
type: "text",
|
||||||
dict: dict({
|
|
||||||
data: [
|
|
||||||
{ label: "是", value: true, color: "success" },
|
|
||||||
{ label: "否", value: false, color: "error" }
|
|
||||||
]
|
|
||||||
}),
|
|
||||||
form: {
|
form: {
|
||||||
key: ["content", "siteMonitor"],
|
key: ["content", "maxMonitorCount"],
|
||||||
value: false
|
component: {
|
||||||
|
name: SuiteValueEdit,
|
||||||
|
vModel: "modelValue",
|
||||||
|
unit: "个"
|
||||||
|
},
|
||||||
|
rules: [{ required: true, message: "此项必填" }]
|
||||||
},
|
},
|
||||||
column: {
|
column: {
|
||||||
width: 120
|
width: 120,
|
||||||
}
|
component: {
|
||||||
},
|
name: SuiteValue,
|
||||||
isBootstrap: {
|
vModel: "modelValue",
|
||||||
title: "是否初始套餐",
|
unit: "个"
|
||||||
type: "dict-switch",
|
}
|
||||||
dict: dict({
|
|
||||||
data: [
|
|
||||||
{ label: "是", value: true, color: "success" },
|
|
||||||
{ label: "否", value: false, color: "gray" }
|
|
||||||
]
|
|
||||||
}),
|
|
||||||
form: {
|
|
||||||
value: false
|
|
||||||
},
|
|
||||||
column: {
|
|
||||||
width: 120
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
durationPrices: {
|
durationPrices: {
|
||||||
|
@ -258,10 +233,26 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
},
|
},
|
||||||
column: {
|
column: {
|
||||||
component: {
|
component: {
|
||||||
name: PriceEdit,
|
name: DurationPriceValue,
|
||||||
vModel: "modelValue",
|
vModel: "modelValue"
|
||||||
edit: false
|
},
|
||||||
}
|
width: 350
|
||||||
|
}
|
||||||
|
},
|
||||||
|
supportBuy: {
|
||||||
|
title: "支持购买",
|
||||||
|
type: "dict-switch",
|
||||||
|
dict: dict({
|
||||||
|
data: [
|
||||||
|
{ label: "是", value: true, color: "success" },
|
||||||
|
{ label: "否", value: false, color: "gray" }
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
form: {
|
||||||
|
value: false
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 120
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
disabled: {
|
disabled: {
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
<template>
|
||||||
|
<div class="cd-duration-price-value">
|
||||||
|
<a-tag v-for="item of modelValue" :key="item.duration" class="flex-o price-group-item m-2">
|
||||||
|
<div style="width: 40px">{{ durationDict.dataMap[item.duration]?.label }}:</div>
|
||||||
|
<price-input v-model="item.price" :edit="false" class="mr-5" />
|
||||||
|
</a-tag>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { PriceItem } from "./api";
|
||||||
|
import PriceInput from "./price-input.vue";
|
||||||
|
import { durationDict } from "../../../certd/suite/api";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "DurationPriceValue"
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
modelValue?: PriceItem[];
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
modelValue: () => {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
<style lang="less">
|
||||||
|
.cd-duration-price-value {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
item-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<template>
|
||||||
|
<a-tag color="green"> {{ durationDict.dataMap[modelValue]?.label }}</a-tag>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { durationDict } from "/@/views/certd/suite/api";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue: number;
|
||||||
|
}>();
|
||||||
|
</script>
|
|
@ -1,50 +1,19 @@
|
||||||
<template>
|
<template>
|
||||||
<fs-page class="page-cert">
|
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||||
<template #header>
|
|
||||||
<div class="title">
|
|
||||||
套餐管理
|
|
||||||
<span class="sub"> 必须设置一个初始套餐 </span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
|
||||||
<template #pagination-left>
|
|
||||||
<a-tooltip title="批量删除">
|
|
||||||
<fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>
|
|
||||||
</a-tooltip>
|
|
||||||
</template>
|
|
||||||
</fs-crud>
|
|
||||||
</fs-page>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted } from "vue";
|
import { defineEmits, onMounted, ref } from "vue";
|
||||||
import { useFs } from "@fast-crud/fast-crud";
|
import { useFs } from "@fast-crud/fast-crud";
|
||||||
import createCrudOptions from "./crud";
|
import createCrudOptions from "./crud";
|
||||||
import { message, Modal } from "ant-design-vue";
|
|
||||||
import { DeleteBatch } from "./api";
|
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "ProductManager"
|
name: "ProductManager"
|
||||||
});
|
});
|
||||||
const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions });
|
const emit = defineEmits(["refreshed"]);
|
||||||
|
|
||||||
const selectedRowKeys = context.selectedRowKeys;
|
const context: any = { emit };
|
||||||
const handleBatchDelete = () => {
|
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context });
|
||||||
if (selectedRowKeys.value?.length > 0) {
|
|
||||||
Modal.confirm({
|
|
||||||
title: "确认",
|
|
||||||
content: `确定要批量删除这${selectedRowKeys.value.length}条记录吗`,
|
|
||||||
async onOk() {
|
|
||||||
await DeleteBatch(selectedRowKeys.value);
|
|
||||||
message.info("删除成功");
|
|
||||||
crudExpose.doRefresh();
|
|
||||||
selectedRowKeys.value = [];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
message.error("请先勾选记录");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 页面打开后获取列表数据
|
// 页面打开后获取列表数据
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { computed } from "vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue?: number;
|
modelValue?: number;
|
||||||
edit: boolean;
|
edit?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const priceValue = computed(() => {
|
const priceValue = computed(() => {
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="target" class="cd-suite-value">
|
<div v-if="target" class="cd-suite-value">
|
||||||
<a-tag :color="target.color" class="m-0">{{ target.label }}</a-tag>
|
<a-tag :color="target.color" class="m-0">
|
||||||
|
<span v-if="used != null">{{ used }} /</span>
|
||||||
|
{{ target.label }}
|
||||||
|
</a-tag>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -10,6 +13,7 @@ import { computed } from "vue";
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: number;
|
modelValue: number;
|
||||||
unit?: string;
|
unit?: string;
|
||||||
|
used?: number;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const target = computed(() => {
|
const target = computed(() => {
|
||||||
|
@ -29,10 +33,14 @@ const target = computed(() => {
|
||||||
color: "red"
|
color: "red"
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
let color = "blue";
|
||||||
|
if (props.used != null) {
|
||||||
|
color = props.used >= props.modelValue ? "red" : "green";
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
value: props.modelValue,
|
value: props.modelValue,
|
||||||
label: props.modelValue + (props.unit || ""),
|
label: props.modelValue + (props.unit || ""),
|
||||||
color: "blue"
|
color: color
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
<template>
|
||||||
|
<fs-page class="page-sys-settings page-sys-settings-suite">
|
||||||
|
<template #header>
|
||||||
|
<div class="title">
|
||||||
|
套餐设置
|
||||||
|
<span class="sub"> 需要<router-link to="/sys/settings" :query="{ tab: 'payment' }">开启至少一种支付方式</router-link></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="form-content">
|
||||||
|
<a-form ref="formRef" :model="formState" :label-col="{ style: { width: '150px' } }" :wrapper-col="{ span: 20 }" autocomplete="off">
|
||||||
|
<a-form-item label="开启套餐功能" name="enabled" required>
|
||||||
|
<a-switch v-model:checked="formState.enabled" />
|
||||||
|
</a-form-item>
|
||||||
|
<template v-if="formState.enabled">
|
||||||
|
<a-form-item label="套餐列表" name="enabled">
|
||||||
|
<div style="height: 400px">
|
||||||
|
<ProductManager @refreshed="onTableRefresh"></ProductManager>
|
||||||
|
</div>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="注册赠送套餐" name="registerGift">
|
||||||
|
<suite-duration-selector ref="suiteDurationSelectedRef" v-model="formState.registerGift"></suite-duration-selector>
|
||||||
|
<div class="helper">添加套餐后再选择</div>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="套餐说明" name="intro">
|
||||||
|
<a-textarea v-model:value="formState.intro" :rows="3"></a-textarea>
|
||||||
|
<div class="helper">将显示在套餐购买页面</div>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<a-form-item label=" " :colon="false">
|
||||||
|
<loading-button type="primary" html-type="button" :click="onClick">保存</loading-button>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</fs-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive, ref } from "vue";
|
||||||
|
import { merge } from "lodash-es";
|
||||||
|
import { notification } from "ant-design-vue";
|
||||||
|
import { request } from "/@/api/service";
|
||||||
|
import SuiteDurationSelector from "/@/views/sys/suite/setting/suite-duration-selector.vue";
|
||||||
|
import ProductManager from "/@/views/sys/suite/product/index.vue";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "SettingsSuite"
|
||||||
|
});
|
||||||
|
|
||||||
|
const api = {
|
||||||
|
async SuiteSettingGet() {
|
||||||
|
return await request({
|
||||||
|
url: "/sys/settings/suite/get",
|
||||||
|
method: "post"
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async SuiteSettingSave(data: any) {
|
||||||
|
return await request({
|
||||||
|
url: "/sys/settings/suite/save",
|
||||||
|
method: "post",
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formRef = ref<any>(null);
|
||||||
|
const formState = reactive<
|
||||||
|
Partial<{
|
||||||
|
enabled: boolean;
|
||||||
|
registerGift?: {
|
||||||
|
productId?: number;
|
||||||
|
duration?: number;
|
||||||
|
};
|
||||||
|
intro?: string;
|
||||||
|
}>
|
||||||
|
>({ enabled: false });
|
||||||
|
|
||||||
|
async function loadSettings() {
|
||||||
|
const data: any = await api.SuiteSettingGet();
|
||||||
|
merge(formState, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSettings();
|
||||||
|
const onClick = async () => {
|
||||||
|
const form = await formRef.value.validateFields();
|
||||||
|
await api.SuiteSettingSave(form);
|
||||||
|
await loadSettings();
|
||||||
|
notification.success({
|
||||||
|
message: "保存成功"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const suiteDurationSelectedRef = ref();
|
||||||
|
function onTableRefresh() {
|
||||||
|
suiteDurationSelectedRef.value?.refresh();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.page-sys-settings-suite {
|
||||||
|
.form-content {
|
||||||
|
padding: 20px;
|
||||||
|
.ant-table-body {
|
||||||
|
height: 400px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,85 @@
|
||||||
|
<template>
|
||||||
|
<fs-dict-select style="width: 200px" :value="selectedValue" :dict="suiteDictRef" @selected-change="onSelectedChange"></fs-dict-select>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { durationDict } from "/@/views/certd/suite/api";
|
||||||
|
import { ref, watch } from "vue";
|
||||||
|
import { dict } from "@fast-crud/fast-crud";
|
||||||
|
import { request } from "/@/api/service";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue?: {
|
||||||
|
productId?: number;
|
||||||
|
duration?: number;
|
||||||
|
};
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const suiteDictRef = dict({
|
||||||
|
async getData() {
|
||||||
|
const res = await request({
|
||||||
|
url: "/sys/suite/product/list",
|
||||||
|
method: "post"
|
||||||
|
});
|
||||||
|
const options: any = [
|
||||||
|
{
|
||||||
|
value: "",
|
||||||
|
label: "不赠送"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
res.forEach((item: any) => {
|
||||||
|
const durationPrices = JSON.parse(item.durationPrices);
|
||||||
|
for (const dp of durationPrices) {
|
||||||
|
const value = item.id + "_" + dp.duration;
|
||||||
|
options.push({
|
||||||
|
label: `${item.title}<${durationDict.dataMap[dp.duration]?.label}>`,
|
||||||
|
value: value,
|
||||||
|
target: {
|
||||||
|
productId: item.id,
|
||||||
|
duration: dp.duration
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedValue = ref();
|
||||||
|
watch(
|
||||||
|
() => {
|
||||||
|
return props.modelValue;
|
||||||
|
},
|
||||||
|
(value) => {
|
||||||
|
if (value && value.productId && value.duration) {
|
||||||
|
selectedValue.value = value.productId + "_" + value.duration;
|
||||||
|
} else {
|
||||||
|
selectedValue.value = "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits(["update:modelValue"]);
|
||||||
|
const onSelectedChange = (value: any) => {
|
||||||
|
selectedValue.value = value;
|
||||||
|
if (!value) {
|
||||||
|
emit("update:modelValue", undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const arr = value.value.split("_");
|
||||||
|
emit("update:modelValue", {
|
||||||
|
productId: parseInt(arr[0]),
|
||||||
|
duration: parseInt(arr[1])
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
refresh() {
|
||||||
|
suiteDictRef.reloadDict();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less"></style>
|
|
@ -10,27 +10,12 @@ CREATE TABLE "cd_product"
|
||||||
"price" integer,
|
"price" integer,
|
||||||
"intro" varchar(10240),
|
"intro" varchar(10240),
|
||||||
"order" integer,
|
"order" integer,
|
||||||
"is_bootstrap" boolean,
|
"support_buy" boolean,
|
||||||
"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)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE "cd_payment"
|
|
||||||
(
|
|
||||||
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
||||||
"type" varchar(100),
|
|
||||||
"title" varchar(100),
|
|
||||||
"setting" text,
|
|
||||||
"order" integer,
|
|
||||||
"disabled" boolean NOT NULL DEFAULT (false),
|
|
||||||
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
|
||||||
"update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE "cd_trade"
|
CREATE TABLE "cd_trade"
|
||||||
(
|
(
|
||||||
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
@ -55,7 +40,7 @@ CREATE TABLE "cd_trade"
|
||||||
|
|
||||||
CREATE INDEX "index_trade_user_id" ON "cd_trade" ("user_id");
|
CREATE INDEX "index_trade_user_id" ON "cd_trade" ("user_id");
|
||||||
CREATE UNIQUE INDEX "index_trade_trade_no" ON "cd_trade" ("trade_no");
|
CREATE UNIQUE INDEX "index_trade_trade_no" ON "cd_trade" ("trade_no");
|
||||||
CREATE INDEX "index_trade_pay_no" ON "cd_trade" ("pay_type","pay_no");
|
CREATE INDEX "index_trade_pay_no" ON "cd_trade" ("pay_type", "pay_no");
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE "cd_user_suite"
|
CREATE TABLE "cd_user_suite"
|
||||||
|
@ -68,7 +53,10 @@ CREATE TABLE "cd_user_suite"
|
||||||
"title" varchar(100),
|
"title" varchar(100),
|
||||||
"content" text,
|
"content" text,
|
||||||
"duration" integer,
|
"duration" integer,
|
||||||
"used_deploy_count" integer,
|
"deploy_count_used" integer,
|
||||||
|
"is_present" boolean,
|
||||||
|
"is_bootstrap" boolean,
|
||||||
|
"disabled" boolean NOT NULL DEFAULT (false),
|
||||||
"active_time" integer,
|
"active_time" integer,
|
||||||
"expires_time" integer,
|
"expires_time" integer,
|
||||||
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||||
|
@ -84,3 +72,63 @@ DROP TABLE IF EXISTS "cd_cert_issuer";
|
||||||
DROP TABLE IF EXISTS "cd_task";
|
DROP TABLE IF EXISTS "cd_task";
|
||||||
DROP TABLE IF EXISTS "cd_task_history";
|
DROP TABLE IF EXISTS "cd_task_history";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE "cd_cert_info"
|
||||||
|
(
|
||||||
|
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
"user_id" integer,
|
||||||
|
"domain" varchar(100),
|
||||||
|
"domains" varchar(10240),
|
||||||
|
"domain_count" integer,
|
||||||
|
"pipeline_id" integer,
|
||||||
|
"apply_time" integer,
|
||||||
|
"from_type" varchar(100),
|
||||||
|
"cert_provider" varchar(100),
|
||||||
|
"expires_time" integer,
|
||||||
|
"cert_info" text,
|
||||||
|
"cert_file" varchar(100),
|
||||||
|
"disabled" boolean NOT NULL DEFAULT (false),
|
||||||
|
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||||
|
"update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX "index_cert_info_user_id" ON "cd_cert_info" ("user_id");
|
||||||
|
CREATE INDEX "index_cert_info_domain" ON "cd_cert_info" ("domain");
|
||||||
|
CREATE INDEX "index_cert_info_domains" ON "cd_cert_info" ("domains");
|
||||||
|
CREATE INDEX "index_cert_info_pipeline" ON "cd_cert_info" ("pipeline_id");
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE "cd_site_info"
|
||||||
|
(
|
||||||
|
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
"user_id" integer,
|
||||||
|
|
||||||
|
"name" varchar(100),
|
||||||
|
"domain" varchar(100),
|
||||||
|
"domains" varchar(10240),
|
||||||
|
"cert_info" varchar(10240),
|
||||||
|
|
||||||
|
"cert_provider" varchar(100),
|
||||||
|
"cert_status" varchar(100),
|
||||||
|
"cert_expires_time" integer,
|
||||||
|
"last_check_time" integer,
|
||||||
|
"check_status" varchar(100),
|
||||||
|
"pipeline_id" integer,
|
||||||
|
"cert_info_id" integer,
|
||||||
|
"disabled" boolean NOT NULL DEFAULT (false),
|
||||||
|
|
||||||
|
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||||
|
"update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX "index_site_info_user_id" ON "cd_site_info" ("user_id");
|
||||||
|
CREATE INDEX "index_site_info_domain" ON "cd_site_info" ("domain");
|
||||||
|
CREATE INDEX "index_site_info_domains" ON "cd_site_info" ("domains");
|
||||||
|
CREATE INDEX "index_site_info_pipeline" ON "cd_site_info" ("pipeline_id");
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE pi_pipeline
|
||||||
|
ADD COLUMN "type" varchar(50);
|
||||||
|
ALTER TABLE pi_pipeline
|
||||||
|
ADD COLUMN "from" varchar(50);
|
||||||
|
|
|
@ -82,6 +82,7 @@
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"log4js": "^6.7.1",
|
"log4js": "^6.7.1",
|
||||||
"lru-cache": "^11.0.1",
|
"lru-cache": "^11.0.1",
|
||||||
|
"mitt": "^3.0.1",
|
||||||
"mwts": "^1.3.0",
|
"mwts": "^1.3.0",
|
||||||
"mwtsc": "^1.4.0",
|
"mwtsc": "^1.4.0",
|
||||||
"mysql": "^2.18.1",
|
"mysql": "^2.18.1",
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
|
||||||
|
import { Constants, CrudController } from '@certd/lib-server';
|
||||||
|
import { AuthService } from '../../modules/sys/authority/service/auth-service.js';
|
||||||
|
import { SiteInfoService } from '../../modules/monitor/service/site-info-service.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知
|
||||||
|
*/
|
||||||
|
@Provide()
|
||||||
|
@Controller('/api/monitor/site')
|
||||||
|
export class SiteInfoController extends CrudController<SiteInfoService> {
|
||||||
|
@Inject()
|
||||||
|
service: SiteInfoService;
|
||||||
|
@Inject()
|
||||||
|
authService: AuthService;
|
||||||
|
|
||||||
|
getService(): SiteInfoService {
|
||||||
|
return this.service;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/page', { summary: Constants.per.authOnly })
|
||||||
|
async page(@Body(ALL) body: any) {
|
||||||
|
body.query = body.query ?? {};
|
||||||
|
body.query.userId = this.getUserId();
|
||||||
|
const res = await this.service.page({
|
||||||
|
query: body.query,
|
||||||
|
page: body.page,
|
||||||
|
sort: body.sort,
|
||||||
|
});
|
||||||
|
return this.ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/list', { summary: Constants.per.authOnly })
|
||||||
|
async list(@Body(ALL) body: any) {
|
||||||
|
body.query = body.query ?? {};
|
||||||
|
body.query.userId = this.getUserId();
|
||||||
|
return await super.list(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/add', { summary: Constants.per.authOnly })
|
||||||
|
async add(@Body(ALL) bean: any) {
|
||||||
|
bean.userId = this.getUserId();
|
||||||
|
return await super.add(bean);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/update', { summary: Constants.per.authOnly })
|
||||||
|
async update(@Body(ALL) bean) {
|
||||||
|
await this.service.checkUserId(bean.id, this.getUserId());
|
||||||
|
delete bean.userId;
|
||||||
|
return await super.update(bean);
|
||||||
|
}
|
||||||
|
@Post('/info', { summary: Constants.per.authOnly })
|
||||||
|
async info(@Query('id') id: number) {
|
||||||
|
await this.service.checkUserId(id, this.getUserId());
|
||||||
|
return await super.info(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/delete', { summary: Constants.per.authOnly })
|
||||||
|
async delete(@Query('id') id: number) {
|
||||||
|
await this.service.checkUserId(id, this.getUserId());
|
||||||
|
return await super.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/all', { summary: Constants.per.authOnly })
|
||||||
|
async all() {
|
||||||
|
const list: any = await this.service.find({
|
||||||
|
where: {
|
||||||
|
userId: this.getUserId(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return this.ok(list);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
|
||||||
|
import { Constants, CrudController } from '@certd/lib-server';
|
||||||
|
import { AuthService } from '../../modules/sys/authority/service/auth-service.js';
|
||||||
|
import { CertInfoService } from '../../modules/monitor/service/cert-info-service.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知
|
||||||
|
*/
|
||||||
|
@Provide()
|
||||||
|
@Controller('/api/monitor/cert')
|
||||||
|
export class CertInfoController extends CrudController<CertInfoService> {
|
||||||
|
@Inject()
|
||||||
|
service: CertInfoService;
|
||||||
|
@Inject()
|
||||||
|
authService: AuthService;
|
||||||
|
|
||||||
|
getService(): CertInfoService {
|
||||||
|
return this.service;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/page', { summary: Constants.per.authOnly })
|
||||||
|
async page(@Body(ALL) body: any) {
|
||||||
|
body.query = body.query ?? {};
|
||||||
|
body.query.userId = this.getUserId();
|
||||||
|
|
||||||
|
const res = await this.service.page({
|
||||||
|
query: body.query,
|
||||||
|
page: body.page,
|
||||||
|
sort: body.sort,
|
||||||
|
});
|
||||||
|
return this.ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/list', { summary: Constants.per.authOnly })
|
||||||
|
async list(@Body(ALL) body: any) {
|
||||||
|
body.query = body.query ?? {};
|
||||||
|
body.query.userId = this.getUserId();
|
||||||
|
return await super.list(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/add', { summary: Constants.per.authOnly })
|
||||||
|
async add(@Body(ALL) bean: any) {
|
||||||
|
bean.userId = this.getUserId();
|
||||||
|
return await super.add(bean);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/update', { summary: Constants.per.authOnly })
|
||||||
|
async update(@Body(ALL) bean) {
|
||||||
|
await this.service.checkUserId(bean.id, this.getUserId());
|
||||||
|
delete bean.userId;
|
||||||
|
return await super.update(bean);
|
||||||
|
}
|
||||||
|
@Post('/info', { summary: Constants.per.authOnly })
|
||||||
|
async info(@Query('id') id: number) {
|
||||||
|
await this.service.checkUserId(id, this.getUserId());
|
||||||
|
return await super.info(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/delete', { summary: Constants.per.authOnly })
|
||||||
|
async delete(@Query('id') id: number) {
|
||||||
|
await this.service.checkUserId(id, this.getUserId());
|
||||||
|
return await super.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/all', { summary: Constants.per.authOnly })
|
||||||
|
async all() {
|
||||||
|
const list: any = await this.service.find({
|
||||||
|
where: {
|
||||||
|
userId: this.getUserId(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return this.ok(list);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
|
import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
|
||||||
import { Constants, CrudController } from '@certd/lib-server';
|
import { Constants, CrudController } from '@certd/lib-server';
|
||||||
import { AccessService } from '../../modules/pipeline/service/access-service.js';
|
import { AccessService } from '@certd/lib-server';
|
||||||
import { AuthService } from '../../modules/sys/authority/service/auth-service.js';
|
import { AuthService } from '../../modules/sys/authority/service/auth-service.js';
|
||||||
import { AccessDefine } from '@certd/pipeline';
|
import { AccessDefine } from '@certd/pipeline';
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,8 @@ import {
|
||||||
PluginRequestHandleReq,
|
PluginRequestHandleReq,
|
||||||
TaskInstanceContext,
|
TaskInstanceContext,
|
||||||
} from '@certd/pipeline';
|
} from '@certd/pipeline';
|
||||||
import { AccessService } from '../../modules/pipeline/service/access-service.js';
|
import { AccessService, AccessGetter } from '@certd/lib-server';
|
||||||
import { EmailService } from '../../modules/basic/service/email-service.js';
|
import { EmailService } from '../../modules/basic/service/email-service.js';
|
||||||
import { AccessGetter } from '../../modules/pipeline/service/access-getter.js';
|
|
||||||
import { http, HttpRequestConfig, logger, mergeUtils, utils } from '@certd/basic';
|
import { http, HttpRequestConfig, logger, mergeUtils, utils } from '@certd/basic';
|
||||||
import { NotificationService } from '../../modules/pipeline/service/notification-service.js';
|
import { NotificationService } from '../../modules/pipeline/service/notification-service.js';
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
|
import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
|
||||||
import { AccessService } from '../../../modules/pipeline/service/access-service.js';
|
import { AccessService } from '@certd/lib-server';
|
||||||
import { AccessController } from '../../pipeline/access-controller.js';
|
import { AccessController } from '../../pipeline/access-controller.js';
|
||||||
import { checkComm } from '@certd/plus-core';
|
import { checkComm } from '@certd/plus-core';
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,8 @@ import { SmsServiceFactory } from '../sms/factory.js';
|
||||||
import { ISmsService } from '../sms/api.js';
|
import { ISmsService } from '../sms/api.js';
|
||||||
import { CodeErrorException } from '@certd/lib-server/dist/basic/exception/code-error-exception.js';
|
import { CodeErrorException } from '@certd/lib-server/dist/basic/exception/code-error-exception.js';
|
||||||
import { EmailService } from './email-service.js';
|
import { EmailService } from './email-service.js';
|
||||||
import { AccessService } from '../../pipeline/service/access-service.js';
|
import { AccessService } from '@certd/lib-server';
|
||||||
import { AccessSysGetter } from '../../pipeline/service/access-sys-getter.js';
|
import { AccessSysGetter } from '@certd/lib-server';
|
||||||
import { isComm } from '@certd/plus-core';
|
import { isComm } from '@certd/plus-core';
|
||||||
|
|
||||||
// {data: '<svg.../svg>', text: 'abcd'}
|
// {data: '<svg.../svg>', text: 'abcd'}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { createDnsProvider, IDnsProvider, parseDomain } from '@certd/plugin-cert
|
||||||
import { CnameProvider, CnameRecord } from '@certd/pipeline';
|
import { CnameProvider, CnameRecord } from '@certd/pipeline';
|
||||||
import { cache, http, logger, utils } from '@certd/basic';
|
import { cache, http, logger, utils } from '@certd/basic';
|
||||||
|
|
||||||
import { AccessService } from '../../pipeline/service/access-service.js';
|
import { AccessService } from '@certd/lib-server';
|
||||||
import { isDev } from '@certd/basic';
|
import { isDev } from '@certd/basic';
|
||||||
import { walkTxtRecord } from '@certd/acme-client';
|
import { walkTxtRecord } from '@certd/acme-client';
|
||||||
import { CnameProviderService } from './cname-provider-service.js';
|
import { CnameProviderService } from './cname-provider-service.js';
|
||||||
|
|
|
@ -29,6 +29,14 @@ export class PipelineEntity {
|
||||||
@Column({ comment: '启用/禁用', nullable: true, default: false })
|
@Column({ comment: '启用/禁用', nullable: true, default: false })
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
|
|
||||||
|
// cert: 证书; backup: 备份; custom:自定义;
|
||||||
|
@Column({ comment: '类型', nullable: true, default: 'cert' })
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
// custom: 自定义; monitor: 监控;
|
||||||
|
@Column({ comment: '来源', nullable: true, default: 'custom' })
|
||||||
|
from: string;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
name: 'last_history_time',
|
name: 'last_history_time',
|
||||||
comment: '最后一次执行时间',
|
comment: '最后一次执行时间',
|
||||||
|
|
|
@ -1,11 +1,20 @@
|
||||||
import { Config, Inject, Provide, Scope, ScopeEnum, sleep } from '@midwayjs/core';
|
import { Config, Inject, Provide, Scope, ScopeEnum, sleep } from '@midwayjs/core';
|
||||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||||
import { In, MoreThan, Repository } from 'typeorm';
|
import { In, MoreThan, Repository } from 'typeorm';
|
||||||
import { BaseService, NeedVIPException, PageReq, SysPublicSettings, SysSettingsService, SysSiteInfo } from '@certd/lib-server';
|
import {
|
||||||
|
AccessGetter,
|
||||||
|
AccessService,
|
||||||
|
BaseService,
|
||||||
|
NeedSuiteException,
|
||||||
|
NeedVIPException,
|
||||||
|
PageReq,
|
||||||
|
SysPublicSettings,
|
||||||
|
SysSettingsService,
|
||||||
|
SysSiteInfo,
|
||||||
|
} from '@certd/lib-server';
|
||||||
import { PipelineEntity } from '../entity/pipeline.js';
|
import { PipelineEntity } from '../entity/pipeline.js';
|
||||||
import { PipelineDetail } from '../entity/vo/pipeline-detail.js';
|
import { PipelineDetail } from '../entity/vo/pipeline-detail.js';
|
||||||
import { Executor, Pipeline, ResultType, RunHistory, SysInfo, UserInfo } from '@certd/pipeline';
|
import { Executor, Pipeline, ResultType, RunHistory, RunnableCollection, SysInfo, UserInfo } from '@certd/pipeline';
|
||||||
import { AccessService } from './access-service.js';
|
|
||||||
import { DbStorage } from './db-storage.js';
|
import { DbStorage } from './db-storage.js';
|
||||||
import { StorageService } from './storage-service.js';
|
import { StorageService } from './storage-service.js';
|
||||||
import { Cron } from '../../cron/cron.js';
|
import { Cron } from '../../cron/cron.js';
|
||||||
|
@ -15,26 +24,26 @@ import { HistoryLogEntity } from '../entity/history-log.js';
|
||||||
import { HistoryLogService } from './history-log-service.js';
|
import { HistoryLogService } from './history-log-service.js';
|
||||||
import { EmailService } from '../../basic/service/email-service.js';
|
import { EmailService } from '../../basic/service/email-service.js';
|
||||||
import { UserService } from '../../sys/authority/service/user-service.js';
|
import { UserService } from '../../sys/authority/service/user-service.js';
|
||||||
import { AccessGetter } from './access-getter.js';
|
|
||||||
import { CnameRecordService } from '../../cname/service/cname-record-service.js';
|
import { CnameRecordService } from '../../cname/service/cname-record-service.js';
|
||||||
import { CnameProxyService } from './cname-proxy-service.js';
|
import { CnameProxyService } from './cname-proxy-service.js';
|
||||||
import { PluginConfigGetter } from '../../plugin/service/plugin-config-getter.js';
|
import { PluginConfigGetter } from '../../plugin/service/plugin-config-getter.js';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { DbAdapter } from '../../db/index.js';
|
import { DbAdapter } from '../../db/index.js';
|
||||||
import { isComm, isPlus } from '@certd/plus-core';
|
import { isComm } from '@certd/plus-core';
|
||||||
import { logger } from '@certd/basic';
|
import { logger } from '@certd/basic';
|
||||||
import { UrlService } from './url-service.js';
|
import { UrlService } from './url-service.js';
|
||||||
import { NotificationService } from './notification-service.js';
|
import { NotificationService } from './notification-service.js';
|
||||||
import { NotificationGetter } from './notification-getter.js';
|
import { NotificationGetter } from './notification-getter.js';
|
||||||
|
import { UserSuiteService } from '@certd/commercial-core';
|
||||||
|
import { CertInfoService } from '../../monitor/service/cert-info-service.js';
|
||||||
|
|
||||||
const runningTasks: Map<string | number, Executor> = new Map();
|
const runningTasks: Map<string | number, Executor> = new Map();
|
||||||
const freeCount = 10;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 证书申请
|
* 证书申请
|
||||||
*/
|
*/
|
||||||
@Provide()
|
@Provide()
|
||||||
@Scope(ScopeEnum.Singleton)
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
export class PipelineService extends BaseService<PipelineEntity> {
|
export class PipelineService extends BaseService<PipelineEntity> {
|
||||||
@InjectEntityModel(PipelineEntity)
|
@InjectEntityModel(PipelineEntity)
|
||||||
repository: Repository<PipelineEntity>;
|
repository: Repository<PipelineEntity>;
|
||||||
|
@ -60,6 +69,9 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||||
@Inject()
|
@Inject()
|
||||||
userService: UserService;
|
userService: UserService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
userSuiteService: UserSuiteService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
cron: Cron;
|
cron: Cron;
|
||||||
|
|
||||||
|
@ -75,6 +87,9 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||||
@Inject()
|
@Inject()
|
||||||
dbAdapter: DbAdapter;
|
dbAdapter: DbAdapter;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
certInfoService: CertInfoService;
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
getRepository() {
|
getRepository() {
|
||||||
return this.repository;
|
return this.repository;
|
||||||
|
@ -144,26 +159,19 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||||
//修改
|
//修改
|
||||||
old = await this.info(bean.id);
|
old = await this.info(bean.id);
|
||||||
}
|
}
|
||||||
|
const pipeline = JSON.parse(bean.content || '{}');
|
||||||
const isUpdate = bean.id > 0 && old != null;
|
const isUpdate = bean.id > 0 && old != null;
|
||||||
|
|
||||||
|
let domains = [];
|
||||||
|
RunnableCollection.each(pipeline.stages, (runnable: any) => {
|
||||||
|
if (runnable.runnableType === 'step' && runnable.type.startsWith('CertApply')) {
|
||||||
|
domains = runnable.input.domains || [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (!isUpdate) {
|
if (!isUpdate) {
|
||||||
//如果是添加,校验数量
|
//如果是添加,校验数量
|
||||||
if (!isPlus()) {
|
await this.checkMaxPipelineCount(bean, pipeline, domains);
|
||||||
const count = await this.repository.count();
|
|
||||||
if (count >= freeCount) {
|
|
||||||
throw new NeedVIPException(`基础版最多只能创建${freeCount}条流水线`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const userId = bean.userId;
|
|
||||||
const userIsAdmin = await this.userService.isAdmin(userId);
|
|
||||||
if (!userIsAdmin) {
|
|
||||||
//非管理员用户,限制pipeline数量
|
|
||||||
const count = await this.repository.count({ where: { userId } });
|
|
||||||
const sysPublic = await this.sysSettingsService.getSetting<SysPublicSettings>(SysPublicSettings);
|
|
||||||
const limitUserPipelineCount = sysPublic.limitUserPipelineCount;
|
|
||||||
if (limitUserPipelineCount && limitUserPipelineCount > 0 && count >= limitUserPipelineCount) {
|
|
||||||
throw new NeedVIPException(`您最多只能创建${limitUserPipelineCount}条流水线`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isUpdate) {
|
if (!isUpdate) {
|
||||||
|
@ -171,19 +179,51 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||||
await this.addOrUpdate(bean);
|
await this.addOrUpdate(bean);
|
||||||
}
|
}
|
||||||
await this.clearTriggers(bean.id);
|
await this.clearTriggers(bean.id);
|
||||||
if (bean.content) {
|
if (pipeline.title) {
|
||||||
const pipeline = JSON.parse(bean.content);
|
bean.title = pipeline.title;
|
||||||
if (pipeline.title) {
|
|
||||||
bean.title = pipeline.title;
|
|
||||||
}
|
|
||||||
pipeline.id = bean.id;
|
|
||||||
bean.content = JSON.stringify(pipeline);
|
|
||||||
}
|
}
|
||||||
|
pipeline.id = bean.id;
|
||||||
|
bean.content = JSON.stringify(pipeline);
|
||||||
await this.addOrUpdate(bean);
|
await this.addOrUpdate(bean);
|
||||||
await this.registerTriggerById(bean.id);
|
await this.registerTriggerById(bean.id);
|
||||||
|
|
||||||
|
//保存域名信息到certInfo表
|
||||||
|
await this.certInfoService.updateDomains(pipeline.id, domains);
|
||||||
return bean;
|
return bean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async checkMaxPipelineCount(bean: PipelineEntity, pipeline: Pipeline, domains: string[]) {
|
||||||
|
// if (!isPlus()) {
|
||||||
|
// const count = await this.repository.count();
|
||||||
|
// if (count >= freeCount) {
|
||||||
|
// throw new NeedVIPException(`基础版最多只能创建${freeCount}条流水线`);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
if (isComm()) {
|
||||||
|
//校验pipelineCount
|
||||||
|
const userSuite = await this.userSuiteService.getMySuiteDetail(bean.userId);
|
||||||
|
if (userSuite?.pipelineCount.used + 1 > userSuite?.pipelineCount.max) {
|
||||||
|
throw new NeedSuiteException(`对不起,您最多只能创建${userSuite?.pipelineCount.max}条流水线,请购买或升级套餐`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userSuite.domainCount.used + domains.length > userSuite.domainCount.max) {
|
||||||
|
throw new NeedSuiteException(`对不起,您最多只能添加${userSuite.domainCount.max}个域名,请购买或升级套餐`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = bean.userId;
|
||||||
|
const userIsAdmin = await this.userService.isAdmin(userId);
|
||||||
|
if (!userIsAdmin) {
|
||||||
|
//非管理员用户,限制pipeline数量
|
||||||
|
const count = await this.repository.count({ where: { userId } });
|
||||||
|
const sysPublic = await this.sysSettingsService.getSetting<SysPublicSettings>(SysPublicSettings);
|
||||||
|
const limitUserPipelineCount = sysPublic.limitUserPipelineCount;
|
||||||
|
if (limitUserPipelineCount && limitUserPipelineCount > 0 && count >= limitUserPipelineCount) {
|
||||||
|
throw new NeedVIPException(`您最多只能创建${limitUserPipelineCount}条流水线`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async foreachPipeline(callback: (pipeline: PipelineEntity) => void) {
|
async foreachPipeline(callback: (pipeline: PipelineEntity) => void) {
|
||||||
const idEntityList = await this.repository.find({
|
const idEntityList = await this.repository.find({
|
||||||
select: {
|
select: {
|
||||||
|
@ -578,4 +618,8 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||||
{ groupId }
|
{ groupId }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getUserPipelineCount(userId) {
|
||||||
|
return await this.repository.count({ where: { userId } });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||||
|
import { PipelineService } from '../../pipeline/service/pipeline-service.js';
|
||||||
|
import { CertInfoService } from '../../monitor/service/cert-info-service.js';
|
||||||
|
import { IUsedCountService } from '@certd/commercial-core';
|
||||||
|
import { SiteInfoService } from '../../monitor/service/site-info-service.js';
|
||||||
|
|
||||||
|
@Provide('myCountService')
|
||||||
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
|
export class MyCountService implements IUsedCountService {
|
||||||
|
@Inject()
|
||||||
|
pipelineService: PipelineService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
certInfoService: CertInfoService;
|
||||||
|
@Inject()
|
||||||
|
siteInfoService: SiteInfoService;
|
||||||
|
|
||||||
|
async getUsedCount(userId: number) {
|
||||||
|
if (!userId) {
|
||||||
|
throw new Error('userId is required');
|
||||||
|
}
|
||||||
|
const pipelineCountUsed = await this.pipelineService.getUserPipelineCount(userId);
|
||||||
|
const domainCountUsed = await this.certInfoService.getUserDomainCount(userId);
|
||||||
|
const monitorCountUsed = await this.siteInfoService.getUserMonitorCount(userId);
|
||||||
|
return {
|
||||||
|
pipelineCountUsed,
|
||||||
|
domainCountUsed,
|
||||||
|
monitorCountUsed,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -206,6 +206,9 @@ export class UserService extends BaseService<UserEntity> {
|
||||||
});
|
});
|
||||||
|
|
||||||
delete newUser.password;
|
delete newUser.password;
|
||||||
|
|
||||||
|
utils.mitter.emit('register', { userId: newUser.id });
|
||||||
|
|
||||||
return newUser;
|
return newUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue