perf: 选项显示图标

pull/265/head
xiaojunnuo 2024-11-30 01:57:09 +08:00
parent 7b55337c5e
commit aedc462135
54 changed files with 298 additions and 52 deletions

View File

@ -18,6 +18,7 @@ export type AccessInputDefine = FormItemProps & {
encrypt?: boolean; encrypt?: boolean;
}; };
export type AccessDefine = Registrable & { export type AccessDefine = Registrable & {
icon?: string;
input?: { input?: {
[key: string]: AccessInputDefine; [key: string]: AccessInputDefine;
}; };

View File

@ -31,6 +31,7 @@ export type NotificationInputDefine = FormItemProps & {
encrypt?: boolean; encrypt?: boolean;
}; };
export type NotificationDefine = Registrable & { export type NotificationDefine = Registrable & {
needPlus?: boolean;
input?: { input?: {
[key: string]: NotificationInputDefine; [key: string]: NotificationInputDefine;
}; };

View File

@ -4,6 +4,7 @@ import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline";
name: "eab", name: "eab",
title: "EAB授权", title: "EAB授权",
desc: "ZeroSSL证书申请需要EAB授权", desc: "ZeroSSL证书申请需要EAB授权",
icon: "ic:outline-lock",
}) })
export class EabAccess extends BaseAccess { export class EabAccess extends BaseAccess {
@AccessInput({ @AccessInput({

View File

@ -4,6 +4,7 @@ import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline";
name: "google", name: "google",
title: "google cloud", title: "google cloud",
desc: "谷歌云授权", desc: "谷歌云授权",
icon: "flat-color-icons:google",
}) })
export class GoogleAccess extends BaseAccess { export class GoogleAccess extends BaseAccess {
@AccessInput({ @AccessInput({

View File

@ -3,6 +3,7 @@ import { IAccess, Registrable } from "@certd/pipeline";
export type DnsProviderDefine = Registrable & { export type DnsProviderDefine = Registrable & {
accessType: string; accessType: string;
icon?: string;
autowire?: { autowire?: {
[key: string]: any; [key: string]: any;
}; };

View File

@ -129,12 +129,12 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
title: "证书颁发机构", title: "证书颁发机构",
value: "letsencrypt", value: "letsencrypt",
component: { component: {
name: "a-select", name: "icon-select",
vModel: "value", vModel: "value",
options: [ options: [
{ value: "letsencrypt", label: "Let's Encrypt" }, { value: "letsencrypt", label: "Let's Encrypt", icon: "simple-icons:letsencrypt" },
{ value: "google", label: "Google" }, { value: "google", label: "Google", icon: "flat-color-icons:google" },
{ value: "zerossl", label: "ZeroSSL" }, { value: "zerossl", label: "ZeroSSL", icon: "emojione:digit-zero" },
], ],
}, },
helper: "Let's Encrypt申请最简单\nGoogle大厂光环兼容性好需要翻墙获取EAB授权\nZeroSSL有数量限制获取EAB授权无需翻墙", helper: "Let's Encrypt申请最简单\nGoogle大厂光环兼容性好需要翻墙获取EAB授权\nZeroSSL有数量限制获取EAB授权无需翻墙",

View File

@ -4,6 +4,7 @@ import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline";
name: "aliyun", name: "aliyun",
title: "阿里云授权", title: "阿里云授权",
desc: "", desc: "",
icon: "ant-design:aliyun-outlined",
}) })
export class AliyunAccess extends BaseAccess { export class AliyunAccess extends BaseAccess {
@AccessInput({ @AccessInput({

View File

@ -5,6 +5,7 @@ import { ConnectConfig } from "ssh2";
name: "ssh", name: "ssh",
title: "主机登录授权", title: "主机登录授权",
desc: "", desc: "",
icon: "clarity:host-line",
input: {}, input: {},
}) })
export class SshAccess extends BaseAccess implements ConnectConfig { export class SshAccess extends BaseAccess implements ConnectConfig {

View File

@ -0,0 +1,16 @@
<template>
<a-select>
<a-select-option v-for="item of options" :keu="item.value" :value="item.value" :label="item.label">
<span class="flex-o">
<fs-icon :icon="item.icon" class="fs-16 color-blue mr-5" />
{{ item.label }}
</span>
</a-select-option>
</a-select>
</template>
<script lang="ts" setup>
const props = defineProps<{
options: { value: any; label: string; icon: string }[];
}>();
</script>

View File

@ -7,7 +7,8 @@ import FoldBox from "./fold-box.vue";
import { CronLight } from "@vue-js-cron/light"; import { CronLight } from "@vue-js-cron/light";
import "@vue-js-cron/light/dist/light.css"; 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 IconSelect from "./icon-select.vue";
export default { export default {
install(app: any) { install(app: any) {
app.component("PiContainer", PiContainer); app.component("PiContainer", PiContainer);
@ -22,6 +23,9 @@ export default {
app.component("InfoCircleOutlined", InfoCircleOutlined); app.component("InfoCircleOutlined", InfoCircleOutlined);
app.component("UndoOutlined", UndoOutlined); app.component("UndoOutlined", UndoOutlined);
app.component("LoadingButton", LoadingButton);
app.component("IconSelect", IconSelect);
app.use(vip); app.use(vip);
app.use(Plugins); app.use(Plugins);
} }

View File

@ -0,0 +1,24 @@
<template>
<a-button :loading="loading" @click="onClick">
<slot></slot>
</a-button>
</template>
<script setup lang="ts">
import { ref } from "vue";
const props = defineProps<{
click?: () => Promise<void>;
}>();
const loading = ref(false);
function onClick() {
loading.value = true;
try {
if (props.click) {
props.click();
}
} finally {
loading.value = false;
}
}
</script>

View File

@ -1,5 +1,5 @@
<template> <template>
<a-select class="dns-provider-selector" :value="modelValue" :options="options" @update:value="onChanged"> </a-select> <icon-select class="dns-provider-selector" :value="modelValue" :options="options" @update:value="onChanged"> </icon-select>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -24,7 +24,8 @@ export default {
for (let item of list) { for (let item of list) {
array.push({ array.push({
value: item.name, value: item.name,
label: item.title label: item.title,
icon: item.icon
}); });
} }
options.value = array; options.value = array;

View File

@ -12,18 +12,19 @@
</div> </div>
</template> </template>
<script lang="tsx" setup> <script lang="tsx" setup>
import { computed, reactive } from "vue"; import { computed, onMounted, reactive } from "vue";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { message, Modal } from "ant-design-vue"; import { message, Modal } from "ant-design-vue";
import * as api from "./api"; import * as api from "./api";
import { useSettingStore } from "/@/store/modules/settings"; import { useSettingStore } from "/@/store/modules/settings";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { useUserStore } from "/@/store/modules/user"; import { useUserStore } from "/@/store/modules/user";
import { mitter } from "/@/utils/util.mitt";
const settingStore = useSettingStore(); const settingStore = useSettingStore();
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
mode?: "button" | "nav" | "icon"; mode?: "comm" | "button" | "nav" | "icon";
}>(), }>(),
{ {
mode: "button" mode: "button"
@ -36,7 +37,29 @@ type Text = {
const text = computed<Text>(() => { const text = computed<Text>(() => {
const vipLabel = settingStore.vipLabel; const vipLabel = settingStore.vipLabel;
const map = { const map = {
isComm: {
comm: {
name: `${vipLabel}已开通`,
title: "到期时间:" + expireTime.value
},
button: {
name: `${vipLabel}已开通`,
title: "到期时间:" + expireTime.value
},
icon: {
name: "",
title: `${vipLabel}已开通`
},
nav: {
name: `${vipLabel}`,
title: "到期时间:" + expireTime.value
}
},
isPlus: { isPlus: {
comm: {
name: "商业版功能",
title: "升级商业版,获取商业授权"
},
button: { button: {
name: `${vipLabel}已开通`, name: `${vipLabel}已开通`,
title: "到期时间:" + expireTime.value title: "到期时间:" + expireTime.value
@ -51,13 +74,17 @@ const text = computed<Text>(() => {
} }
}, },
free: { free: {
comm: {
name: "商业版功能",
title: "升级商业版,获取商业授权"
},
button: { button: {
name: "此为专业版功能", name: "专业版功能",
title: "升级专业版享受更多VIP特权" title: "升级专业版享受更多VIP特权"
}, },
icon: { icon: {
name: "", name: "",
title: "此为专业版功能" title: "专业版功能"
}, },
nav: { nav: {
name: "基础版", name: "基础版",
@ -65,7 +92,9 @@ const text = computed<Text>(() => {
} }
} }
}; };
if (settingStore.isPlus) { if (settingStore.isComm) {
return map.isComm[props.mode];
} else if (settingStore.isPlus) {
return map.isPlus[props.mode]; return map.isPlus[props.mode];
} else { } else {
return map.free[props.mode]; return map.free[props.mode];
@ -313,6 +342,13 @@ function openUpgrade() {
} }
}); });
} }
onMounted(() => {
mitter.on("openVipModal", () => {
if (props.mode === "nav" && !settingStore.isPlus) {
openUpgrade();
}
});
});
</script> </script>
<style lang="less"> <style lang="less">

View File

@ -169,7 +169,7 @@ export const sysResources = [
icon: "ion:person-outline", icon: "ion:person-outline",
permission: "sys:auth:user:view" permission: "sys:auth:user:view"
} }
}, }
// { // {
// title: "商业版设置", // title: "商业版设置",

View File

@ -60,6 +60,11 @@ h1, h2, h3, h4, h5, h6 {
align-items: center; align-items: center;
} }
.flex-between {
display: flex;
justify-content: space-between;
}
.flex { .flex {
display: flex; display: flex;
align-items: center; align-items: center;
@ -192,7 +197,9 @@ h1, h2, h3, h4, h5, h6 {
border-bottom: 1px solid #dedede; border-bottom: 1px solid #dedede;
} }
.color-plus{
color: #c5913f;
}
.color-blue { .color-blue {
color: #1890ff; color: #1890ff;
} }

View File

@ -87,6 +87,14 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
filterOption: (input: string, option: any) => { filterOption: (input: string, option: any) => {
input = input?.toLowerCase(); input = input?.toLowerCase();
return option.value.toLowerCase().indexOf(input) >= 0 || option.label.toLowerCase().indexOf(input) >= 0; return option.value.toLowerCase().indexOf(input) >= 0 || option.label.toLowerCase().indexOf(input) >= 0;
},
renderLabel(item: any) {
return (
<span class={"flex-o"}>
<fs-icon icon={item.icon} class={"mr-5 fs-16 color-blue"} />
{item.label}
</span>
);
} }
}, },
rules: [{ required: true, message: "请选择类型" }], rules: [{ required: true, message: "请选择类型" }],

View File

@ -4,6 +4,7 @@ import { useReference } from "/@/use/use-refrence";
import { forEach, get, merge, set } from "lodash-es"; import { forEach, get, merge, set } from "lodash-es";
import { Modal } from "ant-design-vue"; import { Modal } from "ant-design-vue";
import * as api from "/@/views/sys/cname/provider/api"; import * as api from "/@/views/sys/cname/provider/api";
import { mitter } from "/@/utils/util.mitt";
export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) { export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
provide("notificationApi", api); provide("notificationApi", api);
@ -96,6 +97,14 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
filterOption: (input: string, option: any) => { filterOption: (input: string, option: any) => {
input = input?.toLowerCase(); input = input?.toLowerCase();
return option.value.toLowerCase().indexOf(input) >= 0 || option.label.toLowerCase().indexOf(input) >= 0; 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: "请选择通知类型" }], rules: [{ required: true, message: "请选择通知类型" }],
@ -112,6 +121,8 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
if (!immediate) { if (!immediate) {
form.body = {}; form.body = {};
mitter.emit("openVipModal");
} }
if (!form.name || form.name === lastTitle) { if (!form.name || form.name === lastTitle) {

View File

@ -142,7 +142,7 @@ export default defineComponent({
}, },
{ {
validator: async (rule: any, value: any) => { validator: async (rule: any, value: any) => {
if (value !== formState.password) { if (value && value !== formState.password) {
throw new Error("两次输入密码不一致"); throw new Error("两次输入密码不一致");
} }
return true; return true;

View File

@ -77,3 +77,11 @@ export async function TestProxy() {
method: "post" method: "post"
}); });
} }
export async function TestSms(data: any) {
return await request({
url: apiPrefix + "/testSms",
method: "post",
data
});
}

View File

@ -4,12 +4,12 @@
<!-- <div class="title">系统设置</div>--> <!-- <div class="title">系统设置</div>-->
<!-- </template>--> <!-- </template>-->
<div class="sys-settings-body"> <div class="sys-settings-body">
<a-tabs type="card" class="sys-settings-tabs"> <a-tabs :active-key="activeKey" type="card" class="sys-settings-tabs" @update:active-key="onChange">
<a-tab-pane key="site" tab="基本设置"> <a-tab-pane key="" tab="基本设置">
<SettingBase /> <SettingBase v-if="activeKey === ''" />
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="register" tab="注册设置"> <a-tab-pane key="register" tab="注册设置">
<SettingRegister /> <SettingRegister v-if="activeKey === 'register'" />
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
</div> </div>
@ -17,13 +17,31 @@
</template> </template>
<script setup lang="tsx"> <script setup lang="tsx">
import { SysSettings } from "./api";
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 { useRoute, useRouter } from "vue-router";
import { ref } from "vue";
defineOptions({ defineOptions({
name: "SysSettings" name: "SysSettings"
}); });
const activeKey = ref("");
const route = useRoute();
const router = useRouter();
if (route.query.tab) {
activeKey.value = (route.query.tab as string) || "";
}
function onChange(value: string) {
// activeKey.value = value;
//
const query: any = {};
if (value !== "") {
query.tab = value;
}
// 使`push`
router.push({ path: route.path, query });
}
</script> </script>
<style lang="less"> <style lang="less">

View File

@ -4,28 +4,31 @@
<a-form-item label="开启自助注册" :name="['public', 'registerEnabled']"> <a-form-item label="开启自助注册" :name="['public', 'registerEnabled']">
<a-switch v-model:checked="formState.public.registerEnabled" /> <a-switch v-model:checked="formState.public.registerEnabled" />
</a-form-item> </a-form-item>
<a-form-item label="限制用户流水线数量" :name="['public', 'limitUserPipelineCount']">
<a-input-number v-model:value="formState.public.limitUserPipelineCount" />
<div class="helper">0为不限制</div>
</a-form-item>
<a-form-item label="管理其他用户流水线" :name="['public', 'managerOtherUserPipeline']">
<a-switch v-model:checked="formState.public.managerOtherUserPipeline" />
</a-form-item>
<template v-if="formState.public.registerEnabled"> <template v-if="formState.public.registerEnabled">
<a-form-item label="限制用户流水线数量" :name="['public', 'limitUserPipelineCount']">
<a-input-number v-model:value="formState.public.limitUserPipelineCount" />
<div class="helper">0为不限制</div>
</a-form-item>
<a-form-item label="管理其他用户流水线" :name="['public', 'managerOtherUserPipeline']">
<a-switch v-model:checked="formState.public.managerOtherUserPipeline" />
</a-form-item>
<a-form-item label="开启用户名注册" :name="['public', 'usernameRegisterEnabled']"> <a-form-item label="开启用户名注册" :name="['public', 'usernameRegisterEnabled']">
<a-switch v-model:checked="formState.public.usernameRegisterEnabled" /> <a-switch v-model:checked="formState.public.usernameRegisterEnabled" />
</a-form-item> </a-form-item>
<a-form-item label="开启邮箱注册" :name="['public', 'emailRegisterEnabled']"> <a-form-item label="开启邮箱注册" :name="['public', 'emailRegisterEnabled']">
<a-switch v-model:checked="formState.public.emailRegisterEnabled" /> <div class="flex-o">
<a-switch v-model:checked="formState.public.emailRegisterEnabled" :disabled="!settingsStore.isPlus" title="专业版功能" />
<vip-button class="ml-5" mode="button"></vip-button>
</div>
<div class="helper">需要<router-link to="/sys/settings/email">设置邮箱服务器</router-link></div> <div class="helper">需要<router-link to="/sys/settings/email">设置邮箱服务器</router-link></div>
</a-form-item> </a-form-item>
<a-form-item label="开启密码登录" :name="['public', 'passwordLoginEnabled']">
<a-switch v-model:checked="formState.public.passwordLoginEnabled" />
</a-form-item>
<a-form-item label="开启手机号登录、注册" :name="['public', 'smsLoginEnabled']"> <a-form-item label="开启手机号登录、注册" :name="['public', 'smsLoginEnabled']">
<a-switch v-model:checked="formState.public.smsLoginEnabled" /> <div class="flex-o">
<a-switch v-model:checked="formState.public.smsLoginEnabled" :disabled="!settingsStore.isComm" title="商业版功能" />
<vip-button class="ml-5" mode="comm"></vip-button>
</div>
</a-form-item> </a-form-item>
<template v-if="formState.public.smsLoginEnabled"> <template v-if="formState.public.smsLoginEnabled">
<a-form-item label="短信提供商" :name="['private', 'sms', 'type']"> <a-form-item label="短信提供商" :name="['private', 'sms', 'type']">
@ -34,18 +37,25 @@
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="阿里云授权" :name="['private', 'sms', 'config', 'accessId']"> <a-form-item label="阿里云授权" :name="['private', 'sms', 'config', 'accessId']" :rules="rules.required">
<access-selector v-model="formState.private.sms.config.accessId" /> <access-selector v-model="formState.private.sms.config.accessId" />
</a-form-item> </a-form-item>
<a-form-item label="短信签名" :name="['private', 'sms', 'config', 'signName']"> <a-form-item label="短信签名" :name="['private', 'sms', 'config', 'signName']" :rules="rules.required">
<a-input v-model:value="formState.private.sms.config.signName" /> <a-input v-model:value="formState.private.sms.config.signName" />
</a-form-item> </a-form-item>
<a-form-item label="验证码模版ID" :name="['private', 'sms', 'config', 'codeTemplateId']"> <a-form-item label="验证码模版ID" :name="['private', 'sms', 'config', 'codeTemplateId']" :rules="rules.required">
<a-input v-model:value="formState.private.sms.config.codeTemplateId" /> <a-input v-model:value="formState.private.sms.config.codeTemplateId" />
<div class="helper">需要配置一个变量为{code}的验证码模版</div> <div class="helper">需要配置一个变量为{code}的验证码模版</div>
</a-form-item> </a-form-item>
<a-form-item label="短信测试">
<div class="flex">
<a-input v-model:value="testMobile" placeholder="输入测试手机号" />
<loading-button class="ml-5" title="保存后再点击测试" type="primary" :click="testSendSms">测试</loading-button>
</div>
<div class="helper">保存后再点击测试</div>
</a-form-item>
</template> </template>
</template> </template>
@ -63,13 +73,23 @@ import * as api from "/@/views/sys/settings/api";
import { merge } from "lodash-es"; import { merge } from "lodash-es";
import { useSettingStore } from "/@/store/modules/settings"; import { useSettingStore } from "/@/store/modules/settings";
import { notification } from "ant-design-vue"; import { notification } from "ant-design-vue";
import { util } from "/@/utils";
import AccessSelector from "/@/views/certd/access/access-selector/index.vue";
defineOptions({ defineOptions({
name: "SettingRegister" name: "SettingRegister"
}); });
const testMobile = ref("");
function testSendSms() {
if (!testMobile.value) {
notification.error({
message: "请输入测试手机号"
});
return;
}
api.TestSms({
mobile: testMobile.value
});
}
const formState = reactive<Partial<SysSettings>>({ const formState = reactive<Partial<SysSettings>>({
public: { public: {
registerEnabled: false registerEnabled: false
@ -82,6 +102,22 @@ const formState = reactive<Partial<SysSettings>>({
} }
}); });
const rules = {
leastOneLogin: {
validator: (rule: any, value: any) => {
if (!formState.public.passwordLoginEnabled && !formState.public.smsLoginEnabled) {
return Promise.reject("密码登录和手机号登录至少开启一个");
}
return Promise.resolve();
}
},
required: {
required: true,
trigger: "change",
message: "此项必填"
}
};
async function loadSysSettings() { async function loadSysSettings() {
const data: any = await api.SysSettingsGet(); const data: any = await api.SysSettingsGet();
merge(formState, data); merge(formState, data);

View File

@ -2,6 +2,7 @@ import { ALL, Body, Controller, Inject, Post, Provide } from '@midwayjs/core';
import { LoginService } from '../../modules/login/service/login-service.js'; import { LoginService } from '../../modules/login/service/login-service.js';
import { BaseController, Constants, SysPublicSettings, SysSettingsService } from '@certd/lib-server'; import { BaseController, Constants, SysPublicSettings, SysSettingsService } from '@certd/lib-server';
import { CodeService } from '../../modules/basic/service/code-service.js'; import { CodeService } from '../../modules/basic/service/code-service.js';
import { checkComm } from '@certd/plus-core';
/** /**
*/ */
@ -21,11 +22,6 @@ export class LoginController extends BaseController {
@Body(ALL) @Body(ALL)
user: any user: any
) { ) {
const settings = await this.sysSettingsService.getSetting<SysPublicSettings>(SysPublicSettings);
if (settings.passwordLoginEnabled === false) {
throw new Error('当前站点已禁止密码登录');
}
const token = await this.loginService.loginByPassword(user); const token = await this.loginService.loginByPassword(user);
this.ctx.cookies.set('token', token.token, { this.ctx.cookies.set('token', token.token, {
maxAge: 1000 * token.expire, maxAge: 1000 * token.expire,
@ -43,6 +39,7 @@ export class LoginController extends BaseController {
if (settings.smsLoginEnabled !== true) { if (settings.smsLoginEnabled !== true) {
throw new Error('当前站点禁止短信验证码登录'); throw new Error('当前站点禁止短信验证码登录');
} }
checkComm();
const token = await this.loginService.loginBySmsCode({ const token = await this.loginService.loginBySmsCode({
phoneCode: body.phoneCode, phoneCode: body.phoneCode,

View File

@ -2,6 +2,7 @@ import { ALL, Body, Controller, Inject, Post, Provide } from '@midwayjs/core';
import { BaseController, Constants, SysSettingsService } from '@certd/lib-server'; import { BaseController, Constants, SysSettingsService } from '@certd/lib-server';
import { RegisterType, UserService } from '../../modules/sys/authority/service/user-service.js'; import { RegisterType, UserService } from '../../modules/sys/authority/service/user-service.js';
import { CodeService } from '../../modules/basic/service/code-service.js'; import { CodeService } from '../../modules/basic/service/code-service.js';
import { checkComm, checkPlus } from '@certd/plus-core';
export type RegisterReq = { export type RegisterReq = {
type: RegisterType; type: RegisterType;
@ -53,6 +54,7 @@ export class RegisterController extends BaseController {
if (sysPublicSettings.mobileRegisterEnabled === false) { if (sysPublicSettings.mobileRegisterEnabled === false) {
throw new Error('当前站点已禁止手机号注册功能'); throw new Error('当前站点已禁止手机号注册功能');
} }
checkComm();
//验证短信验证码 //验证短信验证码
await this.codeService.checkSmsCode({ await this.codeService.checkSmsCode({
mobile: body.mobile, mobile: body.mobile,
@ -71,6 +73,7 @@ export class RegisterController extends BaseController {
if (sysPublicSettings.emailRegisterEnabled === false) { if (sysPublicSettings.emailRegisterEnabled === false) {
throw new Error('当前站点已禁止Email注册功能'); throw new Error('当前站点已禁止Email注册功能');
} }
checkPlus();
this.codeService.checkEmailCode({ this.codeService.checkEmailCode({
email: body.email, email: body.email,
randomStr: body.randomStr, randomStr: body.randomStr,

View File

@ -2,6 +2,7 @@ import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/c
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 '../../modules/pipeline/service/access-service.js';
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';
/** /**
* *
@ -77,12 +78,13 @@ export class AccessController extends CrudController<AccessService> {
@Post('/accessTypeDict', { summary: Constants.per.authOnly }) @Post('/accessTypeDict', { summary: Constants.per.authOnly })
async getAccessTypeDict() { async getAccessTypeDict() {
const list = this.service.getDefineList(); const list: AccessDefine[] = this.service.getDefineList();
const dict = []; const dict = [];
for (const item of list) { for (const item of list) {
dict.push({ dict.push({
value: item.name, value: item.name,
label: item.title, label: item.title,
icon: item.icon,
}); });
} }
return this.ok(dict); return this.ok(dict);

View File

@ -2,6 +2,8 @@ import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/c
import { Constants, CrudController, ValidateException } from '@certd/lib-server'; import { Constants, CrudController, ValidateException } from '@certd/lib-server';
import { NotificationService } from '../../modules/pipeline/service/notification-service.js'; import { NotificationService } from '../../modules/pipeline/service/notification-service.js';
import { AuthService } from '../../modules/sys/authority/service/auth-service.js'; import { AuthService } from '../../modules/sys/authority/service/auth-service.js';
import { NotificationDefine } from '@certd/pipeline';
import { checkPlus } from '@certd/plus-core';
/** /**
* *
@ -43,12 +45,35 @@ export class NotificationController extends CrudController<NotificationService>
@Post('/add', { summary: Constants.per.authOnly }) @Post('/add', { summary: Constants.per.authOnly })
async add(@Body(ALL) bean) { async add(@Body(ALL) bean) {
bean.userId = this.getUserId(); bean.userId = this.getUserId();
const type = bean.type;
const define: NotificationDefine = this.service.getDefineByType(type);
if (!define) {
throw new ValidateException('通知类型不存在');
}
if (define.needPlus) {
checkPlus();
}
return super.add(bean); return super.add(bean);
} }
@Post('/update', { summary: Constants.per.authOnly }) @Post('/update', { summary: Constants.per.authOnly })
async update(@Body(ALL) bean) { async update(@Body(ALL) bean) {
await this.service.checkUserId(bean.id, this.getUserId()); await this.service.checkUserId(bean.id, this.getUserId());
const old = await this.service.info(bean.id);
if (!old) {
throw new ValidateException('通知配置不存在');
}
if (old.type !== bean.type) {
const type = bean.type;
const define: NotificationDefine = this.service.getDefineByType(type);
if (!define) {
throw new ValidateException('通知类型不存在');
}
if (define.needPlus) {
checkPlus();
}
}
return super.update(bean); return super.update(bean);
} }
@Post('/info', { summary: Constants.per.authOnly }) @Post('/info', { summary: Constants.per.authOnly })
@ -71,14 +96,19 @@ export class NotificationController extends CrudController<NotificationService>
@Post('/getTypeDict', { summary: Constants.per.authOnly }) @Post('/getTypeDict', { summary: Constants.per.authOnly })
async getTypeDict() { async getTypeDict() {
const list = this.service.getDefineList(); const list: any = this.service.getDefineList();
const dict = []; let dict = [];
for (const item of list) { for (const item of list) {
dict.push({ dict.push({
value: item.name, value: item.name,
label: item.title, label: item.title,
needPlus: item.needPlus ?? false,
icon: item.icon,
}); });
} }
dict = dict.sort(a => {
return a.needPlus ? 0 : -1;
});
return this.ok(dict); return this.ok(dict);
} }

View File

@ -1,11 +1,12 @@
import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core'; import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
import { CrudController, SysPrivateSettings, SysPublicSettings, SysSettingsEntity, SysSettingsService } from '@certd/lib-server'; import { CrudController, SysPrivateSettings, SysPublicSettings, SysSettingsEntity, SysSettingsService } from '@certd/lib-server';
import * as _ from 'lodash-es'; import * as _ from 'lodash-es';
import { merge } from 'lodash-es';
import { PipelineService } from '../../../modules/pipeline/service/pipeline-service.js'; import { PipelineService } from '../../../modules/pipeline/service/pipeline-service.js';
import { UserSettingsService } from '../../../modules/mine/service/user-settings-service.js'; import { UserSettingsService } from '../../../modules/mine/service/user-settings-service.js';
import { getEmailSettings } from '../../../modules/sys/settings/fix.js'; import { getEmailSettings } from '../../../modules/sys/settings/fix.js';
import { http, logger } from '@certd/basic'; import { http, logger, simpleNanoId } from '@certd/basic';
import { merge } from 'lodash-es'; import { CodeService } from '../../../modules/basic/service/code-service.js';
/** /**
*/ */
@ -18,6 +19,8 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
userSettingsService: UserSettingsService; userSettingsService: UserSettingsService;
@Inject() @Inject()
pipelineService: PipelineService; pipelineService: PipelineService;
@Inject()
codeService: CodeService;
getService() { getService() {
return this.service; return this.service;
@ -111,7 +114,7 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
return this.ok({}); return this.ok({});
} }
@Post('/testProxy', { summary: 'sys:settings:view' }) @Post('/testProxy', { summary: 'sys:settings:edit' })
async testProxy(@Body(ALL) body) { async testProxy(@Body(ALL) body) {
const google = 'https://www.google.com/'; const google = 'https://www.google.com/';
const baidu = 'https://www.baidu.com/'; const baidu = 'https://www.baidu.com/';
@ -148,4 +151,10 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
baidu: baiduRes, baidu: baiduRes,
}); });
} }
@Post('/testSms', { summary: 'sys:settings:edit' })
async testSms(@Body(ALL) body) {
await this.codeService.sendSmsCode(body.phoneCode, body.mobile, simpleNanoId());
return this.ok({});
}
} }

View File

@ -56,7 +56,7 @@ export class CodeService {
} }
/** /**
*/ */
async sendSmsCode(phoneCode, mobile, randomStr) { async sendSmsCode(phoneCode = '86', mobile: string, randomStr: string) {
console.assert(phoneCode != null && mobile != null, '手机号不能为空'); console.assert(phoneCode != null && mobile != null, '手机号不能为空');
console.assert(randomStr != null, 'randomStr不能为空'); console.assert(randomStr != null, 'randomStr不能为空');
@ -83,6 +83,7 @@ export class CodeService {
cache.set(key, smsCode, { cache.set(key, smsCode, {
ttl: 5 * 60 * 1000, //5分钟 ttl: 5 * 60 * 1000, //5分钟
}); });
return smsCode;
} }
/** /**
@ -102,6 +103,7 @@ export class CodeService {
cache.set(key, code, { cache.set(key, code, {
ttl: 5 * 60 * 1000, //5分钟 ttl: 5 * 60 * 1000, //5分钟
}); });
return code;
} }
/** /**

View File

@ -8,6 +8,7 @@ import { AliyunAccess, AliyunClient } from '@certd/plugin-lib';
title: '阿里云', title: '阿里云',
desc: '阿里云DNS解析提供商', desc: '阿里云DNS解析提供商',
accessType: 'aliyun', accessType: 'aliyun',
icon: 'ant-design:aliyun-outlined',
}) })
export class AliyunDnsProvider extends AbstractDnsProvider { export class AliyunDnsProvider extends AbstractDnsProvider {
client: any; client: any;

View File

@ -4,6 +4,7 @@ import { AccessInput, BaseAccess, IsAccess } from '@certd/pipeline';
name: 'CacheFly', name: 'CacheFly',
title: 'CacheFly', title: 'CacheFly',
desc: 'CacheFly', desc: 'CacheFly',
icon: 'clarity:plugin-line',
}) })
export class CacheflyAccess extends BaseAccess { export class CacheflyAccess extends BaseAccess {
@AccessInput({ @AccessInput({

View File

@ -7,6 +7,7 @@ import { IsAccess, AccessInput, BaseAccess } from '@certd/pipeline';
@IsAccess({ @IsAccess({
name: 'cloudflare', name: 'cloudflare',
title: 'cloudflare授权', title: 'cloudflare授权',
icon: 'simple-icons:cloudflare',
desc: '', desc: '',
}) })
export class CloudflareAccess extends BaseAccess { export class CloudflareAccess extends BaseAccess {

View File

@ -20,6 +20,7 @@ export type CloudflareRecord = {
name: 'cloudflare', name: 'cloudflare',
title: 'cloudflare', title: 'cloudflare',
desc: 'cloudflare dns provider', desc: 'cloudflare dns provider',
icon: 'simple-icons:cloudflare',
// 这里是对应的 cloudflare的access类型名称 // 这里是对应的 cloudflare的access类型名称
accessType: 'cloudflare', accessType: 'cloudflare',
}) })

View File

@ -8,6 +8,7 @@ import { isDev } from '../../utils/env.js';
@IsAccess({ @IsAccess({
name: 'demo', name: 'demo',
title: '授权插件示例', title: '授权插件示例',
icon: 'clarity:plugin-line',
desc: '', desc: '',
}) })
export class DemoAccess extends BaseAccess { export class DemoAccess extends BaseAccess {

View File

@ -14,6 +14,7 @@ type DemoRecord = {
name: 'demo', name: 'demo',
title: 'Dns提供商Demo', title: 'Dns提供商Demo',
desc: 'dns provider示例', desc: 'dns provider示例',
icon: 'clarity:plugin-line',
// 这里是对应的云平台的access类型名称 // 这里是对应的云平台的access类型名称
accessType: 'demo', accessType: 'demo',
}) })

View File

@ -8,6 +8,7 @@ import { IsAccess, AccessInput, BaseAccess } from '@certd/pipeline';
name: 'dogecloud', name: 'dogecloud',
title: '多吉云', title: '多吉云',
desc: '', desc: '',
icon: 'svg:icon-dogecloud',
}) })
export class DogeCloudAccess extends BaseAccess { export class DogeCloudAccess extends BaseAccess {
/** /**

View File

@ -4,6 +4,7 @@ import { AccessInput, BaseAccess, IsAccess } from '@certd/pipeline';
name: 'Gcore', name: 'Gcore',
title: 'Gcore', title: 'Gcore',
desc: 'Gcore', desc: 'Gcore',
icon: 'clarity:plugin-line',
}) })
export class GcoreAccess extends BaseAccess { export class GcoreAccess extends BaseAccess {
@AccessInput({ @AccessInput({

View File

@ -4,6 +4,7 @@ import { IsAccess, AccessInput, BaseAccess } from '@certd/pipeline';
name: 'huawei', name: 'huawei',
title: '华为云授权', title: '华为云授权',
desc: '', desc: '',
icon: 'svg:icon-huawei',
}) })
export class HuaweiAccess extends BaseAccess { export class HuaweiAccess extends BaseAccess {
@AccessInput({ @AccessInput({

View File

@ -14,6 +14,7 @@ export type SearchRecordOptions = {
title: '华为云', title: '华为云',
desc: '华为云DNS解析提供商', desc: '华为云DNS解析提供商',
accessType: 'huawei', accessType: 'huawei',
icon: 'svg:icon-huawei',
}) })
export class HuaweiDnsProvider extends AbstractDnsProvider { export class HuaweiDnsProvider extends AbstractDnsProvider {
client!: HuaweiYunClient; client!: HuaweiYunClient;

View File

@ -8,6 +8,7 @@ import { IsAccess, AccessInput, BaseAccess } from '@certd/pipeline';
name: 'namesilo', name: 'namesilo',
title: 'namesilo授权', title: 'namesilo授权',
desc: '', desc: '',
icon: 'simple-icons:namesilo',
}) })
export class NamesiloAccess extends BaseAccess { export class NamesiloAccess extends BaseAccess {
/** /**

View File

@ -12,6 +12,7 @@ export type NamesiloRecord = {
name: 'namesilo', name: 'namesilo',
title: 'namesilo', title: 'namesilo',
desc: 'namesilo dns provider', desc: 'namesilo dns provider',
icon: 'simple-icons:namesilo',
// 这里是对应的 cloudflare的access类型名称 // 这里是对应的 cloudflare的access类型名称
accessType: 'namesilo', accessType: 'namesilo',
}) })

View File

@ -4,6 +4,7 @@ import { BaseNotification, IsNotification, NotificationBody, NotificationInput }
name: 'anpush', name: 'anpush',
title: 'AnPush', title: 'AnPush',
desc: 'https://anpush.com', desc: 'https://anpush.com',
needPlus: true,
}) })
export class AnPushNotification extends BaseNotification { export class AnPushNotification extends BaseNotification {
@NotificationInput({ @NotificationInput({

View File

@ -19,6 +19,7 @@ import { BaseNotification, IsNotification, NotificationBody, NotificationInput }
name: 'bark', name: 'bark',
title: 'Bark 通知', title: 'Bark 通知',
desc: 'Bark 推送通知插件', desc: 'Bark 推送通知插件',
needPlus: true,
}) })
export class BarkNotification extends BaseNotification { export class BarkNotification extends BaseNotification {
@NotificationInput({ @NotificationInput({

View File

@ -4,6 +4,7 @@ import { BaseNotification, IsNotification, NotificationBody, NotificationInput }
name: 'discord', name: 'discord',
title: 'Discord 通知', title: 'Discord 通知',
desc: 'Discord 机器人通知', desc: 'Discord 机器人通知',
needPlus: true,
}) })
export class DiscordNotification extends BaseNotification { export class DiscordNotification extends BaseNotification {
@NotificationInput({ @NotificationInput({

View File

@ -4,6 +4,7 @@ import { BaseNotification, IsNotification, NotificationBody, NotificationInput }
name: 'iyuu', name: 'iyuu',
title: '爱语飞飞微信通知(iyuu)', title: '爱语飞飞微信通知(iyuu)',
desc: 'https://iyuu.cn/', desc: 'https://iyuu.cn/',
needPlus: true,
}) })
export class IyuuNotification extends BaseNotification { export class IyuuNotification extends BaseNotification {
@NotificationInput({ @NotificationInput({

View File

@ -4,6 +4,7 @@ import { BaseNotification, IsNotification, NotificationBody, NotificationInput }
name: 'qywx', name: 'qywx',
title: '企业微信通知', title: '企业微信通知',
desc: '企业微信群聊机器人通知', desc: '企业微信群聊机器人通知',
needPlus: true,
}) })
export class QywxNotification extends BaseNotification { export class QywxNotification extends BaseNotification {
@NotificationInput({ @NotificationInput({

View File

@ -4,6 +4,7 @@ import { BaseNotification, IsNotification, NotificationBody, NotificationInput }
name: 'serverchan', name: 'serverchan',
title: 'Server酱', title: 'Server酱',
desc: 'https://sct.ftqq.com/', desc: 'https://sct.ftqq.com/',
needPlus: true,
}) })
export class ServerChanNotification extends BaseNotification { export class ServerChanNotification extends BaseNotification {
@NotificationInput({ @NotificationInput({

View File

@ -4,6 +4,7 @@ import { BaseNotification, IsNotification, NotificationBody, NotificationInput }
name: 'slack', name: 'slack',
title: 'Slack通知', title: 'Slack通知',
desc: 'Slack消息推送通知', desc: 'Slack消息推送通知',
needPlus: true,
}) })
export class SlackNotification extends BaseNotification { export class SlackNotification extends BaseNotification {
@NotificationInput({ @NotificationInput({

View File

@ -4,6 +4,7 @@ import { BaseNotification, IsNotification, NotificationBody, NotificationInput }
name: 'telegram', name: 'telegram',
title: 'Telegram通知', title: 'Telegram通知',
desc: 'Telegram Bot推送通知', desc: 'Telegram Bot推送通知',
needPlus: true,
}) })
export class TelegramNotification extends BaseNotification { export class TelegramNotification extends BaseNotification {
@NotificationInput({ @NotificationInput({

View File

@ -4,6 +4,7 @@ import { BaseNotification, IsNotification, NotificationBody, NotificationInput }
name: 'vocechat', name: 'vocechat',
title: 'VoceChat通知', title: 'VoceChat通知',
desc: 'https://voce.chat', desc: 'https://voce.chat',
needPlus: true,
}) })
export class VoceChatNotification extends BaseNotification { export class VoceChatNotification extends BaseNotification {
@NotificationInput({ @NotificationInput({

View File

@ -5,6 +5,7 @@ import { IsAccess, AccessInput, BaseAccess } from '@certd/pipeline';
title: 'dnspod(已废弃)', title: 'dnspod(已废弃)',
desc: '腾讯云的域名解析接口已迁移到dnspod', desc: '腾讯云的域名解析接口已迁移到dnspod',
deprecated: 'dnspod已废弃请换成腾讯云', deprecated: 'dnspod已废弃请换成腾讯云',
icon: 'svg:icon-tencentcloud',
}) })
export class DnspodAccess extends BaseAccess { export class DnspodAccess extends BaseAccess {
@AccessInput({ @AccessInput({

View File

@ -10,6 +10,7 @@ import { DnspodAccess } from '../access/index.js';
desc: '已废弃,请尽快换成腾讯云类型', desc: '已废弃,请尽快换成腾讯云类型',
accessType: 'dnspod', accessType: 'dnspod',
deprecated: 'dnspod已废弃请换成腾讯云', deprecated: 'dnspod已废弃请换成腾讯云',
icon: 'svg:icon-tencentcloud',
}) })
export class DnspodDnsProvider extends AbstractDnsProvider { export class DnspodDnsProvider extends AbstractDnsProvider {
@Autowire() @Autowire()

View File

@ -8,6 +8,7 @@ import { TencentAccess } from '@certd/plugin-plus';
title: '腾讯云', title: '腾讯云',
desc: '腾讯云域名DNS解析提供者', desc: '腾讯云域名DNS解析提供者',
accessType: 'tencent', accessType: 'tencent',
icon: 'svg:icon-tencentcloud',
}) })
export class TencentDnsProvider extends AbstractDnsProvider { export class TencentDnsProvider extends AbstractDnsProvider {
@Autowire() @Autowire()

View File

@ -8,6 +8,7 @@ import { IsAccess, AccessInput, BaseAccess } from '@certd/pipeline';
name: 'west', name: 'west',
title: '西部数码授权', title: '西部数码授权',
desc: '', desc: '',
icon: 'tabler:map-west',
}) })
export class WestAccess extends BaseAccess { export class WestAccess extends BaseAccess {
/** /**

View File

@ -17,6 +17,7 @@ type westRecord = {
name: 'west', name: 'west',
title: '西部数码', title: '西部数码',
desc: 'west dns provider', desc: 'west dns provider',
icon: 'tabler:map-west',
// 这里是对应的云平台的access类型名称 // 这里是对应的云平台的access类型名称
accessType: 'west', accessType: 'west',
}) })

View File

@ -4,6 +4,7 @@ import { AccessInput, BaseAccess, IsAccess } from '@certd/pipeline';
name: 'woai', name: 'woai',
title: '我爱云授权', title: '我爱云授权',
desc: '我爱云CDN', desc: '我爱云CDN',
icon: 'clarity:plugin-line',
}) })
export class WoaiAccess extends BaseAccess { export class WoaiAccess extends BaseAccess {
@AccessInput({ @AccessInput({