mirror of
https://github.com/certd/certd.git
synced 2026-05-16 05:07:32 +08:00
Merge branch 'v2-translation' into v2-dev
# Conflicts: # packages/ui/certd-client/src/components/plugins/common/remote-select.vue # packages/ui/certd-client/src/router/source/modules/certd.ts # packages/ui/certd-client/src/views/certd/pipeline/certd-form/use.tsx # packages/ui/certd-client/src/views/certd/pipeline/crud.tsx
This commit is contained in:
@@ -2,8 +2,10 @@
|
||||
import { ref } from "vue";
|
||||
import { getCommonColumnDefine } from "/@/views/certd/access/common";
|
||||
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const { t } = useI18n();
|
||||
const { crudBinding } = crudExpose;
|
||||
const { props, ctx, api } = context;
|
||||
const lastResRef = ref();
|
||||
@@ -95,26 +97,26 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
},
|
||||
},
|
||||
name: {
|
||||
title: "名称",
|
||||
title: t("certd.name"),
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: ["text"],
|
||||
form: {
|
||||
rules: [{ required: true, message: "请填写名称" }],
|
||||
helper: "随便填,当多个相同类型的授权时,便于区分",
|
||||
rules: [{ required: true, message: t("certd.pleaseEnterName") }],
|
||||
helper: t("certd.nameHelper"),
|
||||
},
|
||||
column: {
|
||||
width: 200,
|
||||
},
|
||||
},
|
||||
from: {
|
||||
title: "级别",
|
||||
title: t("certd.level"),
|
||||
type: "dict-select",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "系统", value: "sys" },
|
||||
{ label: "用户", value: "user" },
|
||||
{ label: t("certd.system"), value: "sys" },
|
||||
{ label: t("certd.usera"), value: "user" },
|
||||
],
|
||||
}),
|
||||
search: {
|
||||
|
||||
@@ -15,13 +15,13 @@ export default defineComponent({
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: "aliyun"
|
||||
default: "aliyun",
|
||||
},
|
||||
from: {
|
||||
type: String, //user | sys
|
||||
default: "user"
|
||||
default: "user",
|
||||
},
|
||||
modelValue: {}
|
||||
modelValue: {},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
setup(props, ctx) {
|
||||
@@ -39,7 +39,7 @@ export default defineComponent({
|
||||
() => {
|
||||
return props.type;
|
||||
},
|
||||
(value) => {
|
||||
value => {
|
||||
console.log("access type changed:", value);
|
||||
onTypeChanged(value);
|
||||
}
|
||||
@@ -51,9 +51,9 @@ export default defineComponent({
|
||||
|
||||
return {
|
||||
crudBinding,
|
||||
crudRef
|
||||
crudRef,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, ref } from "vue";
|
||||
defineOptions({
|
||||
name: "SecretPlainGetter"
|
||||
name: "SecretPlainGetter",
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
|
||||
@@ -28,28 +28,28 @@ export default defineComponent({
|
||||
props: {
|
||||
modelValue: {
|
||||
type: [Number, String],
|
||||
default: null
|
||||
default: null,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: "aliyun"
|
||||
default: "aliyun",
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "请选择"
|
||||
default: "请选择",
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: "middle"
|
||||
default: "middle",
|
||||
},
|
||||
from: {
|
||||
type: String, //user | sys
|
||||
default: "user"
|
||||
default: "user",
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue", "change", "selectedChange"],
|
||||
setup(props, ctx) {
|
||||
@@ -92,7 +92,7 @@ export default defineComponent({
|
||||
() => {
|
||||
return props.modelValue;
|
||||
},
|
||||
async (value) => {
|
||||
async value => {
|
||||
selectedId.value = null;
|
||||
target.value = null;
|
||||
if (value == null) {
|
||||
@@ -101,7 +101,7 @@ export default defineComponent({
|
||||
await refreshTarget(value);
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -114,11 +114,11 @@ export default defineComponent({
|
||||
() => {
|
||||
return props.type;
|
||||
},
|
||||
async (value) => {
|
||||
async value => {
|
||||
await refreshProviderDefine(value);
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -134,7 +134,7 @@ export default defineComponent({
|
||||
console.log("choose ok:", selectedId.value);
|
||||
emitValue(selectedId.value);
|
||||
chooseForm.show = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -142,9 +142,9 @@ export default defineComponent({
|
||||
target,
|
||||
selectedId,
|
||||
providerDefine,
|
||||
chooseForm
|
||||
chooseForm,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
|
||||
@@ -95,9 +95,12 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
|
||||
},
|
||||
renderLabel(item: any) {
|
||||
return (
|
||||
<span class={"flex-o"}>
|
||||
<fs-icon icon={item.icon} class={"mr-5 fs-16 color-blue"} />
|
||||
{item.label}
|
||||
<span class={"flex flex-between items-center"}>
|
||||
<span class={"flex items-center"}>
|
||||
<fs-icon icon={item.icon} class={"mr-5 fs-16 color-blue"} />
|
||||
{item.label}
|
||||
</span>
|
||||
<span>{item.value}</span>
|
||||
</span>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<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>
|
||||
<fs-page>
|
||||
<template #header>
|
||||
<div class="title">
|
||||
{{ t("certd.authorizationManagement") }}
|
||||
<span class="sub">{{ t("certd.manageThirdPartyAuth") }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -15,25 +15,29 @@ import { defineComponent, onActivated, onMounted } from "vue";
|
||||
import { useFs } from "@fast-crud/fast-crud";
|
||||
import createCrudOptions from "./crud";
|
||||
import { createAccessApi } from "/@/views/certd/access/api";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
name: "AccessManager",
|
||||
setup() {
|
||||
const api = createAccessApi("user");
|
||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { api } });
|
||||
name: "AccessManager",
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
const api = createAccessApi("user");
|
||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { api } });
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
onActivated(() => {
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
onActivated(() => {
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
|
||||
return {
|
||||
crudBinding,
|
||||
crudRef,
|
||||
};
|
||||
},
|
||||
return {
|
||||
crudBinding,
|
||||
crudRef,
|
||||
t
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -8,271 +8,272 @@ import { useSettingStore } from "/@/store/settings";
|
||||
import { message } from "ant-design-vue";
|
||||
import CnameTip from "/@/components/plugins/cert/domains-verify-plan-editor/cname-tip.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 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 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;
|
||||
const dictRef = dict({
|
||||
data: [
|
||||
{ label: "待设置CNAME", value: "cname", color: "warning" },
|
||||
{ label: "验证中", value: "validating", color: "blue" },
|
||||
{ label: "验证成功", value: "valid", color: "green" },
|
||||
{ label: "验证失败", value: "failed", color: "red" },
|
||||
{ label: "验证超时", value: "timeout", color: "red" },
|
||||
],
|
||||
});
|
||||
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,
|
||||
},
|
||||
tabs: {
|
||||
name: "status",
|
||||
show: true,
|
||||
},
|
||||
rowHandle: {
|
||||
minWidth: 200,
|
||||
fixed: "right",
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
column: {
|
||||
width: 80,
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
domain: {
|
||||
title: "被代理域名",
|
||||
type: "text",
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
editForm: {
|
||||
component: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
hostRecord: {
|
||||
title: "主机记录",
|
||||
type: "text",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 250,
|
||||
cellRender: ({ value }) => {
|
||||
return <fs-copyable v-model={value} />;
|
||||
},
|
||||
},
|
||||
},
|
||||
recordValue: {
|
||||
title: "请设置CNAME",
|
||||
type: "copyable",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 500,
|
||||
},
|
||||
},
|
||||
cnameProviderId: {
|
||||
title: "CNAME服务",
|
||||
type: "dict-select",
|
||||
dict: dict({
|
||||
url: "/cname/provider/list",
|
||||
value: "id",
|
||||
label: "domain",
|
||||
}),
|
||||
form: {
|
||||
component: {
|
||||
onDictChange: ({ form, dict }: any) => {
|
||||
if (!form.cnameProviderId) {
|
||||
const list = dict.data.filter((item: any) => {
|
||||
return !item.disabled;
|
||||
});
|
||||
let item = list.find((item: any) => item.isDefault);
|
||||
if (!item && list.length > 0) {
|
||||
item = list[0];
|
||||
}
|
||||
if (item) {
|
||||
form.cnameProviderId = item.id;
|
||||
}
|
||||
}
|
||||
},
|
||||
renderLabel(item: any) {
|
||||
if (item.title) {
|
||||
return `${item.domain}<${item.title}>`;
|
||||
} else {
|
||||
return item.domain;
|
||||
}
|
||||
},
|
||||
},
|
||||
helper: {
|
||||
render() {
|
||||
const closeForm = () => {
|
||||
crudExpose.getFormWrapperRef().close();
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
默认提供公共CNAME服务,您还可以
|
||||
<router-link to={"/sys/cname/provider"} onClick={closeForm}>
|
||||
自定义CNAME服务
|
||||
</router-link>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
column: {
|
||||
width: 120,
|
||||
align: "center",
|
||||
cellRender({ value }) {
|
||||
if (value < 0) {
|
||||
return <a-tag color={"green"}>公共CNAME</a-tag>;
|
||||
} else {
|
||||
return <a-tag color={"blue"}>自定义CNAME</a-tag>;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
status: {
|
||||
title: "状态",
|
||||
type: "dict-select",
|
||||
dict: dictRef,
|
||||
addForm: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 120,
|
||||
align: "center",
|
||||
cellRender({ value, row }) {
|
||||
return (
|
||||
<div class={"flex flex-center"}>
|
||||
<fs-values-format modelValue={value} dict={dictRef}></fs-values-format>
|
||||
{row.error && (
|
||||
<a-tooltip title={row.error}>
|
||||
<fs-icon class={"ml-5 color-red"} icon="ion:warning-outline"></fs-icon>
|
||||
</a-tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
triggerValidate: {
|
||||
title: "验证",
|
||||
type: "text",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
conditionalRenderDisabled: true,
|
||||
width: 130,
|
||||
align: "center",
|
||||
cellRender({ row, value }) {
|
||||
if (row.status === "valid") {
|
||||
return "-";
|
||||
}
|
||||
const userStore = useUserStore();
|
||||
const settingStore = useSettingStore();
|
||||
const selectedRowKeys: Ref<any[]> = ref([]);
|
||||
context.selectedRowKeys = selectedRowKeys;
|
||||
const dictRef = dict({
|
||||
data: [
|
||||
{ label: t('certd.pending_cname_setup'), value: "cname", color: "warning" },
|
||||
{ label: t('certd.validating'), value: "validating", color: "blue" },
|
||||
{ label: t('certd.validation_successful'), value: "valid", color: "green" },
|
||||
{ label: t('certd.validation_failed'), value: "failed", color: "red" },
|
||||
{ label: t('certd.validation_timed_out'), value: "timeout", color: "red" },
|
||||
],
|
||||
});
|
||||
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,
|
||||
},
|
||||
tabs: {
|
||||
name: "status",
|
||||
show: true,
|
||||
},
|
||||
rowHandle: {
|
||||
minWidth: 200,
|
||||
fixed: "right",
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
column: {
|
||||
width: 80,
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
domain: {
|
||||
title: t('certd.proxied_domain'),
|
||||
type: "text",
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
editForm: {
|
||||
component: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
hostRecord: {
|
||||
title: t('certd.host_record'),
|
||||
type: "text",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 250,
|
||||
cellRender: ({ value }) => {
|
||||
return <fs-copyable v-model={value} />;
|
||||
},
|
||||
},
|
||||
},
|
||||
recordValue: {
|
||||
title: t('certd.please_set_cname'),
|
||||
type: "copyable",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 500,
|
||||
},
|
||||
},
|
||||
cnameProviderId: {
|
||||
title: t('certd.cname_service'),
|
||||
type: "dict-select",
|
||||
dict: dict({
|
||||
url: "/cname/provider/list",
|
||||
value: "id",
|
||||
label: "domain",
|
||||
}),
|
||||
form: {
|
||||
component: {
|
||||
onDictChange: ({ form, dict }: any) => {
|
||||
if (!form.cnameProviderId) {
|
||||
const list = dict.data.filter((item: any) => {
|
||||
return !item.disabled;
|
||||
});
|
||||
let item = list.find((item: any) => item.isDefault);
|
||||
if (!item && list.length > 0) {
|
||||
item = list[0];
|
||||
}
|
||||
if (item) {
|
||||
form.cnameProviderId = item.id;
|
||||
}
|
||||
}
|
||||
},
|
||||
renderLabel(item: any) {
|
||||
if (item.title) {
|
||||
return `${item.domain}<${item.title}>`;
|
||||
} else {
|
||||
return item.domain;
|
||||
}
|
||||
},
|
||||
},
|
||||
helper: {
|
||||
render() {
|
||||
const closeForm = () => {
|
||||
crudExpose.getFormWrapperRef().close();
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
{t('certd.default_public_cname')}
|
||||
<router-link to={"/sys/cname/provider"} onClick={closeForm}>
|
||||
{t('certd.customize_cname')}
|
||||
</router-link>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
column: {
|
||||
width: 120,
|
||||
align: "center",
|
||||
cellRender({ value }) {
|
||||
if (value < 0) {
|
||||
return <a-tag color={"green"}>{t('certd.public_cname')}</a-tag>;
|
||||
} else {
|
||||
return <a-tag color={"blue"}>{t('certd.custom_cname')}</a-tag>;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
status: {
|
||||
title: t('certd.fields.status'),
|
||||
type: "dict-select",
|
||||
dict: dictRef,
|
||||
addForm: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 120,
|
||||
align: "center",
|
||||
cellRender({ value, row }) {
|
||||
return (
|
||||
<div class={"flex flex-center"}>
|
||||
<fs-values-format modelValue={value} dict={dictRef}></fs-values-format>
|
||||
{row.error && (
|
||||
<a-tooltip title={row.error}>
|
||||
<fs-icon class={"ml-5 color-red"} icon="ion:warning-outline"></fs-icon>
|
||||
</a-tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
triggerValidate: {
|
||||
title: t('certd.validate'),
|
||||
type: "text",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
conditionalRenderDisabled: true,
|
||||
width: 130,
|
||||
align: "center",
|
||||
cellRender({ row, value }) {
|
||||
if (row.status === "valid") {
|
||||
return "-";
|
||||
}
|
||||
|
||||
async function doVerify() {
|
||||
row._validating_ = true;
|
||||
try {
|
||||
const res = await api.DoVerify(row.id);
|
||||
if (res === true) {
|
||||
message.success("验证成功");
|
||||
row.status = "valid";
|
||||
} else if (res === false) {
|
||||
message.success("验证超时");
|
||||
row.status = "timeout";
|
||||
} else {
|
||||
message.success("开始验证,请耐心等待");
|
||||
}
|
||||
await crudExpose.doRefresh();
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
message.error(e.message);
|
||||
} finally {
|
||||
row._validating_ = false;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<a-button onClick={doVerify} loading={row._validating_} size={"small"} type={"primary"}>
|
||||
点击验证
|
||||
</a-button>
|
||||
<CnameTip record={row} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
createTime: {
|
||||
title: "创建时间",
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 160,
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
updateTime: {
|
||||
title: "更新时间",
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
async function doVerify() {
|
||||
row._validating_ = true;
|
||||
try {
|
||||
const res = await api.DoVerify(row.id);
|
||||
if (res === true) {
|
||||
message.success(t('certd.validation_successful'));
|
||||
row.status = "valid";
|
||||
} else if (res === false) {
|
||||
message.success(t('certd.validation_timed_out'));
|
||||
row.status = "timeout";
|
||||
} else {
|
||||
message.success(t('certd.validation_started'));
|
||||
}
|
||||
await crudExpose.doRefresh();
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
message.error(e.message);
|
||||
} finally {
|
||||
row._validating_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<a-button onClick={doVerify} loading={row._validating_} size={"small"} type={"primary"}>
|
||||
{t('certd.click_to_validate')}
|
||||
</a-button>
|
||||
<CnameTip record={row} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
createTime: {
|
||||
title: t('certd.create_time'),
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 160,
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
updateTime: {
|
||||
title: t('certd.update_time'),
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,59 +1,66 @@
|
||||
<template>
|
||||
<fs-page class="page-cert">
|
||||
<template #header>
|
||||
<div class="title">
|
||||
CNAME记录管理
|
||||
<span class="sub">
|
||||
<a href="https://certd.docmirror.cn/guide/feature/cname/" target="_blank">CNAME功能原理及使用说明</a>
|
||||
</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>
|
||||
<fs-page class="page-cert">
|
||||
<template #header>
|
||||
<div class="title">
|
||||
{{ t('certd.cnameRecord') }}
|
||||
<span class="sub">
|
||||
<a href="https://certd.docmirror.cn/guide/feature/cname/" target="_blank">
|
||||
{{ t('certd.cname_feature_guide') }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
<template #pagination-left>
|
||||
<a-tooltip :title="t('certd.batch_delete')">
|
||||
<fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</fs-crud>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onActivated, 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";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
defineOptions({
|
||||
name: "CnameRecord",
|
||||
name: "CnameRecord",
|
||||
});
|
||||
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("请先勾选记录");
|
||||
}
|
||||
if (selectedRowKeys.value?.length > 0) {
|
||||
Modal.confirm({
|
||||
title: t('certd.confirm'),
|
||||
content: t('certd.confirm_delete_count', { count: selectedRowKeys.value.length }),
|
||||
async onOk() {
|
||||
await DeleteBatch(selectedRowKeys.value);
|
||||
message.info(t('certd.delete_successful'));
|
||||
crudExpose.doRefresh();
|
||||
selectedRowKeys.value = [];
|
||||
},
|
||||
});
|
||||
} else {
|
||||
message.error(t('certd.please_select_records'));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
onActivated(async () => {
|
||||
await crudExpose.doRefresh();
|
||||
await crudExpose.doRefresh();
|
||||
});
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
|
||||
@@ -8,209 +8,209 @@ import { useSettingStore } from "/@/store/settings";
|
||||
import { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status";
|
||||
|
||||
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 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 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;
|
||||
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,
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
search: {
|
||||
formItem: {
|
||||
labelCol: {
|
||||
style: {
|
||||
// width: "100px"
|
||||
},
|
||||
},
|
||||
wrapperCol: {
|
||||
style: {
|
||||
width: "50%",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
rowHandle: {
|
||||
minWidth: 200,
|
||||
fixed: "right",
|
||||
buttons: {
|
||||
edit: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
column: {
|
||||
width: 100,
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
userId: {
|
||||
title: "用户Id",
|
||||
type: "number",
|
||||
search: {
|
||||
show: computed(() => {
|
||||
return userStore.isAdmin && settingStore.sysPublic.managerOtherUserPipeline;
|
||||
}),
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
show: computed(() => {
|
||||
return userStore.isAdmin && settingStore.sysPublic.managerOtherUserPipeline;
|
||||
}),
|
||||
width: 100,
|
||||
},
|
||||
},
|
||||
pipelineId: {
|
||||
title: "流水线Id",
|
||||
type: "number",
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
},
|
||||
},
|
||||
pipelineTitle: {
|
||||
title: "流水线名称",
|
||||
type: "text",
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
column: {
|
||||
width: 300,
|
||||
tooltip: true,
|
||||
ellipsis: true,
|
||||
cellRender: ({ row, value }) => {
|
||||
return <router-link to={{ path: "/certd/pipeline/detail", query: { id: row.pipelineId, editMode: false, historyId: row.id } }}>{value}</router-link>;
|
||||
},
|
||||
},
|
||||
},
|
||||
triggerType: {
|
||||
title: "触发类型",
|
||||
type: "dict-select",
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
dict: dict({
|
||||
data: [
|
||||
{ value: "user", label: "手动执行" },
|
||||
{ value: "timer", label: "定时执行" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
show: false,
|
||||
value: "custom",
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 90,
|
||||
align: "center",
|
||||
show: true,
|
||||
component: {
|
||||
color: "auto",
|
||||
},
|
||||
},
|
||||
},
|
||||
status: {
|
||||
title: "状态",
|
||||
type: "dict-select",
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
dict: dict({
|
||||
data: statusUtil.getOptions(),
|
||||
}),
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 120,
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
createTime: {
|
||||
title: "创建时间",
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 160,
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
updateTime: {
|
||||
title: "更新时间",
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
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,
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
search: {
|
||||
formItem: {
|
||||
labelCol: {
|
||||
style: {
|
||||
// width: "100px"
|
||||
},
|
||||
},
|
||||
wrapperCol: {
|
||||
style: {
|
||||
width: "50%",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
rowHandle: {
|
||||
minWidth: 200,
|
||||
fixed: "right",
|
||||
buttons: {
|
||||
edit: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
column: {
|
||||
width: 100,
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
userId: {
|
||||
title: t("certd.fields.userId"),
|
||||
type: "number",
|
||||
search: {
|
||||
show: computed(() => {
|
||||
return userStore.isAdmin && settingStore.sysPublic.managerOtherUserPipeline;
|
||||
}),
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
show: computed(() => {
|
||||
return userStore.isAdmin && settingStore.sysPublic.managerOtherUserPipeline;
|
||||
}),
|
||||
width: 100,
|
||||
},
|
||||
},
|
||||
pipelineId: {
|
||||
title: t("certd.fields.pipelineId"),
|
||||
type: "number",
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
},
|
||||
},
|
||||
pipelineTitle: {
|
||||
title: t('certd.fields.pipelineName'),
|
||||
type: "text",
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
column: {
|
||||
width: 300,
|
||||
tooltip: true,
|
||||
ellipsis: true,
|
||||
cellRender: ({ row, value }) => {
|
||||
return <router-link to={{ path: "/certd/pipeline/detail", query: { id: row.pipelineId, editMode: false, historyId: row.id } }}>{value}</router-link>;
|
||||
},
|
||||
},
|
||||
},
|
||||
triggerType: {
|
||||
title: t("certd.fields.triggerType"),
|
||||
type: "dict-select",
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
dict: dict({
|
||||
data: [
|
||||
{ value: "user", label: t("certd.triggerTypes.manual") },
|
||||
{ value: "timer", label: t("certd.triggerTypes.timer") },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
show: false,
|
||||
value: "custom",
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 90,
|
||||
align: "center",
|
||||
show: true,
|
||||
component: {
|
||||
color: "auto",
|
||||
},
|
||||
},
|
||||
},
|
||||
status: {
|
||||
title: t("certd.fields.status"),
|
||||
type: "dict-select",
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
dict: dict({
|
||||
data: statusUtil.getOptions(),
|
||||
}),
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 120,
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
createTime: {
|
||||
title: t("certd.fields.createTime"),
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 160,
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
updateTime: {
|
||||
title: t("certd.fields.updateTime"),
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<fs-page class="page-cert">
|
||||
<template #header>
|
||||
<div class="title">流水线执行记录</div>
|
||||
<div class="title">{{ t("certd.pipelineExecutionRecords") }}</div>
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
<template #pagination-left>
|
||||
<a-tooltip title="批量删除">
|
||||
<a-tooltip :title="t('certd.batchDelete')">
|
||||
<fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
@@ -13,12 +13,16 @@
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onActivated, 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";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
defineOptions({
|
||||
name: "PipelineHistory",
|
||||
@@ -29,17 +33,17 @@ const selectedRowKeys = context.selectedRowKeys;
|
||||
const handleBatchDelete = () => {
|
||||
if (selectedRowKeys.value?.length > 0) {
|
||||
Modal.confirm({
|
||||
title: "确认",
|
||||
content: `确定要批量删除这${selectedRowKeys.value.length}条记录吗`,
|
||||
title: t("certd.confirm"),
|
||||
content: t("certd.confirmBatchDeleteContent", { count: selectedRowKeys.value.length }),
|
||||
async onOk() {
|
||||
await DeleteBatch(selectedRowKeys.value);
|
||||
message.info("删除成功");
|
||||
message.info(t("certd.deleteSuccess"));
|
||||
crudExpose.doRefresh();
|
||||
selectedRowKeys.value = [];
|
||||
},
|
||||
});
|
||||
} else {
|
||||
message.error("请先勾选记录");
|
||||
message.error(t("certd.pleaseSelectRecords"));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,105 +1,114 @@
|
||||
<template>
|
||||
<a-button v-if="showButton" type="primary" @click="open">修改密码</a-button>
|
||||
<a-button v-if="showButton" type="primary" @click="open">
|
||||
{{ $t("authentication.changePasswordButton") }}
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
import { CrudOptions, useColumns, useFormWrapper } from "@fast-crud/fast-crud";
|
||||
import * as api from "/@/views/certd/mine/api";
|
||||
import { notification } from "ant-design-vue";
|
||||
import { useUserStore } from "/@/store/user";
|
||||
|
||||
defineProps<{
|
||||
showButton: boolean;
|
||||
showButton: boolean;
|
||||
}>();
|
||||
|
||||
let passwordFormRef = ref();
|
||||
|
||||
const validatePass1 = async (rule: any, value: any) => {
|
||||
if (value === "") {
|
||||
throw new Error("请输入密码");
|
||||
}
|
||||
const formData = passwordFormRef.value.getFormData();
|
||||
if (formData.confirmNewPassword !== "") {
|
||||
passwordFormRef.value.formRef.formRef.validateFields(["confirmNewPassword"]);
|
||||
}
|
||||
if (formData.password === formData.newPassword) {
|
||||
throw new Error("新密码不能和旧密码相同");
|
||||
}
|
||||
if (value === "") {
|
||||
throw new Error(t("authentication.enterPassword"));
|
||||
}
|
||||
const formData = passwordFormRef.value.getFormData();
|
||||
if (formData.confirmNewPassword !== "") {
|
||||
passwordFormRef.value.formRef.formRef.validateFields(["confirmNewPassword"]);
|
||||
}
|
||||
if (formData.password === formData.newPassword) {
|
||||
throw new Error(t("authentication.newPasswordNotSameOld"));
|
||||
}
|
||||
};
|
||||
const validatePass2 = async (rule: any, value: any) => {
|
||||
if (value === "") {
|
||||
throw new Error("请再次输入密码");
|
||||
} else if (value !== passwordFormRef.value.getFormData().newPassword) {
|
||||
throw new Error("两次输入密码不一致!");
|
||||
}
|
||||
if (value === "") {
|
||||
throw new Error(t("authentication.enterPasswordAgain"));
|
||||
} else if (value !== passwordFormRef.value.getFormData().newPassword) {
|
||||
throw new Error(t("authentication.passwordsNotMatch"));
|
||||
}
|
||||
};
|
||||
|
||||
const userStore = useUserStore();
|
||||
const { openDialog } = useFormWrapper();
|
||||
const { buildFormOptions } = useColumns();
|
||||
const passwordFormOptions: CrudOptions = {
|
||||
form: {
|
||||
col: {
|
||||
span: 24,
|
||||
},
|
||||
wrapper: {
|
||||
title: "修改密码",
|
||||
width: "500px",
|
||||
},
|
||||
async doSubmit({ form }) {
|
||||
await api.changePassword(form);
|
||||
//重新加载用户信息
|
||||
await userStore.loadUserInfo();
|
||||
},
|
||||
async afterSubmit() {
|
||||
notification.success({ message: "修改成功" });
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
password: {
|
||||
title: "旧密码",
|
||||
type: "password",
|
||||
form: {
|
||||
rules: [{ required: true, message: "请输入旧密码" }],
|
||||
},
|
||||
},
|
||||
newPassword: {
|
||||
title: "新密码",
|
||||
type: "password",
|
||||
form: {
|
||||
rules: [
|
||||
{ required: true, message: "请输入确认密码" },
|
||||
//@ts-ignore
|
||||
{ validator: validatePass1, trigger: "blur" },
|
||||
],
|
||||
},
|
||||
},
|
||||
confirmNewPassword: {
|
||||
title: "确认新密码",
|
||||
type: "password",
|
||||
form: {
|
||||
rules: [
|
||||
{ required: true, message: "请输入确认密码" },
|
||||
//@ts-ignore
|
||||
{ validator: validatePass2, trigger: "blur" },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
form: {
|
||||
col: {
|
||||
span: 24,
|
||||
},
|
||||
wrapper: {
|
||||
title: t("authentication.title"),
|
||||
width: "500px",
|
||||
},
|
||||
async doSubmit({ form }) {
|
||||
await api.changePassword(form);
|
||||
//重新加载用户信息
|
||||
await userStore.loadUserInfo();
|
||||
},
|
||||
async afterSubmit() {
|
||||
notification.success({ message: t("authentication.successMessage") });
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
password: {
|
||||
title: t("authentication.oldPassword"),
|
||||
type: "password",
|
||||
form: {
|
||||
rules: [{ required: true, message: t("authentication.oldPasswordRequired") }],
|
||||
},
|
||||
},
|
||||
newPassword: {
|
||||
title: t("authentication.newPassword"),
|
||||
type: "password",
|
||||
form: {
|
||||
rules: [
|
||||
{ required: true, message: t("authentication.newPasswordRequired") },
|
||||
//@ts-ignore
|
||||
{ validator: validatePass1, trigger: "blur" },
|
||||
],
|
||||
},
|
||||
},
|
||||
confirmNewPassword: {
|
||||
title: t("authentication.confirmNewPassword"),
|
||||
type: "password",
|
||||
form: {
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: t("authentication.confirmNewPasswordRequired"),
|
||||
},
|
||||
//@ts-ignore
|
||||
{ validator: validatePass2, trigger: "blur" },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async function open(opts: { password: "" }) {
|
||||
const formOptions = buildFormOptions(passwordFormOptions);
|
||||
formOptions.newInstance = true; //新实例打开
|
||||
passwordFormRef.value = await openDialog(formOptions);
|
||||
passwordFormRef.value.setFormData({
|
||||
password: opts.password,
|
||||
});
|
||||
console.log(passwordFormRef.value);
|
||||
const formOptions = buildFormOptions(passwordFormOptions);
|
||||
formOptions.newInstance = true; //新实例打开
|
||||
passwordFormRef.value = await openDialog(formOptions);
|
||||
passwordFormRef.value.setFormData({
|
||||
password: opts.password,
|
||||
});
|
||||
console.log(passwordFormRef.value);
|
||||
}
|
||||
|
||||
const scope = ref({
|
||||
open: open,
|
||||
open: open,
|
||||
});
|
||||
|
||||
defineExpose(scope.value);
|
||||
|
||||
@@ -1,73 +1,82 @@
|
||||
<template>
|
||||
<fs-page class="page-user-settings page-two-factor">
|
||||
<template #header>
|
||||
<div class="title">认证安全设置</div>
|
||||
</template>
|
||||
<div class="user-settings-form settings-form">
|
||||
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off">
|
||||
<a-form-item label="2FA多重验证登录" :name="['authenticator', 'enabled']">
|
||||
<div class="flex mt-5">
|
||||
<a-switch v-model:checked="formState.authenticator.enabled" :disabled="!settingsStore.isPlus" @change="onAuthenticatorEnabledChanged" />
|
||||
<fs-page class="page-user-settings page-two-factor">
|
||||
<template #header>
|
||||
<div class="title">{{ t("certd.securitySettings") }}</div>
|
||||
</template>
|
||||
|
||||
<a-button
|
||||
v-if="formState.authenticator.enabled && formState.authenticator.verified"
|
||||
:disabled="authenticatorOpenRef || !settingsStore.isPlus"
|
||||
size="small"
|
||||
class="ml-5"
|
||||
type="primary"
|
||||
@click="authenticatorForm.open = true"
|
||||
>
|
||||
重新绑定
|
||||
</a-button>
|
||||
<div class="user-settings-form settings-form">
|
||||
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }"
|
||||
autocomplete="off">
|
||||
<a-form-item :label="t('certd.twoFactorAuth')" :name="['authenticator', 'enabled']">
|
||||
<div class="flex mt-5">
|
||||
<a-switch v-model:checked="formState.authenticator.enabled" :disabled="!settingsStore.isPlus"
|
||||
@change="onAuthenticatorEnabledChanged" />
|
||||
|
||||
<vip-button class="ml-5" mode="button"></vip-button>
|
||||
</div>
|
||||
<a-button v-if="formState.authenticator.enabled && formState.authenticator.verified"
|
||||
:disabled="authenticatorOpenRef || !settingsStore.isPlus" size="small" class="ml-5"
|
||||
type="primary" @click="authenticatorForm.open = true">
|
||||
{{ t('certd.rebind') }}
|
||||
</a-button>
|
||||
|
||||
<div class="helper">是否开启多重验证登录</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="authenticatorOpenRef" label="绑定设备" class="authenticator-config">
|
||||
<h3 class="font-bold m-5">1. 安装任意一款支持Authenticator的验证APP,比如:</h3>
|
||||
<div class="ml-20">
|
||||
<ul>
|
||||
<li>
|
||||
<a-tooltip title="如果报没有找到谷歌服务的错误,您可以安装KK谷歌助手">
|
||||
<a href="https://appgallery.huawei.com/app/C100262999" target="_blank"> Microsoft Authenticator</a>
|
||||
</a-tooltip>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://sj.qq.com/appdetail/com.tencent.authenticator" target="_blank">腾讯身份验证器</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.synology.cn/zh-cn/dsm/feature/authentication" target="_blank">群晖身份验证器</a>
|
||||
</li>
|
||||
<li>
|
||||
<a-tooltip title="如果报没有找到谷歌服务的错误,您可以安装KK谷歌助手">
|
||||
<a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2" target="_blank">Google Authenticator</a>
|
||||
</a-tooltip>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://play.google.com/store/apps/details?id=com.authy.authy" target="_blank">Authy</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h3 class="font-bold m-10">2. 扫描二维码添加账号</h3>
|
||||
<div v-if="authenticatorForm.qrcodeSrc" class="qrcode">
|
||||
<div class="ml-20">
|
||||
<img class="full-w" :src="authenticatorForm.qrcodeSrc" />
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="font-bold m-10">3. 输入验证码</h3>
|
||||
<div class="ml-20">
|
||||
<a-input v-model:value="authenticatorForm.verifyCode" placeholder="请输入验证码" />
|
||||
</div>
|
||||
<div class="ml-20 flex mt-10">
|
||||
<loading-button type="primary" html-type="button" :click="doAuthenticatorSave">确认</loading-button>
|
||||
<a-button class="ml-1" @click="authenticatorForm.open = false">取消</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</fs-page>
|
||||
<vip-button class="ml-5" mode="button"></vip-button>
|
||||
</div>
|
||||
|
||||
<div class="helper">{{ t('certd.twoFactorAuthHelper') }}</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item v-if="authenticatorOpenRef" :label="t('certd.bindDevice')" class="authenticator-config">
|
||||
<h3 class="font-bold m-5">{{ t('certd.step1') }}</h3>
|
||||
<div class="ml-20">
|
||||
<ul>
|
||||
<li>
|
||||
<a-tooltip :title="t('certd.tooltipGoogleServiceError')">
|
||||
<a href="https://appgallery.huawei.com/app/C100262999" target="_blank">Microsoft
|
||||
Authenticator</a>
|
||||
</a-tooltip>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://sj.qq.com/appdetail/com.tencent.authenticator"
|
||||
target="_blank">腾讯身份验证器</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.synology.cn/zh-cn/dsm/feature/authentication"
|
||||
target="_blank">群晖身份验证器</a>
|
||||
</li>
|
||||
<li>
|
||||
<a-tooltip :title="t('certd.tooltipGoogleServiceError')">
|
||||
<a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2"
|
||||
target="_blank">Google Authenticator</a>
|
||||
</a-tooltip>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://play.google.com/store/apps/details?id=com.authy.authy"
|
||||
target="_blank">Authy</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h3 class="font-bold m-10">{{ t('certd.step2') }}</h3>
|
||||
<div v-if="authenticatorForm.qrcodeSrc" class="qrcode">
|
||||
<div class="ml-20">
|
||||
<img class="full-w" :src="authenticatorForm.qrcodeSrc" />
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="font-bold m-10">{{ t('certd.step3') }}</h3>
|
||||
<div class="ml-20">
|
||||
<a-input v-model:value="authenticatorForm.verifyCode"
|
||||
:placeholder="t('certd.inputVerifyCode')" />
|
||||
</div>
|
||||
<div class="ml-20 flex mt-10">
|
||||
<loading-button type="primary" html-type="button" :click="doAuthenticatorSave">{{
|
||||
t('certd.confirm')
|
||||
}}</loading-button>
|
||||
<a-button class="ml-1" @click="authenticatorForm.open = false">{{ t('certd.cancel')
|
||||
}}</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
</a-form>
|
||||
</div>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
@@ -77,87 +86,92 @@ import { UserTwoFactorSetting } from "./api";
|
||||
import { Modal, notification } from "ant-design-vue";
|
||||
import { merge } from "lodash-es";
|
||||
import { useSettingStore } from "/@/store/settings";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
const settingsStore = useSettingStore();
|
||||
defineOptions({
|
||||
name: "UserSecurity",
|
||||
name: "UserSecurity",
|
||||
});
|
||||
|
||||
const formState = reactive<Partial<UserTwoFactorSetting>>({
|
||||
authenticator: {
|
||||
enabled: false,
|
||||
verified: false,
|
||||
},
|
||||
authenticator: {
|
||||
enabled: false,
|
||||
verified: false,
|
||||
},
|
||||
});
|
||||
|
||||
const authenticatorForm = reactive({
|
||||
qrcodeSrc: "",
|
||||
verifyCode: "",
|
||||
open: false,
|
||||
qrcodeSrc: "",
|
||||
verifyCode: "",
|
||||
open: false,
|
||||
});
|
||||
|
||||
const authenticatorOpenRef = computed(() => {
|
||||
return formState.authenticator.enabled && (authenticatorForm.open || !formState.authenticator.verified);
|
||||
return formState.authenticator.enabled && (authenticatorForm.open || !formState.authenticator.verified);
|
||||
});
|
||||
watch(
|
||||
() => {
|
||||
return authenticatorOpenRef.value;
|
||||
},
|
||||
async open => {
|
||||
if (open) {
|
||||
//base64 转图片
|
||||
authenticatorForm.qrcodeSrc = await api.TwoFactorAuthenticatorGet();
|
||||
} else {
|
||||
authenticatorForm.qrcodeSrc = "";
|
||||
authenticatorForm.verifyCode = "";
|
||||
}
|
||||
}
|
||||
() => {
|
||||
return authenticatorOpenRef.value;
|
||||
},
|
||||
async open => {
|
||||
if (open) {
|
||||
//base64 转图片
|
||||
authenticatorForm.qrcodeSrc = await api.TwoFactorAuthenticatorGet();
|
||||
} else {
|
||||
authenticatorForm.qrcodeSrc = "";
|
||||
authenticatorForm.verifyCode = "";
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
async function loadUserSettings() {
|
||||
const data: any = await api.TwoFactorSettingsGet();
|
||||
merge(formState, data);
|
||||
const data: any = await api.TwoFactorSettingsGet();
|
||||
merge(formState, data);
|
||||
}
|
||||
|
||||
loadUserSettings();
|
||||
const doAuthenticatorSave = async (form: any) => {
|
||||
await api.TwoFactorAuthenticatorSave({
|
||||
verifyCode: authenticatorForm.verifyCode,
|
||||
});
|
||||
notification.success({
|
||||
message: "保存成功",
|
||||
});
|
||||
authenticatorForm.open = false;
|
||||
formState.authenticator.verified = true;
|
||||
await api.TwoFactorAuthenticatorSave({
|
||||
verifyCode: authenticatorForm.verifyCode,
|
||||
});
|
||||
notification.success({
|
||||
message: t("certd.saveSuccess"),
|
||||
});
|
||||
authenticatorForm.open = false;
|
||||
formState.authenticator.verified = true;
|
||||
};
|
||||
|
||||
|
||||
function onAuthenticatorEnabledChanged(value: any) {
|
||||
if (!value) {
|
||||
//要关闭
|
||||
if (formState.authenticator.verified) {
|
||||
Modal.confirm({
|
||||
title: "确认",
|
||||
content: `确定要关闭多重验证登录吗?`,
|
||||
async onOk() {
|
||||
await api.TwoFactorAuthenticatorOff();
|
||||
notification.success({
|
||||
message: "关闭成功",
|
||||
});
|
||||
loadUserSettings();
|
||||
},
|
||||
onCancel() {
|
||||
formState.authenticator.enabled = true;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!value) {
|
||||
//要关闭
|
||||
if (formState.authenticator.verified) {
|
||||
Modal.confirm({
|
||||
title: t("certd.confirm"),
|
||||
content: t("certd.confirmDisable2FA"),
|
||||
async onOk() {
|
||||
await api.TwoFactorAuthenticatorOff();
|
||||
notification.success({
|
||||
message: t("certd.disabledSuccess"),
|
||||
});
|
||||
loadUserSettings();
|
||||
},
|
||||
onCancel() {
|
||||
formState.authenticator.enabled = true;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.page-user-settings {
|
||||
.user-settings-form {
|
||||
width: 600px;
|
||||
margin: 20px;
|
||||
}
|
||||
.user-settings-form {
|
||||
width: 600px;
|
||||
margin: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,41 +1,50 @@
|
||||
<template>
|
||||
<fs-page class="page-user-profile">
|
||||
<template #header>
|
||||
<div class="title">我的信息</div>
|
||||
</template>
|
||||
<div class="p-10">
|
||||
<a-descriptions title="" bordered>
|
||||
<a-descriptions-item label="用户名">{{ userInfo.username }}</a-descriptions-item>
|
||||
<a-descriptions-item label="头像">
|
||||
<a-avatar v-if="userInfo.avatar" size="large" :src="'api/basic/file/download?&key=' + userInfo.avatar" style="background-color: #eee"> </a-avatar>
|
||||
<a-avatar v-else size="large" style="background-color: #00b4f5">
|
||||
{{ userInfo.username }}
|
||||
</a-avatar>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="昵称">{{ userInfo.nickName }}</a-descriptions-item>
|
||||
<a-descriptions-item label="邮箱">{{ userInfo.email }}</a-descriptions-item>
|
||||
<a-descriptions-item label="手机号">{{ userInfo.phoneCode }}{{ userInfo.mobile }}</a-descriptions-item>
|
||||
<a-descriptions-item label="修改密码">
|
||||
<change-password-button :show-button="true"> </change-password-button>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</fs-page>
|
||||
<fs-page class="page-user-profile">
|
||||
<template #header>
|
||||
<div class="title">{{ t("certd.myInfo") }}</div>
|
||||
</template>
|
||||
<div class="p-10">
|
||||
<a-descriptions title="" bordered :column="1">
|
||||
<a-descriptions-item :label="t('authentication.username')">{{ userInfo.username }}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('authentication.avatar')">
|
||||
<a-avatar v-if="userInfo.avatar" size="large"
|
||||
:src="'api/basic/file/download?&key=' + userInfo.avatar" style="background-color: #eee">
|
||||
</a-avatar>
|
||||
<a-avatar v-else size="large" style="background-color: #00b4f5">
|
||||
{{ userInfo.username }}
|
||||
</a-avatar>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('authentication.nickName')">{{ userInfo.nickName }}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('authentication.email')">{{ userInfo.email }}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('authentication.phoneNumber')">{{ userInfo.phoneCode }}{{ userInfo.mobile
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('authentication.changePassword')">
|
||||
<change-password-button :show-button="true"> </change-password-button>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
</div>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as api from "./api";
|
||||
import { Ref, ref } from "vue";
|
||||
import ChangePasswordButton from "/@/views/certd/mine/change-password-button.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
defineOptions({
|
||||
name: "UserProfile"
|
||||
name: "UserProfile"
|
||||
});
|
||||
|
||||
const userInfo: Ref = ref({});
|
||||
|
||||
const getUserInfo = async () => {
|
||||
userInfo.value = await api.getMineInfo();
|
||||
userInfo.value = await api.getMineInfo();
|
||||
};
|
||||
getUserInfo();
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// @ts-ignore
|
||||
import { useI18n } from "vue-i18n";
|
||||
//
|
||||
import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, useFormWrapper, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||
import { certInfoApi } from "./api";
|
||||
import dayjs from "dayjs";
|
||||
@@ -10,285 +11,288 @@ import CertView from "/@/views/certd/pipeline/cert-view.vue";
|
||||
import { useCertUpload } from "/@/views/certd/pipeline/cert-upload/use";
|
||||
|
||||
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const api = certInfoApi;
|
||||
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 { t } = useI18n();
|
||||
const api = certInfoApi;
|
||||
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 { openCrudFormDialog } = useFormWrapper();
|
||||
const router = useRouter();
|
||||
const addRequest = async (req: AddReq) => {
|
||||
const { form } = req;
|
||||
const res = await api.AddObj(form);
|
||||
return res;
|
||||
};
|
||||
const { openCrudFormDialog } = useFormWrapper();
|
||||
const router = useRouter();
|
||||
|
||||
const model = useModal();
|
||||
const viewCert = async (row: any) => {
|
||||
const cert = await api.GetCert(row.id);
|
||||
if (!cert) {
|
||||
notification.error({ message: "证书还未生成,请先运行流水线" });
|
||||
return;
|
||||
}
|
||||
const model = useModal();
|
||||
const viewCert = async (row: any) => {
|
||||
const cert = await api.GetCert(row.id);
|
||||
if (!cert) {
|
||||
notification.error({ message: t("certd.certificateNotGenerated") });
|
||||
return;
|
||||
}
|
||||
|
||||
model.success({
|
||||
title: "查看证书",
|
||||
maskClosable: true,
|
||||
okText: "关闭",
|
||||
width: 800,
|
||||
content: () => {
|
||||
return <CertView cert={cert}></CertView>;
|
||||
},
|
||||
});
|
||||
};
|
||||
model.success({
|
||||
title: t("certd.modal.viewCertificateTitle"),
|
||||
maskClosable: true,
|
||||
okText: t("certd.modal.close"),
|
||||
width: 800,
|
||||
content: () => {
|
||||
return <CertView cert={cert}></CertView>;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const { openUploadCreateDialog, openUpdateCertDialog } = useCertUpload();
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
form: {
|
||||
labelCol: {
|
||||
//固定label宽度
|
||||
span: null,
|
||||
style: {
|
||||
width: "100px",
|
||||
},
|
||||
},
|
||||
col: {
|
||||
span: 22,
|
||||
},
|
||||
wrapper: {
|
||||
width: 600,
|
||||
},
|
||||
},
|
||||
actionbar: {
|
||||
show: true,
|
||||
buttons: {
|
||||
add: {
|
||||
text: "上传自定义证书",
|
||||
type: "primary",
|
||||
show: false,
|
||||
async click() {
|
||||
await openUploadCreateDialog();
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tabs: {
|
||||
name: "fromType",
|
||||
show: true,
|
||||
},
|
||||
rowHandle: {
|
||||
width: 100,
|
||||
fixed: "right",
|
||||
buttons: {
|
||||
view: { show: false },
|
||||
viewCert: {
|
||||
order: 3,
|
||||
title: "查看证书",
|
||||
type: "link",
|
||||
icon: "ph:certificate",
|
||||
async click({ row }) {
|
||||
await viewCert(row);
|
||||
},
|
||||
},
|
||||
copy: { show: false },
|
||||
edit: { show: false },
|
||||
remove: {
|
||||
order: 10,
|
||||
show: false,
|
||||
},
|
||||
download: {
|
||||
order: 9,
|
||||
title: "下载证书",
|
||||
type: "link",
|
||||
icon: "ant-design:download-outlined",
|
||||
async click({ row }) {
|
||||
if (!row.certFile) {
|
||||
notification.error({ message: "证书还未生成,请先运行流水线" });
|
||||
return;
|
||||
}
|
||||
window.open("/api/monitor/cert/download?id=" + row.id);
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
editable: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
fromType: {
|
||||
title: "来源",
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: "dict-select",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "流水线", value: "pipeline" },
|
||||
{ label: "手动上传", value: "upload" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
sorter: true,
|
||||
component: {
|
||||
color: "auto",
|
||||
},
|
||||
conditionalRender: false,
|
||||
},
|
||||
valueBuilder({ value, row, key }) {
|
||||
if (!value) {
|
||||
row[key] = "pipeline";
|
||||
}
|
||||
},
|
||||
},
|
||||
domains: {
|
||||
title: "域名",
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: "text",
|
||||
form: {
|
||||
rules: [{ required: true, message: "请输入域名" }],
|
||||
},
|
||||
column: {
|
||||
width: 450,
|
||||
sorter: true,
|
||||
component: {
|
||||
name: "fs-values-format",
|
||||
color: "auto",
|
||||
},
|
||||
},
|
||||
},
|
||||
domainCount: {
|
||||
title: "域名数量",
|
||||
type: "number",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 120,
|
||||
sorter: true,
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
expiresLeft: {
|
||||
title: "有效天数",
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "date",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
conditionalRender: false,
|
||||
cellRender({ row }) {
|
||||
const value = row.expiresTime;
|
||||
if (!value) {
|
||||
return "-";
|
||||
}
|
||||
const expireDate = dayjs(value).format("YYYY-MM-DD");
|
||||
const leftDays = dayjs(value).diff(dayjs(), "day");
|
||||
const color = leftDays < 20 ? "red" : "#389e0d";
|
||||
const percent = (leftDays / 90) * 100;
|
||||
return <a-progress title={expireDate + "过期"} percent={percent} strokeColor={color} format={(percent: number) => `${leftDays}天`} />;
|
||||
},
|
||||
},
|
||||
},
|
||||
expiresTime: {
|
||||
title: "过期时间",
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
certProvider: {
|
||||
title: "证书颁发机构",
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "text",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 200,
|
||||
},
|
||||
},
|
||||
applyTime: {
|
||||
title: "申请时间",
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
"pipeline.title": {
|
||||
title: "关联流水线",
|
||||
search: { show: false },
|
||||
type: "link",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 350,
|
||||
sorter: true,
|
||||
component: {
|
||||
on: {
|
||||
onClick({ row }) {
|
||||
router.push({ path: "/certd/pipeline/detail", query: { id: row.pipelineId, editMode: "false" } });
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const { openUploadCreateDialog, openUpdateCertDialog } = useCertUpload();
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
form: {
|
||||
labelCol: {
|
||||
//固定label宽度
|
||||
span: null,
|
||||
style: {
|
||||
width: "100px",
|
||||
},
|
||||
},
|
||||
col: {
|
||||
span: 22,
|
||||
},
|
||||
wrapper: {
|
||||
width: 600,
|
||||
},
|
||||
},
|
||||
actionbar: {
|
||||
show: true,
|
||||
buttons: {
|
||||
add: {
|
||||
text: t('certd.uploadCustomCert'),
|
||||
type: "primary",
|
||||
show: false,
|
||||
async click() {
|
||||
await openUploadCreateDialog();
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tabs: {
|
||||
name: "fromType",
|
||||
show: true,
|
||||
},
|
||||
rowHandle: {
|
||||
width: 100,
|
||||
fixed: "right",
|
||||
buttons: {
|
||||
view: { show: false },
|
||||
viewCert: {
|
||||
order: 3,
|
||||
title: t("certd.viewCert.title"),
|
||||
type: "link",
|
||||
icon: "ph:certificate",
|
||||
async click({ row }) {
|
||||
await viewCert(row);
|
||||
},
|
||||
},
|
||||
copy: { show: false },
|
||||
edit: { show: false },
|
||||
remove: {
|
||||
order: 10,
|
||||
show: false,
|
||||
},
|
||||
download: {
|
||||
order: 9,
|
||||
title: t("certd.download.title"),
|
||||
type: "link",
|
||||
icon: "ant-design:download-outlined",
|
||||
async click({ row }) {
|
||||
if (!row.certFile) {
|
||||
notification.error({ message: t("certd.certificateNotGenerated") });
|
||||
return;
|
||||
}
|
||||
window.open("/api/monitor/cert/download?id=" + row.id);
|
||||
},
|
||||
},
|
||||
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
editable: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
fromType: {
|
||||
title: t('certd.sourcee'),
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: "dict-select",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: t('certd.sourcePipeline'), value: "pipeline" },
|
||||
{ label: t('certd.sourceManualUpload'), value: "upload" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
sorter: true,
|
||||
component: {
|
||||
color: "auto",
|
||||
},
|
||||
conditionalRender: false,
|
||||
},
|
||||
valueBuilder({ value, row, key }) {
|
||||
if (!value) {
|
||||
row[key] = "pipeline";
|
||||
}
|
||||
},
|
||||
},
|
||||
domains: {
|
||||
title: t('certd.domains'),
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: "text",
|
||||
form: {
|
||||
rules: [{ required: true, message: t('certd.enterDomain') }],
|
||||
},
|
||||
column: {
|
||||
width: 450,
|
||||
sorter: true,
|
||||
component: {
|
||||
name: "fs-values-format",
|
||||
color: "auto",
|
||||
},
|
||||
},
|
||||
},
|
||||
domainCount: {
|
||||
title: t('certd.domainCount'),
|
||||
type: "number",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 120,
|
||||
sorter: true,
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
expiresLeft: {
|
||||
title: t('certd.validDays'),
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "date",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
conditionalRender: false,
|
||||
cellRender({ row }) {
|
||||
const value = row.expiresTime;
|
||||
if (!value) {
|
||||
return "-";
|
||||
}
|
||||
const expireDate = dayjs(value).format("YYYY-MM-DD");
|
||||
const leftDays = dayjs(value).diff(dayjs(), "day");
|
||||
const color = leftDays < 20 ? "red" : "#389e0d";
|
||||
const percent = (leftDays / 90) * 100;
|
||||
return <a-progress title={expireDate + t('certd.expires')} percent={percent} strokeColor={color} format={(percent: number) => `${leftDays}${t('certd.days')}`} />;
|
||||
},
|
||||
},
|
||||
},
|
||||
expiresTime: {
|
||||
title: t('certd.expireTime'),
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
certProvider: {
|
||||
title: t('certd.certIssuer'),
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "text",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 200,
|
||||
},
|
||||
},
|
||||
applyTime: {
|
||||
title: t('certd.applyTime'),
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
"pipeline.title": {
|
||||
title: t('certd.relatedPipeline'),
|
||||
search: { show: false },
|
||||
type: "link",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 350,
|
||||
sorter: true,
|
||||
component: {
|
||||
on: {
|
||||
onClick({ row }) {
|
||||
router.push({ path: "/certd/pipeline/detail", query: { id: row.pipelineId, editMode: "false" } });
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,30 +1,33 @@
|
||||
<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>
|
||||
<fs-page>
|
||||
<template #header>
|
||||
<div class="title">
|
||||
{{ t("certd.certificateRepo.title") }}
|
||||
<span class="sub">{{ t("certd.certificateRepo.sub") }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onActivated, onMounted } from "vue";
|
||||
import { useFs } from "@fast-crud/fast-crud";
|
||||
import createCrudOptions from "./crud";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
defineOptions({
|
||||
name: "CertStore",
|
||||
name: "CertStore",
|
||||
});
|
||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} });
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
onActivated(() => {
|
||||
crudExpose.doRefresh();
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
</script>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,53 +1,61 @@
|
||||
<template>
|
||||
<fs-page>
|
||||
<template #header>
|
||||
<div class="title flex items-center">
|
||||
站点证书监控
|
||||
<div class="sub flex-1">
|
||||
<div>
|
||||
每天0点,检查网站证书的过期时间,到期前10天时将发出提醒(使用默认通知渠道);
|
||||
<router-link to="/certd/monitor/setting">站点监控设置</router-link>
|
||||
</div>
|
||||
<div class="flex items-center">基础版限制1条,专业版以上无限制,当前<vip-button class="ml-5" mode="nav"></vip-button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="more">
|
||||
<a-button type="primary" @click="checkAll">检查全部</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||
</fs-page>
|
||||
<fs-page>
|
||||
<template #header>
|
||||
<div class="title flex items-center">
|
||||
{{ t("certd.monitor.title") }}
|
||||
<div class="sub flex-1">
|
||||
<div>
|
||||
{{ t("certd.monitor.description") }}
|
||||
<router-link to="/certd/monitor/setting">{{ t("certd.monitor.settingLink") }}</router-link>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
{{ t("certd.monitor.limitInfo") }}
|
||||
<vip-button class="ml-5" mode="nav"></vip-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="more">
|
||||
<a-button type="primary" @click="checkAll">{{ t("certd.monitor.checkAll") }}</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onActivated, onMounted } from "vue";
|
||||
import { useFs } from "@fast-crud/fast-crud";
|
||||
import createCrudOptions from "./crud";
|
||||
import { siteInfoApi } from "./api";
|
||||
import { Modal, notification } from "ant-design-vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
defineOptions({
|
||||
name: "SiteCertMonitor",
|
||||
name: "SiteCertMonitor",
|
||||
});
|
||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} });
|
||||
function checkAll() {
|
||||
Modal.confirm({
|
||||
title: "确认",
|
||||
content: "确认触发检查全部站点证书吗?",
|
||||
onOk: async () => {
|
||||
await siteInfoApi.CheckAll();
|
||||
notification.success({
|
||||
message: "检查任务已提交",
|
||||
description: "请稍后刷新页面查看结果",
|
||||
});
|
||||
},
|
||||
});
|
||||
Modal.confirm({
|
||||
title: t("certd.monitor.confirmTitle"), // "确认"
|
||||
content: t("certd.monitor.confirmContent"), // "确认触发检查全部站点证书吗?"
|
||||
onOk: async () => {
|
||||
await siteInfoApi.CheckAll();
|
||||
notification.success({
|
||||
message: t("certd.monitor.checkSubmitted"), // "检查任务已提交"
|
||||
description: t("certd.monitor.pleaseRefresh"), // "请稍后刷新页面查看结果"
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
onActivated(() => {
|
||||
crudExpose.doRefresh();
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -7,353 +7,354 @@ import { Modal, notification } from "ant-design-vue";
|
||||
import { useSiteIpMonitor } from "/@/views/certd/monitor/site/ip/use";
|
||||
|
||||
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const api = siteIpApi;
|
||||
const { t } = useI18n();
|
||||
const api = siteIpApi;
|
||||
|
||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||
if (!query.query) {
|
||||
query.query = {};
|
||||
}
|
||||
query.query.siteId = context.props.siteId;
|
||||
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 pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||
if (!query.query) {
|
||||
query.query = {};
|
||||
}
|
||||
query.query.siteId = context.props.siteId;
|
||||
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;
|
||||
form.siteId = context.props.siteId;
|
||||
const res = await api.AddObj(form);
|
||||
return res;
|
||||
};
|
||||
const addRequest = async (req: AddReq) => {
|
||||
const { form } = req;
|
||||
form.siteId = context.props.siteId;
|
||||
const res = await api.AddObj(form);
|
||||
return res;
|
||||
};
|
||||
|
||||
const checkStatusDict = dict({
|
||||
data: [
|
||||
{ label: "成功", value: "ok", color: "green" },
|
||||
{ label: "检查中", value: "checking", color: "blue" },
|
||||
{ label: "异常", value: "error", color: "red" },
|
||||
],
|
||||
});
|
||||
const { openSiteIpImportDialog } = useSiteIpMonitor();
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
form: {
|
||||
labelCol: {
|
||||
//固定label宽度
|
||||
span: null,
|
||||
style: {
|
||||
width: "100px",
|
||||
},
|
||||
},
|
||||
col: {
|
||||
span: 22,
|
||||
},
|
||||
wrapper: {
|
||||
width: 600,
|
||||
},
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
async click() {
|
||||
await crudExpose.openAdd({});
|
||||
},
|
||||
},
|
||||
import: {
|
||||
show: true,
|
||||
text: "批量导入",
|
||||
type: "primary",
|
||||
async click() {
|
||||
openSiteIpImportDialog({
|
||||
siteId: context.props.siteId,
|
||||
afterSubmit() {
|
||||
crudExpose.doRefresh();
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
load: {
|
||||
text: "同步IP",
|
||||
type: "primary",
|
||||
async click() {
|
||||
Modal.confirm({
|
||||
title: "同步IP",
|
||||
content: "确定要同步IP吗?",
|
||||
onOk: async () => {
|
||||
await api.DoSync(context.props.siteId);
|
||||
await crudExpose.doRefresh();
|
||||
notification.success({
|
||||
message: "同步完成",
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
checkAll: {
|
||||
text: "检查全部",
|
||||
type: "primary",
|
||||
click: () => {
|
||||
Modal.confirm({
|
||||
title: "确认",
|
||||
content: "确认触发检查全部IP站点的证书吗?",
|
||||
onOk: async () => {
|
||||
await siteIpApi.CheckAll(context.props.siteId);
|
||||
notification.success({
|
||||
message: "检查任务已提交",
|
||||
description: "请稍后刷新页面查看结果",
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
rowHandle: {
|
||||
fixed: "right",
|
||||
width: 240,
|
||||
buttons: {
|
||||
check: {
|
||||
order: 0,
|
||||
type: "link",
|
||||
text: null,
|
||||
tooltip: {
|
||||
title: "立即检查",
|
||||
},
|
||||
icon: "ion:play-sharp",
|
||||
click: async ({ row }) => {
|
||||
await api.DoCheck(row.id);
|
||||
await crudExpose.doRefresh();
|
||||
notification.success({
|
||||
message: "检查任务已提交,请稍后刷新查看结果",
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 80,
|
||||
align: "center",
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
ipAddress: {
|
||||
title: "IP",
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: "text",
|
||||
helper: "也支持填写CNAME域名",
|
||||
form: {
|
||||
rules: [{ required: true, message: "请输入IP" }],
|
||||
},
|
||||
column: {
|
||||
width: 160,
|
||||
},
|
||||
},
|
||||
certDomains: {
|
||||
title: "证书域名",
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "text",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 200,
|
||||
sorter: true,
|
||||
show: false,
|
||||
cellRender({ value }) {
|
||||
return (
|
||||
<a-tooltip title={value} placement="left">
|
||||
{value}
|
||||
</a-tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
certProvider: {
|
||||
title: "颁发机构",
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "text",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 200,
|
||||
show: false,
|
||||
sorter: true,
|
||||
cellRender({ value }) {
|
||||
return <a-tooltip title={value}>{value}</a-tooltip>;
|
||||
},
|
||||
},
|
||||
},
|
||||
certStatus: {
|
||||
title: "证书状态",
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: "dict-select",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "正常", value: "ok", color: "green" },
|
||||
{ label: "过期", value: "expired", color: "red" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
sorter: true,
|
||||
show: true,
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
certExpiresTime: {
|
||||
title: "证书到期时间",
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "date",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
cellRender({ value }) {
|
||||
if (!value) {
|
||||
return "-";
|
||||
}
|
||||
const expireDate = dayjs(value).format("YYYY-MM-DD");
|
||||
const leftDays = dayjs(value).diff(dayjs(), "day");
|
||||
const color = leftDays < 20 ? "red" : "#389e0d";
|
||||
const percent = (leftDays / 90) * 100;
|
||||
return <a-progress title={expireDate + "过期"} percent={percent} strokeColor={color} format={(percent: number) => `${leftDays}天`} />;
|
||||
},
|
||||
},
|
||||
},
|
||||
checkStatus: {
|
||||
title: "检查状态",
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "dict-select",
|
||||
dict: checkStatusDict,
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
align: "center",
|
||||
sorter: true,
|
||||
cellRender({ value, row, key }) {
|
||||
return (
|
||||
<a-tooltip title={row.error}>
|
||||
<fs-values-format v-model={value} dict={checkStatusDict}></fs-values-format>
|
||||
</a-tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
lastCheckTime: {
|
||||
title: "上次检查时间",
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 155,
|
||||
},
|
||||
},
|
||||
from: {
|
||||
title: "来源",
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "同步", value: "sync", color: "green" },
|
||||
{ label: "手动", value: "manual", color: "blue" },
|
||||
{ label: "导入", value: "import", color: "blue" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
value: false,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
sorter: true,
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
disabled: {
|
||||
title: "禁用启用",
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "启用", value: false, color: "green" },
|
||||
{ label: "禁用", value: true, color: "red" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
value: false,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
sorter: true,
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
remark: {
|
||||
title: "备注",
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "text",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 200,
|
||||
sorter: true,
|
||||
tooltip: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const checkStatusDict = dict({
|
||||
data: [
|
||||
{ label: t("certd.statusSuccess"), value: "ok", color: "green" },
|
||||
{ label: t("certd.statusChecking"), value: "checking", color: "blue" },
|
||||
{ label: t("certd.statusError"), value: "error", color: "red" },
|
||||
],
|
||||
});
|
||||
const { openSiteIpImportDialog } = useSiteIpMonitor();
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
form: {
|
||||
labelCol: {
|
||||
//固定label宽度
|
||||
span: null,
|
||||
style: {
|
||||
width: "100px",
|
||||
},
|
||||
},
|
||||
col: {
|
||||
span: 22,
|
||||
},
|
||||
wrapper: {
|
||||
width: 600,
|
||||
},
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
async click() {
|
||||
await crudExpose.openAdd({});
|
||||
},
|
||||
},
|
||||
import: {
|
||||
show: true,
|
||||
text: t("certd.actionImportBatch"),
|
||||
type: "primary",
|
||||
async click() {
|
||||
openSiteIpImportDialog({
|
||||
siteId: context.props.siteId,
|
||||
afterSubmit() {
|
||||
crudExpose.doRefresh();
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
load: {
|
||||
text: t("certd.actionSyncIp"),
|
||||
type: "primary",
|
||||
async click() {
|
||||
Modal.confirm({
|
||||
title: t("certd.modalTitleSyncIp"),
|
||||
content: t("certd.modalContentSyncIp"),
|
||||
onOk: async () => {
|
||||
await api.DoSync(context.props.siteId);
|
||||
await crudExpose.doRefresh();
|
||||
notification.success({
|
||||
message: t("certd.notificationSyncComplete"),
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
checkAll: {
|
||||
text: t("certd.actionCheckAll"),
|
||||
type: "primary",
|
||||
click: () => {
|
||||
Modal.confirm({
|
||||
title: t("certd.modalTitleConfirm"),
|
||||
content: t("certd.modalContentCheckAll"),
|
||||
onOk: async () => {
|
||||
await siteIpApi.CheckAll(context.props.siteId);
|
||||
notification.success({
|
||||
message: t("certd.notificationCheckSubmitted"),
|
||||
description: t("certd.notificationCheckDescription"),
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
rowHandle: {
|
||||
fixed: "right",
|
||||
width: 240,
|
||||
buttons: {
|
||||
check: {
|
||||
order: 0,
|
||||
type: "link",
|
||||
text: null,
|
||||
tooltip: {
|
||||
title: t("certd.tooltipCheckNow"),
|
||||
},
|
||||
icon: "ion:play-sharp",
|
||||
click: async ({ row }) => {
|
||||
await api.DoCheck(row.id);
|
||||
await crudExpose.doRefresh();
|
||||
notification.success({
|
||||
message: t("certd.notificationCheckSubmittedPleaseRefresh"),
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: t("certd.columnId"),
|
||||
key: "id",
|
||||
type: "number",
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 80,
|
||||
align: "center",
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
ipAddress: {
|
||||
title: t("certd.columnIp"),
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: "text",
|
||||
helper: t("certd.helperIpCname"),
|
||||
form: {
|
||||
rules: [{ required: true, message: t("certd.ruleIpRequired") }],
|
||||
},
|
||||
column: {
|
||||
width: 160,
|
||||
},
|
||||
},
|
||||
certDomains: {
|
||||
title: t("certd.columnCertDomains"),
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "text",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 200,
|
||||
sorter: true,
|
||||
show: false,
|
||||
cellRender({ value }) {
|
||||
return (
|
||||
<a-tooltip title={value} placement="left">
|
||||
{value}
|
||||
</a-tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
certProvider: {
|
||||
title: t("certd.columnCertProvider"),
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "text",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 200,
|
||||
show: false,
|
||||
sorter: true,
|
||||
cellRender({ value }) {
|
||||
return <a-tooltip title={value}>{value}</a-tooltip>;
|
||||
},
|
||||
},
|
||||
},
|
||||
certStatus: {
|
||||
title: t("certd.columnCertStatus"),
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: "dict-select",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: t("certd.statusNormal"), value: "ok", color: "green" },
|
||||
{ label: t("certd.statusExpired"), value: "expired", color: "red" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
sorter: true,
|
||||
show: true,
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
certExpiresTime: {
|
||||
title: t("certd.columnCertExpiresTime"),
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "date",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
cellRender({ value }) {
|
||||
if (!value) {
|
||||
return "-";
|
||||
}
|
||||
const expireDate = dayjs(value).format("YYYY-MM-DD");
|
||||
const leftDays = dayjs(value).diff(dayjs(), "day");
|
||||
const color = leftDays < 20 ? "red" : "#389e0d";
|
||||
const percent = (leftDays / 90) * 100;
|
||||
return <a-progress title={expireDate + " " + t("certd.expired")} percent={percent} strokeColor={color} format={(percent: number) => `${leftDays} ${t("certd.days")}`} />;
|
||||
},
|
||||
},
|
||||
},
|
||||
checkStatus: {
|
||||
title: t("certd.columnCheckStatus"),
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "dict-select",
|
||||
dict: checkStatusDict,
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
align: "center",
|
||||
sorter: true,
|
||||
cellRender({ value, row, key }) {
|
||||
return (
|
||||
<a-tooltip title={row.error}>
|
||||
<fs-values-format v-model={value} dict={checkStatusDict}></fs-values-format>
|
||||
</a-tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
lastCheckTime: {
|
||||
title: t("certd.columnLastCheckTime"),
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 155,
|
||||
},
|
||||
},
|
||||
from: {
|
||||
title: t("certd.columnSource"),
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: t("certd.sourceSync"), value: "sync", color: "green" },
|
||||
{ label: t("certd.sourceManual"), value: "manual", color: "blue" },
|
||||
{ label: t("certd.sourceImport"), value: "import", color: "blue" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
value: false,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
sorter: true,
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
disabled: {
|
||||
title: t("certd.columnDisabled"),
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: t("certd.enabled"), value: false, color: "green" },
|
||||
{ label: t("certd.disabled"), value: true, color: "red" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
value: false,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
sorter: true,
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
remark: {
|
||||
title: t("certd.columnRemark"),
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "text",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 200,
|
||||
sorter: true,
|
||||
tooltip: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,37 +1,41 @@
|
||||
<template>
|
||||
<fs-page class="page-user-settings page-site-monitor-setting">
|
||||
<template #header>
|
||||
<div class="title">站点监控设置</div>
|
||||
</template>
|
||||
<div class="user-settings-form settings-form">
|
||||
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off">
|
||||
<a-form-item label="通知渠道" :name="['notificationId']">
|
||||
<div class="flex">
|
||||
<NotificationSelector v-model="formState.notificationId" />
|
||||
</div>
|
||||
<div class="helper">设置通知渠道</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="重试次数" :name="['retryTimes']">
|
||||
<div class="flex">
|
||||
<a-input-number v-model:value="formState.retryTimes" />
|
||||
</div>
|
||||
<div class="helper">监控请求重试次数</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="监控定时设置" :name="['cron']">
|
||||
<div class="flex flex-baseline">
|
||||
<cron-editor v-model="formState.cron" :disabled="!settingsStore.isPlus" :allow-every-min="userStore.isAdmin" />
|
||||
<vip-button class="ml-5" mode="button"></vip-button>
|
||||
</div>
|
||||
<div class="helper">定时触发监控</div>
|
||||
</a-form-item>
|
||||
<a-form-item label=" " :colon="false" :wrapper-col="{ span: 16 }">
|
||||
<loading-button type="primary" html-type="button" :click="doSave">保存</loading-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</fs-page>
|
||||
<fs-page class="page-user-settings page-site-monitor-setting">
|
||||
<template #header>
|
||||
<div class="title">{{ t("certd.siteMonitorSettings") }}</div>
|
||||
</template>
|
||||
<div class="user-settings-form settings-form">
|
||||
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }"
|
||||
autocomplete="off">
|
||||
<a-form-item :label="t('certd.notificationChannel')" :name="['notificationId']">
|
||||
<div class="flex">
|
||||
<NotificationSelector v-model="formState.notificationId" />
|
||||
</div>
|
||||
<div class="helper">{{ t('certd.setNotificationChannel') }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('certd.retryTimes')" :name="['retryTimes']">
|
||||
<div class="flex">
|
||||
<a-input-number v-model:value="formState.retryTimes" />
|
||||
</div>
|
||||
<div class="helper">{{ t('certd.monitorRetryTimes') }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('certd.monitorCronSetting')" :name="['cron']">
|
||||
<div class="flex flex-baseline">
|
||||
<cron-editor v-model="formState.cron" :disabled="!settingsStore.isPlus"
|
||||
:allow-every-min="userStore.isAdmin" />
|
||||
<vip-button class="ml-5" mode="button"></vip-button>
|
||||
</div>
|
||||
<div class="helper">{{ t('certd.cronTrigger') }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item label=" " :colon="false" :wrapper-col="{ span: 16 }">
|
||||
<loading-button type="primary" html-type="button" :click="doSave">{{ t('certd.save')
|
||||
}}</loading-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { reactive } from "vue";
|
||||
import * as api from "./api";
|
||||
@@ -41,38 +45,41 @@ import { merge } from "lodash-es";
|
||||
import { useSettingStore } from "/src/store/settings";
|
||||
import NotificationSelector from "/@/views/certd/notification/notification-selector/index.vue";
|
||||
import { useUserStore } from "/@/store/user";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const settingsStore = useSettingStore();
|
||||
const userStore = useUserStore();
|
||||
defineOptions({
|
||||
name: "UserSecurity",
|
||||
name: "UserSecurity",
|
||||
});
|
||||
|
||||
const formState = reactive<Partial<UserSiteMonitorSetting>>({
|
||||
notificationId: 0,
|
||||
notificationId: 0,
|
||||
});
|
||||
|
||||
async function loadUserSettings() {
|
||||
const data: any = await api.SiteMonitorSettingsGet();
|
||||
merge(formState, data);
|
||||
const data: any = await api.SiteMonitorSettingsGet();
|
||||
merge(formState, data);
|
||||
}
|
||||
|
||||
loadUserSettings();
|
||||
const doSave = async (form: any) => {
|
||||
await api.SiteMonitorSettingsSave({
|
||||
...formState,
|
||||
});
|
||||
notification.success({
|
||||
message: "保存成功",
|
||||
});
|
||||
await api.SiteMonitorSettingsSave({
|
||||
...formState,
|
||||
});
|
||||
notification.success({
|
||||
message: t("certd.saveSuccess"),
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.page-user-settings {
|
||||
.user-settings-form {
|
||||
width: 700px;
|
||||
margin: 20px;
|
||||
}
|
||||
.user-settings-form {
|
||||
width: 700px;
|
||||
margin: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,41 +1,44 @@
|
||||
import { useFormWrapper } from "@fast-crud/fast-crud";
|
||||
import { siteInfoApi } from "./api";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
export function useSiteImport() {
|
||||
const { openCrudFormDialog } = useFormWrapper();
|
||||
const { t } = useI18n();
|
||||
const { openCrudFormDialog } = useFormWrapper();
|
||||
|
||||
async function openSiteImportDialog(opts: { afterSubmit: any }) {
|
||||
const { afterSubmit } = opts;
|
||||
await openCrudFormDialog<any>({
|
||||
crudOptions: {
|
||||
columns: {
|
||||
text: {
|
||||
type: "textarea",
|
||||
title: "域名列表",
|
||||
form: {
|
||||
helper: "格式【域名:端口:名称】,一行一个,其中端口、名称可以省略\n比如:\nwww.baidu.com:443:百度\nwww.taobao.com::淘宝\nwww.google.com",
|
||||
rules: [{ required: true, message: "请输入要导入的域名" }],
|
||||
component: {
|
||||
placeholder: "www.baidu.com:443:百度\nwww.taobao.com::淘宝\nwww.google.com\n",
|
||||
rows: 8,
|
||||
},
|
||||
col: {
|
||||
span: 24,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
form: {
|
||||
async doSubmit({ form }) {
|
||||
return siteInfoApi.Import(form);
|
||||
},
|
||||
afterSubmit,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
async function openSiteImportDialog(opts: { afterSubmit: any }) {
|
||||
const { afterSubmit } = opts;
|
||||
await openCrudFormDialog<any>({
|
||||
crudOptions: {
|
||||
columns: {
|
||||
text: {
|
||||
type: "textarea",
|
||||
title: t("certd.domainList.title"), // 域名列表
|
||||
form: {
|
||||
helper: t("certd.domainList.helper"),
|
||||
rules: [{ required: true, message: t("certd.domainList.required") }],
|
||||
component: {
|
||||
placeholder: t("certd.domainList.placeholder"),
|
||||
rows: 8,
|
||||
},
|
||||
col: {
|
||||
span: 24,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
return {
|
||||
openSiteImportDialog,
|
||||
};
|
||||
form: {
|
||||
async doSubmit({ form }) {
|
||||
return siteInfoApi.Import(form);
|
||||
},
|
||||
afterSubmit,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
openSiteImportDialog,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,238 +5,241 @@ 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";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
export function notificationProvide(api: any) {
|
||||
provide("notificationApi", api);
|
||||
provide("get:plugin:type", () => {
|
||||
return "notification";
|
||||
});
|
||||
provide("notificationApi", api);
|
||||
provide("get:plugin:type", () => {
|
||||
return "notification";
|
||||
});
|
||||
}
|
||||
|
||||
export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
|
||||
const notificationTypeDictRef = dict({
|
||||
url: "/pi/notification/getTypeDict",
|
||||
});
|
||||
const defaultPluginConfig = {
|
||||
component: {
|
||||
name: "a-input",
|
||||
vModel: "value",
|
||||
},
|
||||
};
|
||||
const { t } = useI18n();
|
||||
|
||||
function buildDefineFields(define: any, form: any, mode: string) {
|
||||
const formWrapperRef = crudExpose.getFormWrapperRef();
|
||||
const columnsRef = toRef(formWrapperRef.formOptions, "columns");
|
||||
const notificationTypeDictRef = dict({
|
||||
url: "/pi/notification/getTypeDict",
|
||||
});
|
||||
const defaultPluginConfig = {
|
||||
component: {
|
||||
name: "a-input",
|
||||
vModel: "value",
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
function buildDefineFields(define: any, form: any, mode: string) {
|
||||
const formWrapperRef = crudExpose.getFormWrapperRef();
|
||||
const columnsRef = toRef(formWrapperRef.formOptions, "columns");
|
||||
|
||||
if (column.required) {
|
||||
if (!column.rules) {
|
||||
column.rules = [];
|
||||
}
|
||||
column.rules.push({ required: true, message: "此项必填" });
|
||||
}
|
||||
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.value != null && get(form, key) == null) {
|
||||
set(form, key, column.value);
|
||||
}
|
||||
//字段配置赋值
|
||||
columnsRef.value[key] = column;
|
||||
console.log("form", columnsRef.value, form);
|
||||
});
|
||||
}
|
||||
if (column.required) {
|
||||
if (!column.rules) {
|
||||
column.rules = [];
|
||||
}
|
||||
column.rules.push({ required: true, message: t("certd.requiredField") });
|
||||
}
|
||||
|
||||
const currentDefine = ref();
|
||||
//设置默认值
|
||||
if (column.value != null && get(form, key) == null) {
|
||||
set(form, key, column.value);
|
||||
}
|
||||
//字段配置赋值
|
||||
columnsRef.value[key] = column;
|
||||
console.log("form", columnsRef.value, form);
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
const currentDefine = ref();
|
||||
|
||||
if (!immediate) {
|
||||
form.body = {};
|
||||
if (define.needPlus) {
|
||||
mitter.emit("openVipModal");
|
||||
}
|
||||
}
|
||||
return {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
column: {
|
||||
width: 100,
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
type: {
|
||||
title: t("certd.notificationType"),
|
||||
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: t("certd.selectNotificationType") }],
|
||||
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 (!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,
|
||||
},
|
||||
},
|
||||
isDefault: {
|
||||
title: "是否默认",
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "是", value: true, color: "success" },
|
||||
{ label: "否", value: false, color: "default" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
value: false,
|
||||
rules: [{ required: true, message: "请选择是否默认" }],
|
||||
order: 999,
|
||||
},
|
||||
column: {
|
||||
align: "center",
|
||||
width: 100,
|
||||
component: {
|
||||
name: "a-switch",
|
||||
vModel: "checked",
|
||||
disabled: compute(({ value }) => {
|
||||
return value === true;
|
||||
}),
|
||||
on: {
|
||||
change({ row }) {
|
||||
Modal.confirm({
|
||||
title: "提示",
|
||||
content: "确定设置为默认通知?",
|
||||
onOk: async () => {
|
||||
await api.SetDefault(row.id);
|
||||
await crudExpose.doRefresh();
|
||||
},
|
||||
onCancel: async () => {
|
||||
await crudExpose.doRefresh();
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ColumnCompositionProps,
|
||||
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,
|
||||
};
|
||||
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: t("certd.notificationName"),
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: ["text"],
|
||||
form: {
|
||||
rules: [{ required: true, message: t("certd.enterName") }],
|
||||
helper: t("certd.helperNotificationName"),
|
||||
},
|
||||
column: {
|
||||
width: 200,
|
||||
},
|
||||
},
|
||||
isDefault: {
|
||||
title: t("certd.isDefault"),
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: t("certd.yes"), value: true, color: "success" },
|
||||
{ label: t("certd.no"), value: false, color: "default" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
value: false,
|
||||
rules: [{ required: true, message: t("certd.selectIsDefault") }],
|
||||
order: 999,
|
||||
},
|
||||
column: {
|
||||
align: "center",
|
||||
width: 100,
|
||||
component: {
|
||||
name: "a-switch",
|
||||
vModel: "checked",
|
||||
disabled: compute(({ value }) => {
|
||||
return value === true;
|
||||
}),
|
||||
on: {
|
||||
change({ row }) {
|
||||
Modal.confirm({
|
||||
title: t("certd.prompt"),
|
||||
content: t("certd.confirmSetDefaultNotification"),
|
||||
onOk: async () => {
|
||||
await api.SetDefault(row.id);
|
||||
await crudExpose.doRefresh();
|
||||
},
|
||||
onCancel: async () => {
|
||||
await crudExpose.doRefresh();
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ColumnCompositionProps,
|
||||
test: {
|
||||
title: t("certd.test"),
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
+87
-93
@@ -1,36 +1,26 @@
|
||||
<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" v-bind="select" @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"
|
||||
v-bind="tableSelect"
|
||||
@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>
|
||||
<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" v-bind="select"
|
||||
@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"
|
||||
v-bind="tableSelect" @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>
|
||||
@@ -41,23 +31,26 @@ import { dict } from "@fast-crud/fast-crud";
|
||||
import createCrudOptions from "../crud";
|
||||
import { notificationProvide } from "/@/views/certd/notification/common";
|
||||
import { useUserStore } from "/@/store/user";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
defineOptions({
|
||||
name: "NotificationSelector",
|
||||
name: "NotificationSelector",
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: number | string | number[] | string[];
|
||||
type?: string;
|
||||
placeholder?: string;
|
||||
size?: string;
|
||||
disabled?: boolean;
|
||||
select?: any;
|
||||
tableSelect?: any;
|
||||
modelValue?: number | string | number[] | string[];
|
||||
type?: string;
|
||||
placeholder?: string;
|
||||
size?: string;
|
||||
disabled?: boolean;
|
||||
select?: any;
|
||||
tableSelect?: any;
|
||||
}>();
|
||||
|
||||
const onChange = async (value: number) => {
|
||||
await emitValue(value);
|
||||
await emitValue(value);
|
||||
};
|
||||
|
||||
const emit = defineEmits(["update:modelValue", "selectedChange", "change"]);
|
||||
@@ -76,89 +69,90 @@ notificationProvide(api);
|
||||
// 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);
|
||||
},
|
||||
url: "/pi/notification/options",
|
||||
value: "id",
|
||||
label: "name",
|
||||
onReady: ({ dict }) => {
|
||||
const data = [
|
||||
{
|
||||
id: 0,
|
||||
name: t("certd.notificationDefault"),
|
||||
icon: "ion:notifications",
|
||||
},
|
||||
...dict.data,
|
||||
];
|
||||
dict.setData(data);
|
||||
},
|
||||
});
|
||||
|
||||
const renderLabel = (option: any) => {
|
||||
return <span>{option.name}</span>;
|
||||
return <span>{option.name}</span>;
|
||||
};
|
||||
|
||||
async function openTableSelectDialog() {
|
||||
selectOpened.value = false;
|
||||
await tableSelectRef.value.open({});
|
||||
await tableSelectRef.value.crudExpose.openAdd({});
|
||||
selectOpened.value = false;
|
||||
await tableSelectRef.value.open({});
|
||||
await tableSelectRef.value.crudExpose.openAdd({});
|
||||
}
|
||||
|
||||
const selectOpened = ref(false);
|
||||
const selectSlots = ref({
|
||||
dropdownRender({ menuNode, props }: 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;
|
||||
},
|
||||
dropdownRender({ menuNode, props }: 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);
|
||||
if (props.disabled) {
|
||||
return;
|
||||
}
|
||||
emitValue(null);
|
||||
}
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
async function emitValue(value: any) {
|
||||
// target.value = optionsDictRef.dataMap[value];
|
||||
const userId = userStore.userInfo.id;
|
||||
if (pipeline?.value && pipeline.value.userId !== userId) {
|
||||
message.error("对不起,您不能修改他人流水线的通知");
|
||||
return;
|
||||
}
|
||||
emit("change", value);
|
||||
emit("update:modelValue", value);
|
||||
// target.value = optionsDictRef.dataMap[value];
|
||||
const userId = userStore.userInfo.id;
|
||||
if (pipeline?.value && pipeline.value.userId !== userId) {
|
||||
message.error("对不起,您不能修改他人流水线的通知");
|
||||
return;
|
||||
}
|
||||
emit("change", value);
|
||||
emit("update:modelValue", value);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => {
|
||||
return props.modelValue;
|
||||
},
|
||||
async value => {
|
||||
await optionsDictRef.loadDict();
|
||||
//@ts-ignore
|
||||
target.value = optionsDictRef.dataMap[value];
|
||||
emit("selectedChange", target.value);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
() => {
|
||||
return props.modelValue;
|
||||
},
|
||||
async value => {
|
||||
await optionsDictRef.loadDict();
|
||||
//@ts-ignore
|
||||
target.value = optionsDictRef.dataMap[value];
|
||||
emit("selectedChange", target.value);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
//当不在pipeline中编辑时,可能为空
|
||||
const pipeline = inject("pipeline", null);
|
||||
|
||||
async function doRefresh() {
|
||||
await optionsDictRef.reloadDict();
|
||||
await optionsDictRef.reloadDict();
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.notification-selector {
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,181 +5,182 @@ import { OPEN_API_DOC, openkeyApi } from "./api";
|
||||
import { useModal } from "/@/use/use-modal";
|
||||
|
||||
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const { t } = useI18n();
|
||||
const api = openkeyApi;
|
||||
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 { t } = useI18n();
|
||||
const api = openkeyApi;
|
||||
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 model = useModal();
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
form: {
|
||||
labelCol: {
|
||||
//固定label宽度
|
||||
span: null,
|
||||
style: {
|
||||
width: "100px",
|
||||
},
|
||||
},
|
||||
col: {
|
||||
span: 22,
|
||||
},
|
||||
wrapper: {
|
||||
width: 600,
|
||||
},
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
text: "生成新的Key",
|
||||
},
|
||||
},
|
||||
},
|
||||
rowHandle: {
|
||||
width: 300,
|
||||
fixed: "right",
|
||||
buttons: {
|
||||
view: { show: true },
|
||||
copy: { show: false },
|
||||
edit: { show: false },
|
||||
remove: { show: true },
|
||||
gen: {
|
||||
text: "接口测试",
|
||||
size: "mini",
|
||||
icon: "devicon-plain:vitest",
|
||||
type: "primary",
|
||||
async click({ row }) {
|
||||
const apiToken = await api.GetApiToken(row.id);
|
||||
const addRequest = async (req: AddReq) => {
|
||||
const { form } = req;
|
||||
const res = await api.AddObj(form);
|
||||
return res;
|
||||
};
|
||||
const model = useModal();
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
form: {
|
||||
labelCol: {
|
||||
//固定label宽度
|
||||
span: null,
|
||||
style: {
|
||||
width: "100px",
|
||||
},
|
||||
},
|
||||
col: {
|
||||
span: 22,
|
||||
},
|
||||
wrapper: {
|
||||
width: 600,
|
||||
},
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
text: t("certd.actionbar.add"),
|
||||
},
|
||||
},
|
||||
},
|
||||
rowHandle: {
|
||||
width: 300,
|
||||
fixed: "right",
|
||||
buttons: {
|
||||
view: { show: true },
|
||||
copy: { show: false },
|
||||
edit: { show: false },
|
||||
remove: { show: true },
|
||||
gen: {
|
||||
text: t("certd.gen.text"),
|
||||
size: "mini",
|
||||
icon: "devicon-plain:vitest",
|
||||
type: "primary",
|
||||
async click({ row }) {
|
||||
const apiToken = await api.GetApiToken(row.id);
|
||||
|
||||
model.success({
|
||||
title: "x-certd-token",
|
||||
maskClosable: true,
|
||||
okText: "确定",
|
||||
width: 600,
|
||||
content: () => {
|
||||
return (
|
||||
<div>
|
||||
<div class={"m-10 p-10"}>
|
||||
测试x-certd-token如下,您可以在3分钟内使用它进行
|
||||
<a href={OPEN_API_DOC} target={"_blank"}>
|
||||
开放接口
|
||||
</a>
|
||||
请求测试
|
||||
</div>
|
||||
<div class={"m-10 p-10"} style={{ border: "1px solid #333" }}>
|
||||
<fs-copyable model-value={apiToken}></fs-copyable>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
editable: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
keyId: {
|
||||
title: "KeyId",
|
||||
type: ["text", "copyable"],
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 250,
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
keySecret: {
|
||||
title: "KeySecret",
|
||||
type: ["text", "copyable"],
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 580,
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
scope: {
|
||||
title: "权限范围",
|
||||
type: "dict-radio",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "仅开放接口", value: "open", color: "blue" },
|
||||
{ label: "账户所有权限", value: "user", color: "red" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
value: "open",
|
||||
show: true,
|
||||
rules: [{ required: true, message: "此项必填" }],
|
||||
helper: "仅开放接口只可以访问开放接口,账户所有权限可以访问所有接口",
|
||||
component: {
|
||||
vModel: "value",
|
||||
},
|
||||
},
|
||||
column: {
|
||||
width: 120,
|
||||
align: "center",
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
createTime: {
|
||||
title: "创建时间",
|
||||
type: "datetime",
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
model.success({
|
||||
title: t("certd.gen.title"),
|
||||
maskClosable: true,
|
||||
okText: t("certd.gen.okText"),
|
||||
width: 600,
|
||||
content: () => {
|
||||
return (
|
||||
<div>
|
||||
<div class={"m-10 p-10"}>
|
||||
{t("certd.gen.contentPart1")}
|
||||
<a href={OPEN_API_DOC} target={"_blank"}>
|
||||
{t("certd.gen.openApi")}
|
||||
</a>
|
||||
{t("certd.gen.contentPart2")}
|
||||
</div>
|
||||
<div class={"m-10 p-10"} style={{ border: "1px solid #333" }}>
|
||||
<fs-copyable model-value={apiToken}></fs-copyable>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
editable: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
keyId: {
|
||||
title: "KeyId",
|
||||
type: ["text", "copyable"],
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 250,
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
keySecret: {
|
||||
title: "KeySecret",
|
||||
type: ["text", "copyable"],
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 580,
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
scope: {
|
||||
title: t("certd.scope"),
|
||||
type: "dict-radio",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: t("certd.scopeOpenApiOnly"), value: "open", color: "blue" },
|
||||
{ label: t("certd.scopeFullAccount"), value: "user", color: "red" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
value: "open",
|
||||
show: true,
|
||||
rules: [{ required: true, message: t("certd.required") }],
|
||||
helper: t("certd.scopeHelper"),
|
||||
component: {
|
||||
vModel: "value",
|
||||
},
|
||||
},
|
||||
column: {
|
||||
width: 120,
|
||||
align: "center",
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
createTime: {
|
||||
title: t("certd.fields.createTime"),
|
||||
type: "datetime",
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import * as api from "../api";
|
||||
import { PluginGroup, usePluginStore } from "/@/store/plugin";
|
||||
import { createNotificationApi } from "/@/views/certd/notification/api";
|
||||
import GroupSelector from "../group/group-selector.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
|
||||
export function fillPipelineByDefaultForm(pipeline: any, form: any) {
|
||||
const triggers = [];
|
||||
@@ -32,238 +34,241 @@ export function fillPipelineByDefaultForm(pipeline: any, form: any) {
|
||||
}
|
||||
|
||||
export function setRunnableIds(pipeline: any) {
|
||||
const idMap: any = {};
|
||||
function createId(oldId: any) {
|
||||
if (oldId == null) {
|
||||
return nanoid();
|
||||
}
|
||||
const newId = nanoid();
|
||||
idMap[oldId] = newId;
|
||||
return newId;
|
||||
}
|
||||
if (pipeline.stages) {
|
||||
for (const stage of pipeline.stages) {
|
||||
stage.id = createId(stage.id);
|
||||
if (stage.tasks) {
|
||||
for (const task of stage.tasks) {
|
||||
task.id = createId(task.id);
|
||||
if (task.steps) {
|
||||
for (const step of task.steps) {
|
||||
step.id = createId(step.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const { t } = useI18n();
|
||||
const idMap: any = {};
|
||||
function createId(oldId: any) {
|
||||
if (oldId == null) {
|
||||
return nanoid();
|
||||
}
|
||||
const newId = nanoid();
|
||||
idMap[oldId] = newId;
|
||||
return newId;
|
||||
}
|
||||
if (pipeline.stages) {
|
||||
for (const stage of pipeline.stages) {
|
||||
stage.id = createId(stage.id);
|
||||
if (stage.tasks) {
|
||||
for (const task of stage.tasks) {
|
||||
task.id = createId(task.id);
|
||||
if (task.steps) {
|
||||
for (const step of task.steps) {
|
||||
step.id = createId(step.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const trigger of pipeline.triggers) {
|
||||
trigger.id = nanoid();
|
||||
}
|
||||
for (const notification of pipeline.notifications) {
|
||||
notification.id = nanoid();
|
||||
}
|
||||
for (const trigger of pipeline.triggers) {
|
||||
trigger.id = nanoid();
|
||||
}
|
||||
for (const notification of pipeline.notifications) {
|
||||
notification.id = nanoid();
|
||||
}
|
||||
|
||||
let content = JSON.stringify(pipeline);
|
||||
for (const key in idMap) {
|
||||
content = content.replaceAll(key, idMap[key]);
|
||||
}
|
||||
return JSON.parse(content);
|
||||
let content = JSON.stringify(pipeline);
|
||||
for (const key in idMap) {
|
||||
content = content.replaceAll(key, idMap[key]);
|
||||
}
|
||||
return JSON.parse(content);
|
||||
}
|
||||
|
||||
export function useCertPipelineCreator() {
|
||||
const { openCrudFormDialog } = useFormWrapper();
|
||||
const { t } = useI18n();
|
||||
const { openCrudFormDialog } = useFormWrapper();
|
||||
|
||||
const pluginStore = usePluginStore();
|
||||
const router = useRouter();
|
||||
const pluginStore = usePluginStore();
|
||||
const router = useRouter();
|
||||
|
||||
function createCrudOptions(certPlugins: any[], getFormData: any, doSubmit: any): CreateCrudOptionsRet {
|
||||
const inputs: any = {};
|
||||
const moreParams = [];
|
||||
for (const plugin of certPlugins) {
|
||||
for (const inputKey in plugin.input) {
|
||||
if (inputs[inputKey]) {
|
||||
//如果两个插件有的字段,直接显示
|
||||
inputs[inputKey].form.show = true;
|
||||
continue;
|
||||
}
|
||||
const inputDefine = cloneDeep(plugin.input[inputKey]);
|
||||
if (!inputDefine.required && !inputDefine.maybeNeed) {
|
||||
moreParams.push(inputKey);
|
||||
// continue;
|
||||
}
|
||||
useReference(inputDefine);
|
||||
inputs[inputKey] = {
|
||||
title: inputDefine.title,
|
||||
form: {
|
||||
...inputDefine,
|
||||
show: compute(ctx => {
|
||||
const form = getFormData();
|
||||
if (!form) {
|
||||
return false;
|
||||
}
|
||||
function createCrudOptions(certPlugins: any[], getFormData: any, doSubmit: any): CreateCrudOptionsRet {
|
||||
const inputs: any = {};
|
||||
const moreParams = [];
|
||||
for (const plugin of certPlugins) {
|
||||
for (const inputKey in plugin.input) {
|
||||
if (inputs[inputKey]) {
|
||||
//如果两个插件有的字段,直接显示
|
||||
inputs[inputKey].form.show = true;
|
||||
continue;
|
||||
}
|
||||
const inputDefine = cloneDeep(plugin.input[inputKey]);
|
||||
if (!inputDefine.required && !inputDefine.maybeNeed) {
|
||||
moreParams.push(inputKey);
|
||||
// continue;
|
||||
}
|
||||
useReference(inputDefine);
|
||||
inputs[inputKey] = {
|
||||
title: inputDefine.title,
|
||||
form: {
|
||||
...inputDefine,
|
||||
show: compute(ctx => {
|
||||
const form = getFormData();
|
||||
if (!form) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let inputDefineShow = true;
|
||||
if (inputDefine.show != null) {
|
||||
const computeShow = inputDefine.show as any;
|
||||
if (computeShow === false) {
|
||||
inputDefineShow = false;
|
||||
} else if (computeShow && computeShow.computeFn) {
|
||||
inputDefineShow = computeShow.computeFn({ form });
|
||||
}
|
||||
}
|
||||
return form?.certApplyPlugin === plugin.name && inputDefineShow;
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
let inputDefineShow = true;
|
||||
if (inputDefine.show != null) {
|
||||
const computeShow = inputDefine.show as any;
|
||||
if (computeShow === false) {
|
||||
inputDefineShow = false;
|
||||
} else if (computeShow && computeShow.computeFn) {
|
||||
inputDefineShow = computeShow.computeFn({ form });
|
||||
}
|
||||
}
|
||||
return form?.certApplyPlugin === plugin.name && inputDefineShow;
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const pluginStore = usePluginStore();
|
||||
const randomHour = Math.floor(Math.random() * 6);
|
||||
const randomMin = Math.floor(Math.random() * 60);
|
||||
const pluginStore = usePluginStore();
|
||||
const randomHour = Math.floor(Math.random() * 6);
|
||||
const randomMin = Math.floor(Math.random() * 60);
|
||||
|
||||
const groupDictRef = dict({
|
||||
url: "/pi/pipeline/group/all",
|
||||
value: "id",
|
||||
label: "name",
|
||||
});
|
||||
const groupDictRef = dict({
|
||||
url: "/pi/pipeline/group/all",
|
||||
value: "id",
|
||||
label: "name",
|
||||
});
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
form: {
|
||||
doSubmit,
|
||||
wrapper: {
|
||||
width: 1350,
|
||||
saveRemind: false,
|
||||
title: "创建证书流水线",
|
||||
},
|
||||
group: {
|
||||
groups: {
|
||||
more: {
|
||||
header: "更多参数",
|
||||
columns: moreParams,
|
||||
collapsed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
certApplyPlugin: {
|
||||
title: "证书申请插件",
|
||||
type: "dict-select",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ value: "CertApply", label: "JS-ACME" },
|
||||
{ value: "CertApplyLego", label: "Lego-ACME" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
order: 0,
|
||||
value: "CertApply",
|
||||
helper: {
|
||||
render: () => {
|
||||
return (
|
||||
<ul>
|
||||
<li>JS-ACME:使用简单方便,功能强大【推荐】</li>
|
||||
<li>Lego-ACME:基于Lego实现,支持海量DNS提供商,熟悉LEGO的用户可以使用</li>
|
||||
</ul>
|
||||
);
|
||||
},
|
||||
},
|
||||
valueChange: {
|
||||
handle: async ({ form, value }) => {
|
||||
const config = await pluginStore.getPluginConfig({
|
||||
name: value,
|
||||
type: "builtIn",
|
||||
});
|
||||
if (config.sysSetting?.input) {
|
||||
merge(form, config.sysSetting.input);
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
...inputs,
|
||||
triggerCron: {
|
||||
title: "定时触发",
|
||||
type: "text",
|
||||
form: {
|
||||
value: `0 ${randomMin} ${randomHour} * * *`,
|
||||
component: {
|
||||
name: "cron-editor",
|
||||
vModel: "modelValue",
|
||||
placeholder: "0 0 4 * * *",
|
||||
},
|
||||
helper: "点击上面的按钮,选择每天几点定时执行。\n建议设置为每天触发一次,证书未到期之前任务会跳过,不会重复执行",
|
||||
order: 100,
|
||||
},
|
||||
},
|
||||
notification: {
|
||||
title: "失败通知",
|
||||
type: "text",
|
||||
form: {
|
||||
value: 0,
|
||||
component: {
|
||||
name: NotificationSelector,
|
||||
vModel: "modelValue",
|
||||
on: {
|
||||
selectedChange({ $event, form }) {
|
||||
form.notificationTarget = $event;
|
||||
},
|
||||
},
|
||||
},
|
||||
order: 101,
|
||||
helper: "任务执行失败实时提醒",
|
||||
},
|
||||
},
|
||||
groupId: {
|
||||
title: "流水线分组",
|
||||
type: "dict-select",
|
||||
dict: groupDictRef,
|
||||
form: {
|
||||
component: {
|
||||
name: GroupSelector,
|
||||
vModel: "modelValue",
|
||||
},
|
||||
order: 9999,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
crudOptions: {
|
||||
form: {
|
||||
doSubmit,
|
||||
wrapper: {
|
||||
width: 1350,
|
||||
saveRemind: false,
|
||||
title: t("certd.pipelineForm.createTitle"),
|
||||
},
|
||||
group: {
|
||||
groups: {
|
||||
more: {
|
||||
header: t("certd.pipelineForm.moreParams"),
|
||||
columns: moreParams,
|
||||
collapsed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
certApplyPlugin: {
|
||||
title: t("certd.plugin.selectTitle"),
|
||||
type: "dict-select",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ value: "CertApply", label: "JS-ACME" },
|
||||
{ value: "CertApplyLego", label: "Lego-ACME" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
order: 0,
|
||||
value: "CertApply",
|
||||
helper: {
|
||||
render: () => {
|
||||
return (
|
||||
<ul>
|
||||
<li>{t("certd.plugin.jsAcme")}</li>
|
||||
<li>{t("certd.plugin.legoAcme")}</li>
|
||||
</ul>
|
||||
);
|
||||
},
|
||||
},
|
||||
valueChange: {
|
||||
handle: async ({ form, value }) => {
|
||||
const config = await pluginStore.getPluginConfig({
|
||||
name: value,
|
||||
type: "builtIn",
|
||||
});
|
||||
if (config.sysSetting?.input) {
|
||||
merge(form, config.sysSetting.input);
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
...inputs,
|
||||
triggerCron: {
|
||||
title: t("certd.pipelineForm.triggerCronTitle"),
|
||||
type: "text",
|
||||
form: {
|
||||
value: `0 ${randomMin} ${randomHour} * * *`,
|
||||
component: {
|
||||
name: "cron-editor",
|
||||
vModel: "modelValue",
|
||||
placeholder: "0 0 4 * * *",
|
||||
},
|
||||
helper: t("certd.pipelineForm.triggerCronHelper"),
|
||||
order: 100,
|
||||
},
|
||||
},
|
||||
notification: {
|
||||
title: t("certd.pipelineForm.notificationTitle"),
|
||||
type: "text",
|
||||
form: {
|
||||
value: 0,
|
||||
component: {
|
||||
name: NotificationSelector,
|
||||
vModel: "modelValue",
|
||||
on: {
|
||||
selectedChange({ $event, form }) {
|
||||
form.notificationTarget = $event;
|
||||
},
|
||||
},
|
||||
},
|
||||
order: 101,
|
||||
helper: t("certd.pipelineForm.notificationHelper"),
|
||||
},
|
||||
},
|
||||
groupId: {
|
||||
title: t("certd.pipelineForm.groupIdTitle"),
|
||||
type: "dict-select",
|
||||
dict: groupDictRef,
|
||||
form: {
|
||||
component: {
|
||||
name: GroupSelector,
|
||||
vModel: "modelValue",
|
||||
},
|
||||
order: 9999,
|
||||
},
|
||||
}
|
||||
|
||||
async function getCertPlugins() {
|
||||
const pluginGroup = await pluginStore.getGroups();
|
||||
const pluginGroups: { [key: string]: PluginGroup } = pluginGroup.groups;
|
||||
const certPluginGroup = pluginGroups.cert;
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const certPlugins = [];
|
||||
for (const plugin of certPluginGroup.plugins) {
|
||||
const detail: any = await pluginStore.getPluginDefine(plugin.name);
|
||||
certPlugins.push(detail);
|
||||
}
|
||||
return certPlugins;
|
||||
}
|
||||
async function getCertPlugins() {
|
||||
const pluginGroup = await pluginStore.getGroups();
|
||||
const pluginGroups: { [key: string]: PluginGroup } = pluginGroup.groups;
|
||||
const certPluginGroup = pluginGroups.cert;
|
||||
|
||||
async function openAddCertdPipelineDialog(req: { defaultGroupId?: number }) {
|
||||
//检查是否流水线数量超出限制
|
||||
await checkPipelineLimit();
|
||||
const certPlugins = [];
|
||||
for (const plugin of certPluginGroup.plugins) {
|
||||
const detail: any = await pluginStore.getPluginDefine(plugin.name);
|
||||
certPlugins.push(detail);
|
||||
}
|
||||
return certPlugins;
|
||||
}
|
||||
|
||||
const wrapperRef = ref();
|
||||
function getFormData() {
|
||||
if (!wrapperRef.value) {
|
||||
return null;
|
||||
}
|
||||
return wrapperRef.value.getFormData();
|
||||
}
|
||||
async function openAddCertdPipelineDialog(req: { defaultGroupId?: number }) {
|
||||
//检查是否流水线数量超出限制
|
||||
await checkPipelineLimit();
|
||||
|
||||
async function doSubmit({ form }: any) {
|
||||
// const certDetail = readCertDetail(form.cert.crt);
|
||||
// 添加certd pipeline
|
||||
const pluginInput = omit(form, ["triggerCron", "notification", "notificationTarget", "certApplyPlugin", "groupId"]);
|
||||
const wrapperRef = ref();
|
||||
function getFormData() {
|
||||
if (!wrapperRef.value) {
|
||||
return null;
|
||||
}
|
||||
return wrapperRef.value.getFormData();
|
||||
}
|
||||
|
||||
async function doSubmit({ form }: any) {
|
||||
// const certDetail = readCertDetail(form.cert.crt);
|
||||
// 添加certd pipeline
|
||||
const pluginInput = omit(form, ["triggerCron", "notification", "notificationTarget", "certApplyPlugin", "groupId"]);
|
||||
let pipeline: any = {
|
||||
title: form.domains[0] + "证书自动化",
|
||||
runnableType: "pipeline",
|
||||
@@ -296,38 +301,38 @@ export function useCertPipelineCreator() {
|
||||
],
|
||||
};
|
||||
|
||||
pipeline = fillPipelineByDefaultForm(pipeline, form);
|
||||
pipeline = fillPipelineByDefaultForm(pipeline, form);
|
||||
|
||||
pipeline = setRunnableIds(pipeline);
|
||||
const groupId = form.groupId;
|
||||
const id = await api.Save({
|
||||
title: pipeline.title,
|
||||
content: JSON.stringify(pipeline),
|
||||
keepHistoryCount: 30,
|
||||
type: "cert",
|
||||
groupId,
|
||||
});
|
||||
if (form.email) {
|
||||
try {
|
||||
//创建一个默认的邮件通知
|
||||
const notificationApi = createNotificationApi();
|
||||
await notificationApi.GetOrCreateDefault({ email: form.email });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
message.success("创建成功,请添加证书部署任务");
|
||||
router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } });
|
||||
}
|
||||
const certPlugins = await getCertPlugins();
|
||||
const { crudOptions } = createCrudOptions(certPlugins, getFormData, doSubmit);
|
||||
//@ts-ignore
|
||||
crudOptions.columns.groupId.form.value = req.defaultGroupId || undefined;
|
||||
const wrapper = await openCrudFormDialog({ crudOptions });
|
||||
wrapperRef.value = wrapper;
|
||||
}
|
||||
const groupId = form.groupId;
|
||||
const id = await api.Save({
|
||||
title: pipeline.title,
|
||||
content: JSON.stringify(pipeline),
|
||||
keepHistoryCount: 30,
|
||||
type: "cert",
|
||||
groupId,
|
||||
});
|
||||
if (form.email) {
|
||||
try {
|
||||
//创建一个默认的邮件通知
|
||||
const notificationApi = createNotificationApi();
|
||||
await notificationApi.GetOrCreateDefault({ email: form.email });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
message.success("创建成功,请添加证书部署任务");
|
||||
router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } });
|
||||
}
|
||||
const certPlugins = await getCertPlugins();
|
||||
const { crudOptions } = createCrudOptions(certPlugins, getFormData, doSubmit);
|
||||
//@ts-ignore
|
||||
crudOptions.columns.groupId.form.value = req.defaultGroupId || undefined;
|
||||
const wrapper = await openCrudFormDialog({ crudOptions });
|
||||
wrapperRef.value = wrapper;
|
||||
}
|
||||
|
||||
return {
|
||||
openAddCertdPipelineDialog,
|
||||
};
|
||||
return {
|
||||
openAddCertdPipelineDialog,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,62 +1,70 @@
|
||||
<template>
|
||||
<fs-button icon="mdi:format-list-group" type="link" text="修改定时" @click="openFormDialog"></fs-button>
|
||||
<fs-button icon="mdi:format-list-group" type="link" :text="t('certd.editSchedule')"
|
||||
@click="openFormDialog"></fs-button>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as api from "../api";
|
||||
import { useFormWrapper } from "@fast-crud/fast-crud";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
selectedRowKeys: any[];
|
||||
selectedRowKeys: any[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: any;
|
||||
change: any;
|
||||
}>();
|
||||
async function batchUpdateRequest(form: any) {
|
||||
await api.BatchUpdateTrigger(props.selectedRowKeys, {
|
||||
title: "定时触发",
|
||||
type: "timer",
|
||||
props: form.props,
|
||||
});
|
||||
emit("change");
|
||||
await api.BatchUpdateTrigger(props.selectedRowKeys, {
|
||||
title: "定时触发",
|
||||
type: "timer",
|
||||
props: form.props,
|
||||
});
|
||||
emit("change");
|
||||
}
|
||||
|
||||
|
||||
const { openCrudFormDialog } = useFormWrapper();
|
||||
|
||||
async function openFormDialog() {
|
||||
const crudOptions: any = {
|
||||
columns: {
|
||||
"props.cron": {
|
||||
title: "定时",
|
||||
form: {
|
||||
component: {
|
||||
name: "cron-editor",
|
||||
vModel: "modelValue",
|
||||
},
|
||||
rules: [{ required: true, message: "请选择定时Cron" }],
|
||||
},
|
||||
},
|
||||
},
|
||||
form: {
|
||||
mode: "edit",
|
||||
//@ts-ignore
|
||||
async doSubmit({ form }) {
|
||||
await batchUpdateRequest(form);
|
||||
},
|
||||
col: {
|
||||
span: 22,
|
||||
},
|
||||
labelCol: {
|
||||
style: {
|
||||
width: "100px",
|
||||
},
|
||||
},
|
||||
wrapper: {
|
||||
title: "批量修改定时",
|
||||
width: 600,
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
await openCrudFormDialog({ crudOptions });
|
||||
const crudOptions: any = {
|
||||
columns: {
|
||||
"props.cron": {
|
||||
title: t("certd.schedule"),
|
||||
form: {
|
||||
component: {
|
||||
name: "cron-editor",
|
||||
vModel: "modelValue",
|
||||
},
|
||||
rules: [{ required: true, message: t("certd.selectCron") }],
|
||||
},
|
||||
},
|
||||
},
|
||||
form: {
|
||||
mode: "edit",
|
||||
//@ts-ignore
|
||||
async doSubmit({ form }) {
|
||||
await batchUpdateRequest(form);
|
||||
},
|
||||
col: {
|
||||
span: 22,
|
||||
},
|
||||
labelCol: {
|
||||
style: {
|
||||
width: "100px",
|
||||
},
|
||||
},
|
||||
wrapper: {
|
||||
title: t("certd.batchEditSchedule"),
|
||||
width: 600,
|
||||
},
|
||||
},
|
||||
|
||||
} as any;
|
||||
await openCrudFormDialog({ crudOptions });
|
||||
}
|
||||
</script>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,130 +5,131 @@ import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, Edi
|
||||
import { pipelineGroupApi } from "./api";
|
||||
|
||||
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
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 { 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;
|
||||
};
|
||||
const addRequest = async (req: AddReq) => {
|
||||
const { form } = req;
|
||||
const res = await api.AddObj(form);
|
||||
return res;
|
||||
};
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
settings: {
|
||||
plugins: {
|
||||
mobile: {
|
||||
props: {
|
||||
rowHandle: {
|
||||
width: 160,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
form: {
|
||||
labelCol: {
|
||||
//固定label宽度
|
||||
span: null,
|
||||
style: {
|
||||
width: "100px",
|
||||
},
|
||||
},
|
||||
col: {
|
||||
span: 22,
|
||||
},
|
||||
wrapper: {
|
||||
width: 600,
|
||||
},
|
||||
},
|
||||
rowHandle: {
|
||||
width: 200,
|
||||
group: {
|
||||
editable: {
|
||||
edit: {
|
||||
text: "编辑",
|
||||
order: -1,
|
||||
type: "primary",
|
||||
click({ row, index }) {
|
||||
crudExpose.openEdit({
|
||||
index,
|
||||
row,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
table: {
|
||||
editable: {
|
||||
enabled: true,
|
||||
mode: "cell",
|
||||
exclusive: true,
|
||||
//排他式激活效果,将其他行的编辑状态触发保存
|
||||
exclusiveEffect: "save", //自动保存其他行编辑状态,cancel = 自动关闭其他行编辑状态
|
||||
async updateCell(opts) {
|
||||
const { row, key, value } = opts;
|
||||
//如果是添加,需要返回{[rowKey]:xxx},比如:{id:2}
|
||||
return await api.UpdateObj({ id: row.id, [key]: value });
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
editable: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
name: {
|
||||
title: "分组名称",
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: "text",
|
||||
form: {
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: "请输入分组名称",
|
||||
},
|
||||
],
|
||||
},
|
||||
column: {
|
||||
width: 400,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return {
|
||||
crudOptions: {
|
||||
settings: {
|
||||
plugins: {
|
||||
mobile: {
|
||||
props: {
|
||||
rowHandle: {
|
||||
width: 160,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
form: {
|
||||
labelCol: {
|
||||
//固定label宽度
|
||||
span: null,
|
||||
style: {
|
||||
width: "100px",
|
||||
},
|
||||
},
|
||||
col: {
|
||||
span: 22,
|
||||
},
|
||||
wrapper: {
|
||||
width: 600,
|
||||
},
|
||||
},
|
||||
rowHandle: {
|
||||
width: 200,
|
||||
group: {
|
||||
editable: {
|
||||
edit: {
|
||||
text: t('certd.edit'),
|
||||
order: -1,
|
||||
type: "primary",
|
||||
click({ row, index }) {
|
||||
crudExpose.openEdit({
|
||||
index,
|
||||
row,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
table: {
|
||||
editable: {
|
||||
enabled: true,
|
||||
mode: "cell",
|
||||
exclusive: true,
|
||||
//排他式激活效果,将其他行的编辑状态触发保存
|
||||
exclusiveEffect: "save", //自动保存其他行编辑状态,cancel = 自动关闭其他行编辑状态
|
||||
async updateCell(opts) {
|
||||
const { row, key, value } = opts;
|
||||
//如果是添加,需要返回{[rowKey]:xxx},比如:{id:2}
|
||||
return await api.UpdateObj({ id: row.id, [key]: value });
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
editable: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
name: {
|
||||
title: t('certd.groupName'),
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: "text",
|
||||
form: {
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: t('certd.enterGroupName'),
|
||||
},
|
||||
],
|
||||
},
|
||||
column: {
|
||||
width: 400,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<fs-page class="page-cert">
|
||||
<template #header>
|
||||
<div class="title">我的流水线</div>
|
||||
<div class="title">{{ t("certd.myPipelines") }}</div>
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
<div v-if="selectedRowKeys.length > 0" class="batch-actions">
|
||||
<div class="batch-actions-inner">
|
||||
<span> 已选择 {{ selectedRowKeys.length }} 项 </span>
|
||||
<fs-button icon="ion:trash-outline" class="color-red" type="link" text="批量删除" @click="batchDelete"></fs-button>
|
||||
<fs-button icon="icon-park-outline:replay-music" class="need-plus" type="link" text="强制重新运行" @click="batchRerun"></fs-button>
|
||||
<span>{{ t("certd.selectedCount", { count: selectedRowKeys.length }) }}</span>
|
||||
<fs-button icon="ion:trash-outline" class="color-red" type="link" :text="t('certd.batchDelete')" @click="batchDelete"></fs-button>
|
||||
<fs-button icon="icon-park-outline:replay-music" class="need-plus" type="link" :text="t('certd.batchForceRerun')" @click="batchRerun"></fs-button>
|
||||
<change-group class="color-green" :selected-row-keys="selectedRowKeys" @change="batchFinished"></change-group>
|
||||
<change-notification class="color-green" :selected-row-keys="selectedRowKeys" @change="batchFinished"></change-notification>
|
||||
<change-trigger class="color-green" :selected-row-keys="selectedRowKeys" @change="batchFinished"></change-trigger>
|
||||
@@ -16,12 +16,13 @@
|
||||
</div>
|
||||
<template #actionbar-right> </template>
|
||||
<template #form-bottom>
|
||||
<div>申请证书</div>
|
||||
<div>{{ t("certd.applyCertificate") }}</div>
|
||||
</template>
|
||||
</fs-crud>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onActivated, onMounted, ref } from "vue";
|
||||
import { dict, useFs } from "@fast-crud/fast-crud";
|
||||
@@ -31,6 +32,9 @@ import ChangeGroup from "./components/change-group.vue";
|
||||
import ChangeTrigger from "./components/change-trigger.vue";
|
||||
import { Modal, notification } from "ant-design-vue";
|
||||
import * as api from "./api";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
import ChangeNotification from "/@/views/certd/pipeline/components/change-notification.vue";
|
||||
|
||||
defineOptions({
|
||||
|
||||
+135
-163
@@ -2,9 +2,11 @@
|
||||
<a-drawer v-model:open="notificationDrawerVisible" placement="right" :closable="true" width="600px" class="pi-notification-form" @after-open-change="notificationDrawerOnAfterVisibleChange">
|
||||
<template #title>
|
||||
<div>
|
||||
编辑通知
|
||||
{{ t("certd.edit_notification") }}
|
||||
<a-button v-if="mode === 'edit'" @click="notificationDelete()">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -15,7 +17,7 @@
|
||||
v-if="currentNotification.type === 'email'"
|
||||
v-model="currentNotification.type"
|
||||
:item="{
|
||||
title: '类型',
|
||||
title: t('certd.type'),
|
||||
key: 'type',
|
||||
value: 'email',
|
||||
component: {
|
||||
@@ -23,17 +25,17 @@
|
||||
vModel: 'value',
|
||||
disabled: !editMode,
|
||||
options: [
|
||||
{ value: 'email', label: '邮件' },
|
||||
{ value: 'other', label: '其他通知方式' },
|
||||
{ value: 'email', label: t('certd.email') },
|
||||
{ value: 'other', label: t('certd.other_notification_method') },
|
||||
],
|
||||
},
|
||||
rules: [{ required: true, message: '此项必填' }],
|
||||
rules: [{ required: true, message: t('certd.required') }],
|
||||
}"
|
||||
/>
|
||||
<fs-form-item
|
||||
v-model="currentNotification.when"
|
||||
:item="{
|
||||
title: '触发时机',
|
||||
title: t('certd.trigger_time'),
|
||||
key: 'when',
|
||||
value: ['error'],
|
||||
component: {
|
||||
@@ -42,14 +44,14 @@
|
||||
disabled: !editMode,
|
||||
mode: 'multiple',
|
||||
options: [
|
||||
{ value: 'start', label: '开始时' },
|
||||
{ value: 'success', label: '成功时' },
|
||||
{ value: 'turnToSuccess', label: '失败转成功时' },
|
||||
{ value: 'error', label: '失败时' },
|
||||
{ value: 'start', label: t('certd.start_time') },
|
||||
{ value: 'success', label: t('certd.success_time') },
|
||||
{ value: 'turnToSuccess', label: t('certd.fail_to_success_time') },
|
||||
{ value: 'error', label: t('certd.fail_time') },
|
||||
],
|
||||
},
|
||||
helper: `建议仅选择'失败时'和'失败转成功'两种即可`,
|
||||
rules: [{ required: true, message: '此项必填' }],
|
||||
helper: t('certd.helper_suggest_fail_only'),
|
||||
rules: [{ required: true, message: t('certd.required') }],
|
||||
}"
|
||||
/>
|
||||
<pi-notification-form-email v-if="currentNotification.type === 'email'" ref="optionsRef" v-model:options="currentNotification.options"></pi-notification-form-email>
|
||||
@@ -58,22 +60,22 @@
|
||||
v-else
|
||||
v-model="currentNotification.notificationId"
|
||||
:item="{
|
||||
title: '通知配置',
|
||||
title: t('certd.notification_config'),
|
||||
key: 'notificationId',
|
||||
component: {
|
||||
disabled: !editMode,
|
||||
name: NotificationSelector,
|
||||
onSelectedChange,
|
||||
},
|
||||
helper: '请选择通知方式',
|
||||
rules: [{ required: true, message: '此项必填' }],
|
||||
helper: t('certd.please_select_notification'),
|
||||
rules: [{ required: true, message: t('certd.required') }],
|
||||
}"
|
||||
/>
|
||||
</a-form>
|
||||
|
||||
<template #footer>
|
||||
<a-form-item v-if="editMode" :wrapper-col="{ span: 14, offset: 4 }">
|
||||
<a-button type="primary" @click="notificationSave"> 确定 </a-button>
|
||||
<a-button type="primary" @click="notificationSave"> {{ t("certd.confirm") }} </a-button>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</pi-container>
|
||||
@@ -81,163 +83,133 @@
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import { Modal } from "ant-design-vue";
|
||||
import { ref, Ref } from "vue";
|
||||
import * as _ from "lodash-es";
|
||||
import { nanoid } from "nanoid";
|
||||
import PiNotificationFormEmail from "./pi-notification-form-email.vue";
|
||||
import NotificationSelector from "/@/views/certd/notification/notification-selector/index.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
export default {
|
||||
const { t } = useI18n();
|
||||
|
||||
defineOptions({
|
||||
name: "PiNotificationForm",
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: { NotificationSelector, PiNotificationFormEmail },
|
||||
props: {
|
||||
editMode: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
editMode: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(["update"]);
|
||||
|
||||
/**
|
||||
* notification drawer
|
||||
* @returns
|
||||
*/
|
||||
const mode = ref("add");
|
||||
const callback = ref();
|
||||
const currentNotification: Ref<any> = ref({ type: undefined, when: [], options: {}, notificationId: undefined, title: "" });
|
||||
const currentPlugin = ref({});
|
||||
const notificationFormRef = ref(null);
|
||||
const notificationDrawerVisible = ref(false);
|
||||
const optionsRef = ref();
|
||||
const rules = ref({
|
||||
type: [
|
||||
{
|
||||
type: "string",
|
||||
required: true,
|
||||
message: t("certd.please_select_type"),
|
||||
},
|
||||
},
|
||||
emits: ["update"],
|
||||
setup(props: any, context: any) {
|
||||
/**
|
||||
* notification drawer
|
||||
* @returns
|
||||
*/
|
||||
function useNotificationForm() {
|
||||
const mode = ref("add");
|
||||
const callback = ref();
|
||||
const currentNotification: Ref<any> = ref({ type: undefined, when: [], options: {}, notificationId: undefined, title: "" });
|
||||
const currentPlugin = ref({});
|
||||
const notificationFormRef = ref(null);
|
||||
const notificationDrawerVisible = ref(false);
|
||||
const optionsRef = ref();
|
||||
const rules = ref({
|
||||
type: [
|
||||
{
|
||||
type: "string",
|
||||
required: true,
|
||||
message: "请选择类型",
|
||||
},
|
||||
],
|
||||
when: [
|
||||
{
|
||||
type: "string",
|
||||
required: true,
|
||||
message: "请选择通知时机",
|
||||
},
|
||||
],
|
||||
notificationId: [
|
||||
{
|
||||
type: "number",
|
||||
required: true,
|
||||
message: "请选择通知配置",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const notificationDrawerShow = () => {
|
||||
notificationDrawerVisible.value = true;
|
||||
};
|
||||
const notificationDrawerClose = () => {
|
||||
notificationDrawerVisible.value = false;
|
||||
};
|
||||
|
||||
const notificationDrawerOnAfterVisibleChange = (val: any) => {
|
||||
console.log("notificationDrawerOnAfterVisibleChange", val);
|
||||
};
|
||||
|
||||
const notificationOpen = (notification: any, emit: any) => {
|
||||
callback.value = emit;
|
||||
currentNotification.value = _.cloneDeep(notification);
|
||||
console.log("currentNotificationOpen", currentNotification.value);
|
||||
notificationDrawerShow();
|
||||
};
|
||||
|
||||
const notificationAdd = (emit: any) => {
|
||||
mode.value = "add";
|
||||
const notification = { id: nanoid(), type: "custom", when: ["error", "turnToSuccess"] };
|
||||
notificationOpen(notification, emit);
|
||||
};
|
||||
|
||||
const notificationEdit = (notification: any, emit: any) => {
|
||||
mode.value = "edit";
|
||||
notificationOpen(notification, emit);
|
||||
};
|
||||
|
||||
const notificationView = (notification: any, emit: any) => {
|
||||
mode.value = "view";
|
||||
notificationOpen(notification, emit);
|
||||
};
|
||||
|
||||
const notificationSave = async (e: any) => {
|
||||
if (optionsRef.value) {
|
||||
currentNotification.value.options = await optionsRef.value.getValue();
|
||||
}
|
||||
|
||||
console.log("currentNotificationSave", currentNotification.value);
|
||||
try {
|
||||
await notificationFormRef.value.validate();
|
||||
} catch (e) {
|
||||
console.error("表单验证失败:", e);
|
||||
return;
|
||||
}
|
||||
|
||||
callback.value("save", currentNotification.value);
|
||||
notificationDrawerClose();
|
||||
};
|
||||
|
||||
const notificationDelete = () => {
|
||||
Modal.confirm({
|
||||
title: "确认",
|
||||
content: `确定要删除此触发器吗?`,
|
||||
async onOk() {
|
||||
callback.value("delete");
|
||||
notificationDrawerClose();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const blankFn = () => {
|
||||
return {};
|
||||
};
|
||||
|
||||
function onSelectedChange(node: any) {
|
||||
currentNotification.value.title = node?.name || null;
|
||||
}
|
||||
return {
|
||||
notificationFormRef,
|
||||
onSelectedChange,
|
||||
mode,
|
||||
notificationAdd,
|
||||
notificationEdit,
|
||||
notificationView,
|
||||
notificationDrawerShow,
|
||||
notificationDrawerVisible,
|
||||
notificationDrawerOnAfterVisibleChange,
|
||||
currentNotification,
|
||||
currentPlugin,
|
||||
notificationSave,
|
||||
notificationDelete,
|
||||
rules,
|
||||
blankFn,
|
||||
optionsRef,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...useNotificationForm(),
|
||||
labelCol: { span: 6 },
|
||||
wrapperCol: { span: 16 },
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
NotificationSelector() {
|
||||
return NotificationSelector;
|
||||
],
|
||||
when: [
|
||||
{
|
||||
type: "string",
|
||||
required: true,
|
||||
message: t("certd.please_select_trigger_time"),
|
||||
},
|
||||
},
|
||||
],
|
||||
notificationId: [
|
||||
{
|
||||
type: "number",
|
||||
required: true,
|
||||
message: t("certd.please_select_notification_config"),
|
||||
},
|
||||
],
|
||||
});
|
||||
const notificationDrawerShow = () => {
|
||||
notificationDrawerVisible.value = true;
|
||||
};
|
||||
const notificationDrawerClose = () => {
|
||||
notificationDrawerVisible.value = false;
|
||||
};
|
||||
|
||||
const notificationDrawerOnAfterVisibleChange = (val: any) => {
|
||||
console.log("notificationDrawerOnAfterVisibleChange", val);
|
||||
};
|
||||
|
||||
const notificationOpen = (notification: any, emit: any) => {
|
||||
callback.value = emit;
|
||||
currentNotification.value = _.cloneDeep(notification);
|
||||
console.log("currentNotificationOpen", currentNotification.value);
|
||||
notificationDrawerShow();
|
||||
};
|
||||
|
||||
const notificationAdd = (emit: any) => {
|
||||
mode.value = "add";
|
||||
const notification = { id: nanoid(), type: "custom", when: ["error", "turnToSuccess"] };
|
||||
notificationOpen(notification, emit);
|
||||
};
|
||||
|
||||
const notificationEdit = (notification: any, emit: any) => {
|
||||
mode.value = "edit";
|
||||
notificationOpen(notification, emit);
|
||||
};
|
||||
|
||||
const notificationView = (notification: any, emit: any) => {
|
||||
mode.value = "view";
|
||||
notificationOpen(notification, emit);
|
||||
};
|
||||
|
||||
const notificationSave = async (e: any) => {
|
||||
if (optionsRef.value) {
|
||||
currentNotification.value.options = await optionsRef.value.getValue();
|
||||
}
|
||||
|
||||
console.log("currentNotificationSave", currentNotification.value);
|
||||
try {
|
||||
await notificationFormRef.value.validate();
|
||||
} catch (e) {
|
||||
console.error("表单验证失败:", e);
|
||||
return;
|
||||
}
|
||||
|
||||
callback.value("save", currentNotification.value);
|
||||
notificationDrawerClose();
|
||||
};
|
||||
|
||||
const notificationDelete = () => {
|
||||
Modal.confirm({
|
||||
title: t("certd.confirm"),
|
||||
content: t("certd.confirm_delete_trigger"),
|
||||
async onOk() {
|
||||
callback.value("delete");
|
||||
notificationDrawerClose();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const blankFn = () => {
|
||||
return {};
|
||||
};
|
||||
|
||||
function onSelectedChange(node: any) {
|
||||
currentNotification.value.title = node?.name || null;
|
||||
}
|
||||
|
||||
const labelCol = { span: 6 };
|
||||
const wrapperCol = { span: 16 };
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
||||
+40
-39
@@ -1,36 +1,37 @@
|
||||
<template>
|
||||
<div>
|
||||
<fs-form-item
|
||||
v-model="optionsFormState.receivers"
|
||||
:item="{
|
||||
title: '收件邮箱',
|
||||
key: 'type',
|
||||
component: {
|
||||
name: 'a-select',
|
||||
vModel: 'value',
|
||||
mode: 'tags',
|
||||
open: false
|
||||
},
|
||||
helper: '输入你的收件邮箱地址,支持多个邮箱',
|
||||
rules: [{ required: true, message: '此项必填' }]
|
||||
}"
|
||||
/>
|
||||
<div>
|
||||
<fs-form-item v-model="optionsFormState.receivers" :item="{
|
||||
title: t('certd.email.title'),
|
||||
key: 'type',
|
||||
component: {
|
||||
name: 'a-select',
|
||||
vModel: 'value',
|
||||
mode: 'tags',
|
||||
open: false
|
||||
},
|
||||
helper: t('certd.email.helper'),
|
||||
rules: [{ required: true, message: t('certd.email.required') }]
|
||||
}" />
|
||||
|
||||
<a-alert v-if="!settingStore.isPlus" class="m-1" type="info">
|
||||
<template #message> 还没有配置邮件服务器?<router-link :to="{ path: '/sys/settings/email' }">现在就去</router-link> </template>
|
||||
</a-alert>
|
||||
</div>
|
||||
<a-alert v-if="!settingStore.isPlus" class="m-1" type="info">
|
||||
<template #message> 还没有配置邮件服务器?<router-link :to="{ path: '/sys/settings/email' }">现在就去</router-link>
|
||||
</template>
|
||||
</a-alert>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Ref, ref, watch } from "vue";
|
||||
import { useUserStore } from "/@/store/user";
|
||||
import { useSettingStore } from "/@/store/settings";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
options: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => {}
|
||||
}
|
||||
options: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => { }
|
||||
}
|
||||
});
|
||||
|
||||
const settingStore = useSettingStore();
|
||||
@@ -38,30 +39,30 @@ const settingStore = useSettingStore();
|
||||
const optionsFormState: Ref<any> = ref({});
|
||||
|
||||
watch(
|
||||
() => {
|
||||
return props.options;
|
||||
},
|
||||
() => {
|
||||
optionsFormState.value = {
|
||||
...props.options
|
||||
};
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
() => {
|
||||
return props.options;
|
||||
},
|
||||
() => {
|
||||
optionsFormState.value = {
|
||||
...props.options
|
||||
};
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits(["change"]);
|
||||
function doEmit() {
|
||||
emit("change", { ...optionsFormState.value });
|
||||
emit("change", { ...optionsFormState.value });
|
||||
}
|
||||
|
||||
function getValue() {
|
||||
return { ...optionsFormState.value };
|
||||
return { ...optionsFormState.value };
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
doEmit,
|
||||
getValue
|
||||
doEmit,
|
||||
getValue
|
||||
});
|
||||
</script>
|
||||
|
||||
+18
-14
@@ -2,9 +2,11 @@
|
||||
<a-drawer v-model:open="triggerDrawerVisible" placement="right" :closable="true" width="650px" class="pi-trigger-form" @after-open-change="triggerDrawerOnAfterVisibleChange">
|
||||
<template #title>
|
||||
<div>
|
||||
编辑触发器
|
||||
{{ t("certd.editTrigger") }}
|
||||
<a-button v-if="mode === 'edit'" @click="triggerDelete()">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -14,45 +16,45 @@
|
||||
<fs-form-item
|
||||
v-model="currentTrigger.title"
|
||||
:item="{
|
||||
title: '触发器名称',
|
||||
title: t('certd.triggerName'),
|
||||
key: 'title',
|
||||
component: {
|
||||
name: 'a-input',
|
||||
vModel: 'value',
|
||||
disabled: !editMode,
|
||||
},
|
||||
rules: [{ required: true, message: '此项必填' }],
|
||||
rules: [{ required: true, message: t('certd.requiredField') }],
|
||||
}"
|
||||
/>
|
||||
|
||||
<fs-form-item
|
||||
v-model="currentTrigger.type"
|
||||
:item="{
|
||||
title: '类型',
|
||||
title: t('certd.type'),
|
||||
key: 'type',
|
||||
value: 'timer',
|
||||
component: {
|
||||
name: 'a-select',
|
||||
vModel: 'value',
|
||||
disabled: !editMode,
|
||||
options: [{ value: 'timer', label: '定时' }],
|
||||
options: [{ value: 'timer', label: t('certd.schedule') }],
|
||||
},
|
||||
rules: [{ required: true, message: '此项必填' }],
|
||||
rules: [{ required: true, message: t('certd.requiredField') }],
|
||||
}"
|
||||
/>
|
||||
|
||||
<fs-form-item
|
||||
v-model="currentTrigger.props.cron"
|
||||
:item="{
|
||||
title: '定时脚本',
|
||||
title: t('certd.cronForm.title'),
|
||||
key: 'props.cron',
|
||||
component: {
|
||||
disabled: !editMode,
|
||||
name: 'cron-editor',
|
||||
vModel: 'modelValue',
|
||||
},
|
||||
helper: '点击上面的按钮,选择每天几点定时执行。\n建议设置为每天触发一次,证书未到期之前任务会跳过,不会重复执行',
|
||||
rules: [{ required: true, message: '此项必填' }],
|
||||
helper: t('certd.cronForm.helper'),
|
||||
rules: [{ required: true, message: t('certd.cronForm.required') }],
|
||||
}"
|
||||
/>
|
||||
</a-form>
|
||||
@@ -71,6 +73,7 @@
|
||||
import { message, Modal } from "ant-design-vue";
|
||||
import { inject, ref } from "vue";
|
||||
import * as _ from "lodash-es";
|
||||
import { useI18n } from "/src/locales/";
|
||||
import { nanoid } from "nanoid";
|
||||
export default {
|
||||
name: "PiTriggerForm",
|
||||
@@ -86,6 +89,7 @@ export default {
|
||||
* trigger drawer
|
||||
* @returns
|
||||
*/
|
||||
const { t } = useI18n();
|
||||
function useTriggerForm() {
|
||||
const mode = ref("add");
|
||||
const callback = ref();
|
||||
@@ -98,7 +102,7 @@ export default {
|
||||
{
|
||||
type: "string",
|
||||
required: true,
|
||||
message: "请输入名称",
|
||||
message: t("certd.enterName"),
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -123,7 +127,7 @@ export default {
|
||||
|
||||
const triggerAdd = emit => {
|
||||
mode.value = "add";
|
||||
const trigger = { id: nanoid(), title: "定时触发", type: "timer", props: {} };
|
||||
const trigger = { id: nanoid(), title: t("certd.timerTrigger"), type: "timer", props: {} };
|
||||
triggerOpen(trigger, emit);
|
||||
};
|
||||
|
||||
@@ -152,8 +156,8 @@ export default {
|
||||
|
||||
const triggerDelete = () => {
|
||||
Modal.confirm({
|
||||
title: "确认",
|
||||
content: `确定要删除此触发器吗?`,
|
||||
title: t("certd.confirm"),
|
||||
content: t("certd.confirmDeleteTrigger"),
|
||||
async onOk() {
|
||||
callback.value("delete");
|
||||
triggerDrawerClose();
|
||||
|
||||
@@ -1,137 +1,139 @@
|
||||
import * as api from "./api";
|
||||
import { Ref, ref } from "vue";
|
||||
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
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 { 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 addRequest = async ({ form }: AddReq) => {
|
||||
const res = await api.AddObj(form);
|
||||
return res;
|
||||
};
|
||||
|
||||
const selectedRowKeys: Ref<any[]> = ref([]);
|
||||
context.selectedRowKeys = selectedRowKeys;
|
||||
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,
|
||||
},
|
||||
// tabs: {
|
||||
// name: "status",
|
||||
// show: true,
|
||||
// },
|
||||
rowHandle: {
|
||||
minWidth: 200,
|
||||
fixed: "right",
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
column: {
|
||||
width: 80,
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
domain: {
|
||||
title: "托管的子域名",
|
||||
type: "text",
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
form: {
|
||||
helper: {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
如果您不理解什么是子域托管,可以参考文档
|
||||
<a href={"https://help.aliyun.com/zh/dns/subdomain-management"} target={"_blank"}>
|
||||
子域管理
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
editForm: {
|
||||
component: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
disabled: {
|
||||
title: "是否禁用",
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ value: false, label: "启用", color: "green" },
|
||||
{ value: true, label: "禁用", color: "gray" },
|
||||
],
|
||||
}),
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
form: {
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
createTime: {
|
||||
title: "创建时间",
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 160,
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
updateTime: {
|
||||
title: "更新时间",
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
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,
|
||||
},
|
||||
// tabs: {
|
||||
// name: "status",
|
||||
// show: true,
|
||||
// },
|
||||
rowHandle: {
|
||||
minWidth: 200,
|
||||
fixed: "right",
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
column: {
|
||||
width: 80,
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
domain: {
|
||||
title: t('certd.subdomainHosted'),
|
||||
type: "text",
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
form: {
|
||||
helper: {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{t('certd.subdomainHelpText')}
|
||||
<a href={"https://help.aliyun.com/zh/dns/subdomain-management"} target={"_blank"}>
|
||||
{t('certd.subdomainManagement')}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
editForm: {
|
||||
component: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
disabled: {
|
||||
title: t('certd.isDisabled'),
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ value: false, label: t('certd.enabled'), color: "green" },
|
||||
{ value: true, label: t('certd.disabled'), color: "gray" },
|
||||
],
|
||||
}),
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
form: {
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
createTime: {
|
||||
title: t('certd.createTime'),
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 160,
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
updateTime: {
|
||||
title: t('certd.updateTime'),
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<template>
|
||||
<fs-page class="page-cert">
|
||||
<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>
|
||||
<fs-page class="page-cert">
|
||||
<template #header>
|
||||
<div class="title">
|
||||
{{ t('certd.subdomainHosting') }}
|
||||
<span class="sub">{{ t('certd.subdomainHostingHint') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
<template #pagination-left>
|
||||
<a-tooltip :title="t('certd.batchDelete')">
|
||||
<fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</fs-crud>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -22,36 +22,40 @@ import { useFs } from "@fast-crud/fast-crud";
|
||||
import createCrudOptions from "./crud";
|
||||
import { message, Modal } from "ant-design-vue";
|
||||
import { DeleteBatch } from "./api";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
defineOptions({
|
||||
name: "CnameRecord",
|
||||
name: "CnameRecord",
|
||||
});
|
||||
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("请先勾选记录");
|
||||
}
|
||||
if (selectedRowKeys.value?.length > 0) {
|
||||
Modal.confirm({
|
||||
title: t('certd.confirm'),
|
||||
content: t('certd.batchDeleteConfirm', { count: selectedRowKeys.value.length }),
|
||||
async onOk() {
|
||||
await DeleteBatch(selectedRowKeys.value);
|
||||
message.info(t('certd.deleteSuccess'));
|
||||
crudExpose.doRefresh();
|
||||
selectedRowKeys.value = [];
|
||||
},
|
||||
});
|
||||
} else {
|
||||
message.error(t('certd.selectRecordFirst'));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
onActivated(async () => {
|
||||
await crudExpose.doRefresh();
|
||||
await crudExpose.doRefresh();
|
||||
});
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
|
||||
@@ -7,310 +7,310 @@ import DurationValue from "/@/views/sys/suite/product/duration-value.vue";
|
||||
import UserSuiteStatus from "/@/views/certd/suite/mine/user-suite-status.vue";
|
||||
import dayjs from "dayjs";
|
||||
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 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 addRequest = async (req: AddReq) => {
|
||||
const { form } = req;
|
||||
const res = await api.AddObj(form);
|
||||
return res;
|
||||
};
|
||||
|
||||
const router = useRouter();
|
||||
const router = useRouter();
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest
|
||||
},
|
||||
form: {
|
||||
labelCol: {
|
||||
//固定label宽度
|
||||
span: null,
|
||||
style: {
|
||||
width: "100px"
|
||||
}
|
||||
},
|
||||
col: {
|
||||
span: 22
|
||||
},
|
||||
wrapper: {
|
||||
width: 600
|
||||
}
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: { show: false },
|
||||
buy: {
|
||||
text: "购买",
|
||||
type: "primary",
|
||||
click() {
|
||||
router.push({
|
||||
path: "/certd/suite/buy"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
rowHandle: {
|
||||
width: 200,
|
||||
fixed: "right",
|
||||
buttons: {
|
||||
view: { show: false },
|
||||
copy: { show: false },
|
||||
edit: { show: false },
|
||||
remove: { show: false }
|
||||
// continue:{
|
||||
// text:"续期",
|
||||
// type:"link",
|
||||
// click(){
|
||||
// console.log("续期");
|
||||
// }
|
||||
// }
|
||||
}
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
search: {
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
editable: {
|
||||
disabled: true
|
||||
}
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
title: {
|
||||
title: "套餐名称",
|
||||
type: "text",
|
||||
search: {
|
||||
show: true
|
||||
},
|
||||
form: {
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 200
|
||||
}
|
||||
},
|
||||
productType: {
|
||||
title: "类型",
|
||||
type: "dict-select",
|
||||
editForm: {
|
||||
component: {
|
||||
disabled: true
|
||||
}
|
||||
},
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "套餐", value: "suite", color: "green" },
|
||||
{ label: "加量包", value: "addon", color: "blue" }
|
||||
]
|
||||
}),
|
||||
form: {
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 80,
|
||||
align: "center"
|
||||
},
|
||||
valueBuilder: ({ row }) => {
|
||||
if (row.content) {
|
||||
row.content = JSON.parse(row.content);
|
||||
}
|
||||
},
|
||||
valueResolve: ({ form }) => {
|
||||
if (form.content) {
|
||||
form.content = JSON.stringify(form.content);
|
||||
}
|
||||
}
|
||||
},
|
||||
"content.maxDomainCount": {
|
||||
title: "域名数量",
|
||||
type: "text",
|
||||
form: {
|
||||
key: ["content", "maxDomainCount"],
|
||||
component: {
|
||||
name: SuiteValueEdit,
|
||||
vModel: "modelValue",
|
||||
unit: "个"
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
component: {
|
||||
name: SuiteValue,
|
||||
vModel: "modelValue",
|
||||
unit: "个"
|
||||
},
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
"content.maxPipelineCount": {
|
||||
title: "流水线数量",
|
||||
type: "text",
|
||||
form: {
|
||||
key: ["content", "maxPipelineCount"],
|
||||
component: {
|
||||
name: SuiteValueEdit,
|
||||
vModel: "modelValue",
|
||||
unit: "条"
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
component: {
|
||||
name: SuiteValue,
|
||||
vModel: "modelValue",
|
||||
unit: "条"
|
||||
},
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
"content.maxDeployCount": {
|
||||
title: "部署次数",
|
||||
type: "text",
|
||||
form: {
|
||||
key: ["content", "maxDeployCount"],
|
||||
component: {
|
||||
name: SuiteValueEdit,
|
||||
vModel: "modelValue",
|
||||
unit: "次"
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
component: {
|
||||
name: SuiteValue,
|
||||
vModel: "modelValue",
|
||||
unit: "次",
|
||||
used: compute(({ row }) => {
|
||||
return row.deployCountUsed;
|
||||
})
|
||||
},
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
"content.maxMonitorCount": {
|
||||
title: "证书监控数量",
|
||||
type: "text",
|
||||
form: {
|
||||
key: ["content", "maxMonitorCount"],
|
||||
component: {
|
||||
name: SuiteValueEdit,
|
||||
vModel: "modelValue",
|
||||
unit: "个"
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 120,
|
||||
component: {
|
||||
name: SuiteValue,
|
||||
vModel: "modelValue",
|
||||
unit: "个"
|
||||
},
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
duration: {
|
||||
title: "时长",
|
||||
type: "text",
|
||||
form: {},
|
||||
column: {
|
||||
component: {
|
||||
name: DurationValue,
|
||||
vModel: "modelValue"
|
||||
},
|
||||
width: 100,
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
status: {
|
||||
title: "状态",
|
||||
type: "text",
|
||||
form: { show: false },
|
||||
column: {
|
||||
width: 100,
|
||||
align: "center",
|
||||
component: {
|
||||
name: UserSuiteStatus,
|
||||
userSuite: compute(({ row }) => {
|
||||
return row;
|
||||
}),
|
||||
currentSuite: context.currentSuite
|
||||
},
|
||||
conditionalRender: {
|
||||
match() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
activeTime: {
|
||||
title: "激活时间",
|
||||
type: "date",
|
||||
column: {
|
||||
width: 150
|
||||
}
|
||||
},
|
||||
expiresTime: {
|
||||
title: "过期时间",
|
||||
type: "date",
|
||||
column: {
|
||||
width: 150,
|
||||
component: {
|
||||
name: "expires-time-text",
|
||||
vModel: "value",
|
||||
mode: "tag",
|
||||
title: compute(({ value }) => {
|
||||
return dayjs(value).format("YYYY-MM-DD HH:mm:ss");
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
isPresent: {
|
||||
title: "是否赠送",
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "是", value: true, color: "success" },
|
||||
{ label: "否", value: false, color: "blue" }
|
||||
]
|
||||
}),
|
||||
form: {
|
||||
value: true
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
align: "center"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest
|
||||
},
|
||||
form: {
|
||||
labelCol: {
|
||||
//固定label宽度
|
||||
span: null,
|
||||
style: {
|
||||
width: "100px"
|
||||
}
|
||||
},
|
||||
col: {
|
||||
span: 22
|
||||
},
|
||||
wrapper: {
|
||||
width: 600
|
||||
}
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: { show: false },
|
||||
buy: {
|
||||
text: "购买",
|
||||
type: "primary",
|
||||
click() {
|
||||
router.push({
|
||||
path: "/certd/suite/buy"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
rowHandle: {
|
||||
width: 200,
|
||||
fixed: "right",
|
||||
buttons: {
|
||||
view: { show: false },
|
||||
copy: { show: false },
|
||||
edit: { show: false },
|
||||
remove: { show: false }
|
||||
// continue:{
|
||||
// text:"续期",
|
||||
// type:"link",
|
||||
// click(){
|
||||
// console.log("续期");
|
||||
// }
|
||||
// }
|
||||
}
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
search: {
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
editable: {
|
||||
disabled: true
|
||||
}
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
title: {
|
||||
title: "套餐名称",
|
||||
type: "text",
|
||||
search: {
|
||||
show: true
|
||||
},
|
||||
form: {
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 200
|
||||
}
|
||||
},
|
||||
productType: {
|
||||
title: "类型",
|
||||
type: "dict-select",
|
||||
editForm: {
|
||||
component: {
|
||||
disabled: true
|
||||
}
|
||||
},
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "套餐", value: "suite", color: "green" },
|
||||
{ label: "加量包", value: "addon", color: "blue" }
|
||||
]
|
||||
}),
|
||||
form: {
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 80,
|
||||
align: "center"
|
||||
},
|
||||
valueBuilder: ({ row }) => {
|
||||
if (row.content) {
|
||||
row.content = JSON.parse(row.content);
|
||||
}
|
||||
},
|
||||
valueResolve: ({ form }) => {
|
||||
if (form.content) {
|
||||
form.content = JSON.stringify(form.content);
|
||||
}
|
||||
}
|
||||
},
|
||||
"content.maxDomainCount": {
|
||||
title: "域名数量",
|
||||
type: "text",
|
||||
form: {
|
||||
key: ["content", "maxDomainCount"],
|
||||
component: {
|
||||
name: SuiteValueEdit,
|
||||
vModel: "modelValue",
|
||||
unit: "个"
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
component: {
|
||||
name: SuiteValue,
|
||||
vModel: "modelValue",
|
||||
unit: "个"
|
||||
},
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
"content.maxPipelineCount": {
|
||||
title: "流水线数量",
|
||||
type: "text",
|
||||
form: {
|
||||
key: ["content", "maxPipelineCount"],
|
||||
component: {
|
||||
name: SuiteValueEdit,
|
||||
vModel: "modelValue",
|
||||
unit: "条"
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
component: {
|
||||
name: SuiteValue,
|
||||
vModel: "modelValue",
|
||||
unit: "条"
|
||||
},
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
"content.maxDeployCount": {
|
||||
title: "部署次数",
|
||||
type: "text",
|
||||
form: {
|
||||
key: ["content", "maxDeployCount"],
|
||||
component: {
|
||||
name: SuiteValueEdit,
|
||||
vModel: "modelValue",
|
||||
unit: "次"
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
component: {
|
||||
name: SuiteValue,
|
||||
vModel: "modelValue",
|
||||
unit: "次",
|
||||
used: compute(({ row }) => {
|
||||
return row.deployCountUsed;
|
||||
})
|
||||
},
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
"content.maxMonitorCount": {
|
||||
title: "证书监控数量",
|
||||
type: "text",
|
||||
form: {
|
||||
key: ["content", "maxMonitorCount"],
|
||||
component: {
|
||||
name: SuiteValueEdit,
|
||||
vModel: "modelValue",
|
||||
unit: "个"
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 120,
|
||||
component: {
|
||||
name: SuiteValue,
|
||||
vModel: "modelValue",
|
||||
unit: "个"
|
||||
},
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
duration: {
|
||||
title: "时长",
|
||||
type: "text",
|
||||
form: {},
|
||||
column: {
|
||||
component: {
|
||||
name: DurationValue,
|
||||
vModel: "modelValue"
|
||||
},
|
||||
width: 100,
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
status: {
|
||||
title: "状态",
|
||||
type: "text",
|
||||
form: { show: false },
|
||||
column: {
|
||||
width: 100,
|
||||
align: "center",
|
||||
component: {
|
||||
name: UserSuiteStatus,
|
||||
userSuite: compute(({ row }) => {
|
||||
return row;
|
||||
}),
|
||||
currentSuite: context.currentSuite
|
||||
},
|
||||
conditionalRender: {
|
||||
match() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
activeTime: {
|
||||
title: "激活时间",
|
||||
type: "date",
|
||||
column: {
|
||||
width: 150
|
||||
}
|
||||
},
|
||||
expiresTime: {
|
||||
title: "过期时间",
|
||||
type: "date",
|
||||
column: {
|
||||
width: 150,
|
||||
component: {
|
||||
name: "expires-time-text",
|
||||
vModel: "value",
|
||||
mode: "tag",
|
||||
title: compute(({ value }) => {
|
||||
return dayjs(value).format("YYYY-MM-DD HH:mm:ss");
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
isPresent: {
|
||||
title: "是否赠送",
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "是", value: true, color: "success" },
|
||||
{ label: "否", value: false, color: "blue" }
|
||||
]
|
||||
}),
|
||||
form: {
|
||||
value: true
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
align: "center"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,32 +1,46 @@
|
||||
<template>
|
||||
<a-modal v-model:open="openRef" class="order-modal" title="订单确认" @ok="orderCreate">
|
||||
<a-modal v-model:open="openRef" class="order-modal" :title="$t('order.confirmTitle')" @ok="orderCreate">
|
||||
<div v-if="product" class="order-box">
|
||||
<div class="flex-o mt-5"><span class="label">套餐:</span>{{ product.title }}</div>
|
||||
<div class="flex-o mt-5"><span class="label">说明:</span>{{ product.intro }}</div>
|
||||
<div class="flex-o mt-5">
|
||||
<span class="label">规格:</span>
|
||||
<span class="label">{{$t('order.package')}}:</span>{{ product.title }}
|
||||
</div>
|
||||
<div class="flex-o mt-5">
|
||||
<span class="label">{{$t('order.description')}}:</span>{{ product.intro }}
|
||||
</div>
|
||||
<div class="flex-o mt-5">
|
||||
<span class="label">{{$t('order.specifications')}}:</span>
|
||||
<span class="flex-o flex-wrap">
|
||||
<span class="flex-o">流水线<suite-value class="ml-5" :model-value="product.content.maxPipelineCount" unit="条" />;</span>
|
||||
<span class="flex-o">域名<suite-value class="ml-5" :model-value="product.content.maxDomainCount" unit="个" />;</span>
|
||||
<span class="flex-o">部署次数<suite-value class="ml-5" :model-value="product.content.maxDeployCount" unit="次" />;</span>
|
||||
<span class="flex-o">
|
||||
{{$t('order.pipeline')}}<suite-value class="ml-5" :model-value="product.content.maxPipelineCount" unit="{{$t('order.unit.pieces')}}" />;
|
||||
</span>
|
||||
<span class="flex-o">
|
||||
{{$t('order.domain')}}<suite-value class="ml-5" :model-value="product.content.maxDomainCount" unit="{{$t('order.unit.count')}}" />;
|
||||
</span>
|
||||
<span class="flex-o">
|
||||
{{$t('order.deployTimes')}}<suite-value class="ml-5" :model-value="product.content.maxDeployCount" unit="{{$t('order.unit.times')}}" />;
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-o mt-5">
|
||||
<span class="label">时长:</span>
|
||||
<span class="label">{{$t('order.duration')}}:</span>
|
||||
<duration-value v-model="formRef.duration"></duration-value>
|
||||
</div>
|
||||
<div class="flex-o mt-5"><span class="label">价格:</span> <price-input :edit="false" :model-value="durationSelected.price"></price-input></div>
|
||||
<div class="flex-o mt-5">
|
||||
<span class="label">{{$t('order.price')}}:</span>
|
||||
<price-input :edit="false" :model-value="durationSelected.price"></price-input>
|
||||
</div>
|
||||
|
||||
<div class="flex-o mt-5">
|
||||
<span class="label">支付方式:</span>
|
||||
<div v-if="durationSelected.price === 0">免费</div>
|
||||
<span class="label">{{$t('order.paymentMethod')}}:</span>
|
||||
<div v-if="durationSelected.price === 0">{{$t('order.free')}}</div>
|
||||
<fs-dict-select v-else v-model:value="formRef.payType" :dict="paymentsDictRef" style="width: 200px"> </fs-dict-select>
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { ref } from "vue";
|
||||
import { GetPaymentTypes, OrderModalOpenReq, TradeCreate } from "/@/views/certd/suite/api";
|
||||
|
||||
@@ -11,19 +11,21 @@
|
||||
<div class="text">
|
||||
<div class="left">
|
||||
<div>
|
||||
<span>您好,{{ userInfo.nickName || userInfo.username }}, 欢迎使用 【{{ siteInfo.title }}】</span>
|
||||
<span>{{ t('certd.dashboard.greeting', { name: userInfo.nickName || userInfo.username, site: siteInfo.title }) }}</span>
|
||||
</div>
|
||||
<div class="flex-o flex-wrap profile-badges">
|
||||
<a-tooltip :title="deltaTimeTip">
|
||||
<a-badge :dot="deltaTimeWarning">
|
||||
<a-tag :color="deltaTimeWarning ? 'red' : 'green'" class="flex-inline pointer"> <fs-icon icon="ion:time-outline"></fs-icon> {{ now }}</a-tag>
|
||||
<a-tag :color="deltaTimeWarning ? 'red' : 'green'" class="flex-inline pointer">
|
||||
<fs-icon icon="ion:time-outline"></fs-icon> {{ now }}
|
||||
</a-tag>
|
||||
</a-badge>
|
||||
</a-tooltip>
|
||||
|
||||
<template v-if="userStore.isAdmin">
|
||||
<a-divider type="vertical" />
|
||||
<a-badge :dot="hasNewVersion">
|
||||
<a-tag color="blue" class="flex-inline pointer mr-0" :title="'最新版本:' + latestVersion" @click="openUpgradeUrl()">
|
||||
<a-tag color="blue" class="flex-inline pointer mr-0" :title="t('certd.dashboard.latestVersion', { version: latestVersion })" @click="openUpgradeUrl()">
|
||||
<fs-icon icon="ion:rocket-outline" class="mr-5"></fs-icon>
|
||||
v{{ version }}
|
||||
</a-tag>
|
||||
@@ -37,7 +39,7 @@
|
||||
</template>
|
||||
<template v-if="settingsStore.isPlus && settingsStore.sysPublic.userValidTimeEnabled === true && userInfo.validTime">
|
||||
<a-divider type="vertical" />
|
||||
<valid-time-format class="flex-o" prefix="账户有效期:" :model-value="userInfo.validTime" />
|
||||
<valid-time-format class="flex-o" :prefix="t('certd.dashboard.validUntil')" :model-value="userInfo.validTime" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -46,9 +48,9 @@
|
||||
|
||||
<div class="suggest hidden md:block">
|
||||
<tutorial-button class="flex-center mt-2">
|
||||
<a-tooltip title="点击查看详细教程">
|
||||
<a-tooltip :title="t('certd.dashboard.tutorialTooltip')">
|
||||
<a-tag color="blue" class="flex-center">
|
||||
仅需3步,全自动申请部署证书
|
||||
{{ t('certd.dashboard.tutorialText') }}
|
||||
<fs-icon class="font-size-16 ml-5" icon="mingcute:question-line"></fs-icon>
|
||||
</a-tag>
|
||||
</a-tooltip>
|
||||
@@ -59,9 +61,10 @@
|
||||
<div v-if="!settingStore.isComm" class="warning">
|
||||
<a-alert type="warning" show-icon>
|
||||
<template #message>
|
||||
证书和授权为敏感信息,不要使用来历不明的在线Certd服务和镜像,以免泄露;请务必私有化部署使用,认准官方版本发布渠道:
|
||||
<a class="ml-5 flex-inline" href="https://gitee.com/certd/certd" target="_blank">gitee</a>、 <a class="ml-5 flex-inline" href="https://github.com/certd/certd" target="_blank">github</a>、
|
||||
<a class="ml-5 flex-inline" href="https://certd.docmirror.cn" target="_blank">帮助文档</a>
|
||||
{{ t('certd.dashboard.alertMessage') }}
|
||||
<a class="ml-5 flex-inline" href="https://gitee.com/certd/certd" target="_blank">gitee</a>、
|
||||
<a class="ml-5 flex-inline" href="https://github.com/certd/certd" target="_blank">github</a>、
|
||||
<a class="ml-5 flex-inline" href="https://certd.docmirror.cn" target="_blank">{{ t('certd.dashboard.helpDoc') }}</a>
|
||||
</template>
|
||||
</a-alert>
|
||||
</div>
|
||||
@@ -69,30 +72,32 @@
|
||||
<div class="statistic-data m-20">
|
||||
<a-row :gutter="20" class="flex-wrap">
|
||||
<a-col :md="6" :xs="24">
|
||||
<statistic-card title="证书流水线数量" :count="count.pipelineCount">
|
||||
<statistic-card :title="t('certd.dashboard.pipelineCount')" :count="count.pipelineCount">
|
||||
<template v-if="count.pipelineCount === 0" #default>
|
||||
<div class="flex-center flex-1 flex-col">
|
||||
<div style="font-size: 18px; font-weight: 700">您还没有证书流水线</div>
|
||||
<fs-button type="primary" class="mt-10" icon="ion:add-circle-outline" @click="goPipeline">立即创建</fs-button>
|
||||
<div style="font-size: 18px; font-weight: 700">{{ t('certd.dashboard.noPipeline') }}</div>
|
||||
<fs-button type="primary" class="mt-10" icon="ion:add-circle-outline" @click="goPipeline">{{ t('certd.dashboard.createNow') }}</fs-button>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<router-link to="/certd/pipeline" class="flex"><fs-icon icon="ion:settings-outline" class="mr-5 fs-16" /> 管理流水线</router-link>
|
||||
<router-link to="/certd/pipeline" class="flex">
|
||||
<fs-icon icon="ion:settings-outline" class="mr-5 fs-16" /> {{ t('certd.dashboard.managePipeline') }}
|
||||
</router-link>
|
||||
</template>
|
||||
</statistic-card>
|
||||
</a-col>
|
||||
<a-col :md="6" :xs="24">
|
||||
<statistic-card title="流水线状态" :footer="false">
|
||||
<statistic-card :title="t('certd.dashboard.pipelineStatus')" :footer="false">
|
||||
<pie-count v-if="count.pipelineStatusCount" :data="count.pipelineStatusCount"></pie-count>
|
||||
</statistic-card>
|
||||
</a-col>
|
||||
<a-col :md="6" :xs="24">
|
||||
<statistic-card title="最近运行统计" :footer="false">
|
||||
<day-count v-if="count.historyCountPerDay" :data="count.historyCountPerDay" title="运行次数"></day-count>
|
||||
<statistic-card :title="t('certd.dashboard.recentRun')" :footer="false">
|
||||
<day-count v-if="count.historyCountPerDay" :data="count.historyCountPerDay" :title="t('certd.dashboard.runCount')"></day-count>
|
||||
</statistic-card>
|
||||
</a-col>
|
||||
<a-col :md="6" :xs="24">
|
||||
<statistic-card title="最快到期证书">
|
||||
<statistic-card :title="t('certd.dashboard.expiringCerts')">
|
||||
<expiring-list v-if="count.expiringList" :data="count.expiringList"></expiring-list>
|
||||
</statistic-card>
|
||||
</a-col>
|
||||
@@ -102,7 +107,8 @@
|
||||
<div v-if="pluginGroups" class="plugin-list">
|
||||
<a-card>
|
||||
<template #title>
|
||||
已支持的部署任务总览 <a-tag color="green">{{ pluginGroups.groups.all.plugins.length }}</a-tag>
|
||||
{{ t('certd.dashboard.supportedTasks') }}
|
||||
<a-tag color="green">{{ pluginGroups.groups.all.plugins.length }}</a-tag>
|
||||
</template>
|
||||
<a-row :gutter="10">
|
||||
<a-col v-for="item of pluginGroups.groups.all.plugins" :key="item.name" class="plugin-item-col" :xl="4" :md="6" :xs="24">
|
||||
@@ -130,6 +136,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { FsIcon } from "@fast-crud/fast-crud";
|
||||
import SimpleSteps from "/@/components/tutorial/simple-steps.vue";
|
||||
@@ -148,6 +155,8 @@ import { UserInfoRes } from "/@/store/user/api.user";
|
||||
import { GetStatisticCount } from "/@/views/framework/home/dashboard/api";
|
||||
import { useRouter } from "vue-router";
|
||||
import * as api from "./api";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
import { usePluginStore } from "/@/store/plugin";
|
||||
defineOptions({
|
||||
name: "DashboardUser",
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
<fs-page class="home—index bg-neutral-100 dark:bg-black">
|
||||
<!-- <page-content />-->
|
||||
<dashboard-user />
|
||||
<change-password-button ref="changePasswordButtonRef" :show-button="false"></change-password-button>
|
||||
<change-password-button
|
||||
ref="changePasswordButtonRef"
|
||||
:show-button="false"
|
||||
></change-password-button>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
@@ -12,20 +15,23 @@ import { useUserStore } from "/@/store/user";
|
||||
import ChangePasswordButton from "/@/views/certd/mine/change-password-button.vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { Modal } from "ant-design-vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const userStore = useUserStore();
|
||||
const changePasswordButtonRef = ref();
|
||||
onMounted(() => {
|
||||
if (userStore.getUserInfo.isWeak === true) {
|
||||
Modal.info({
|
||||
title: "修改密码",
|
||||
content: "为了您的账户安全,请立即修改密码",
|
||||
title: t("passwordForm.title"),
|
||||
content: t("passwordForm.weakPasswordWarning"),
|
||||
onOk: () => {
|
||||
changePasswordButtonRef.value.open({
|
||||
password: "123456",
|
||||
});
|
||||
},
|
||||
okText: "立即修改",
|
||||
okText: t("passwordForm.changeNow"),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,18 +3,18 @@
|
||||
<a-form v-if="!twoFactor.loginId" ref="formRef" class="user-layout-login" name="custom-validation" :model="formState" v-bind="layout" @finish="handleFinish" @finish-failed="handleFinishFailed">
|
||||
<!-- <div class="login-title">登录</div>-->
|
||||
<a-tabs v-model:active-key="formState.loginType" :tab-bar-style="{ textAlign: 'center', borderBottom: 'unset' }">
|
||||
<a-tab-pane key="password" tab="密码登录" :disabled="sysPublicSettings.passwordLoginEnabled !== true">
|
||||
<a-tab-pane key="password" :tab="t('authentication.passwordTab')" :disabled="sysPublicSettings.passwordLoginEnabled !== true">
|
||||
<template v-if="formState.loginType === 'password'">
|
||||
<!-- <div class="login-title">登录</div>-->
|
||||
<a-form-item required has-feedback name="username" :rules="rules.username">
|
||||
<a-input v-model:value="formState.username" placeholder="请输入用户名/邮箱/手机号" autocomplete="off">
|
||||
<a-input v-model:value="formState.username" :placeholder="t('authentication.usernamePlaceholder')" autocomplete="off">
|
||||
<template #prefix>
|
||||
<fs-icon icon="ion:phone-portrait-outline"></fs-icon>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item has-feedback name="password" :rules="rules.password">
|
||||
<a-input-password v-model:value="formState.password" placeholder="请输入密码" autocomplete="off">
|
||||
<a-input-password v-model:value="formState.password" :placeholder="t('authentication.passwordPlaceholder')" autocomplete="off">
|
||||
<template #prefix>
|
||||
<fs-icon icon="ion:lock-closed-outline"></fs-icon>
|
||||
</template>
|
||||
@@ -22,15 +22,16 @@
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="sms" tab="短信验证码登录" :disabled="sysPublicSettings.smsLoginEnabled !== true">
|
||||
<a-tab-pane v-if="sysPublicSettings.smsLoginEnabled === true" key="sms" :tab="t('authentication.smsTab')">
|
||||
<template v-if="formState.loginType === 'sms'">
|
||||
<a-form-item has-feedback name="mobile" :rules="rules.mobile">
|
||||
<a-input v-model:value="formState.mobile" placeholder="请输入手机号" autocomplete="off">
|
||||
<a-input v-model:value="formState.mobile" :placeholder="t('authentication.mobilePlaceholder')" autocomplete="off">
|
||||
<template #prefix>
|
||||
<fs-icon icon="ion:phone-portrait-outline"></fs-icon>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item has-feedback name="imgCode">
|
||||
<image-code v-model:value="formState.imgCode" v-model:random-str="formState.randomStr"></image-code>
|
||||
</a-form-item>
|
||||
@@ -42,13 +43,24 @@
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<a-form-item>
|
||||
<a-button type="primary" size="large" html-type="submit" :loading="loading" class="login-button">登录</a-button>
|
||||
<a-button type="primary" size="large" html-type="submit" :loading="loading" class="login-button">
|
||||
{{ t("authentication.loginButton") }}
|
||||
</a-button>
|
||||
|
||||
<div v-if="!settingStore.isComm" class="mt-2"><a href="https://certd.docmirror.cn/guide/use/forgotpasswd/" target="_blank">忘记管理员密码?</a></div>
|
||||
<div v-if="!settingStore.isComm" class="mt-2">
|
||||
<a href="https://certd.docmirror.cn/guide/use/forgotpasswd/" target="_blank">
|
||||
{{ t("authentication.forgotAdminPassword") }}
|
||||
</a>
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="user-login-other">
|
||||
<router-link v-if="hasRegisterTypeEnabled()" class="register" :to="{ name: 'register' }"> 注册 </router-link>
|
||||
<div class="flex flex-between justify-between items-center">
|
||||
<language-toggle class="color-blue"></language-toggle>
|
||||
<router-link v-if="hasRegisterTypeEnabled()" class="register" :to="{ name: 'register' }">
|
||||
{{ t("authentication.registerLink") }}
|
||||
</router-link>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-form v-else ref="twoFactorFormRef" class="user-layout-login" :model="twoFactor" v-bind="layout">
|
||||
@@ -77,11 +89,14 @@ import { useSettingStore } from "/@/store/settings";
|
||||
import { utils } from "@fast-crud/fast-crud";
|
||||
import ImageCode from "/@/views/framework/login/image-code.vue";
|
||||
import SmsCode from "/@/views/framework/login/sms-code.vue";
|
||||
import { useI18n } from "/@/locales";
|
||||
import { LanguageToggle } from "/@/vben/layouts";
|
||||
|
||||
export default defineComponent({
|
||||
name: "LoginPage",
|
||||
components: { SmsCode, ImageCode },
|
||||
components: { LanguageToggle, SmsCode, ImageCode },
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
const verifyCodeInputRef = ref();
|
||||
const loading = ref(false);
|
||||
const userStore = useUserStore();
|
||||
@@ -180,6 +195,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
return {
|
||||
t,
|
||||
loading,
|
||||
formState,
|
||||
formRef,
|
||||
@@ -202,9 +218,11 @@ export default defineComponent({
|
||||
|
||||
<style lang="less">
|
||||
@import "../../../style/theme/index.less";
|
||||
|
||||
.login-page.main {
|
||||
//margin: 20px !important;
|
||||
margin-bottom: 100px;
|
||||
|
||||
.user-layout-login {
|
||||
//label {
|
||||
// font-size: 14px;
|
||||
@@ -216,6 +234,7 @@ export default defineComponent({
|
||||
text-align: center;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.getCaptcha {
|
||||
display: block;
|
||||
width: 100%;
|
||||
@@ -224,10 +243,12 @@ export default defineComponent({
|
||||
.image-code {
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
.input-right {
|
||||
width: 160px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.forge-password {
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -261,13 +282,16 @@ export default defineComponent({
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.fs-icon {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.ant-input-affix-wrapper {
|
||||
line-height: 1.8 !important;
|
||||
font-size: 14px !important;
|
||||
|
||||
> * {
|
||||
line-height: 1.8 !important;
|
||||
font-size: 14px !important;
|
||||
|
||||
@@ -1,150 +1,152 @@
|
||||
import * as api from "./api.js";
|
||||
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||
const list = await api.GetTree();
|
||||
const { t } = useI18n();
|
||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||
const list = await api.GetTree();
|
||||
|
||||
return {
|
||||
offset: 0,
|
||||
records: list,
|
||||
total: 10000,
|
||||
limit: 10000
|
||||
};
|
||||
};
|
||||
return {
|
||||
offset: 0,
|
||||
records: list,
|
||||
total: 10000,
|
||||
limit: 10000
|
||||
};
|
||||
};
|
||||
|
||||
async function afterChange() {
|
||||
await permissionTreeDict.reloadDict();
|
||||
}
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
form.id = row.id;
|
||||
const ret = await api.UpdateObj(form);
|
||||
await afterChange();
|
||||
return ret;
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
const ret = await api.DelObj(row.id);
|
||||
await afterChange();
|
||||
return ret;
|
||||
};
|
||||
async function afterChange() {
|
||||
await permissionTreeDict.reloadDict();
|
||||
}
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
form.id = row.id;
|
||||
const ret = await api.UpdateObj(form);
|
||||
await afterChange();
|
||||
return ret;
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
const ret = await api.DelObj(row.id);
|
||||
await afterChange();
|
||||
return ret;
|
||||
};
|
||||
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
const ret = await api.AddObj(form);
|
||||
await afterChange();
|
||||
return ret;
|
||||
};
|
||||
const permissionTreeDict = dict({
|
||||
url: "/sys/authority/permission/tree",
|
||||
isTree: true,
|
||||
value: "id",
|
||||
label: "title",
|
||||
async onReady({ dict }: any) {
|
||||
dict.setData([{ id: -1, title: "根节点", children: dict.data }]);
|
||||
}
|
||||
});
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest
|
||||
},
|
||||
actionbar: {
|
||||
show: false
|
||||
},
|
||||
toolbar: {
|
||||
show: false
|
||||
},
|
||||
table: {
|
||||
show: false
|
||||
// scroll: { fixed: true }
|
||||
},
|
||||
rowHandle: {
|
||||
fixed: "right"
|
||||
},
|
||||
search: {
|
||||
show: false
|
||||
},
|
||||
pagination: {
|
||||
show: false,
|
||||
pageSize: 100000
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "id",
|
||||
type: "number",
|
||||
form: { show: false }, // 表单配置
|
||||
column: {
|
||||
width: 120,
|
||||
sortable: "custom"
|
||||
}
|
||||
},
|
||||
title: {
|
||||
title: "权限名称",
|
||||
type: "text",
|
||||
form: {
|
||||
rules: [
|
||||
{ required: true, message: "请输入权限名称" },
|
||||
{ max: 50, message: "最大50个字符" }
|
||||
],
|
||||
component: {
|
||||
placeholder: "权限名称"
|
||||
}
|
||||
},
|
||||
column: {
|
||||
width: 200
|
||||
}
|
||||
},
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
const ret = await api.AddObj(form);
|
||||
await afterChange();
|
||||
return ret;
|
||||
};
|
||||
const permissionTreeDict = dict({
|
||||
url: "/sys/authority/permission/tree",
|
||||
isTree: true,
|
||||
value: "id",
|
||||
label: "title",
|
||||
async onReady({ dict }: any) {
|
||||
dict.setData([{ id: -1, title: t("certd.rootNode"), children: dict.data }]);
|
||||
}
|
||||
});
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest
|
||||
},
|
||||
actionbar: {
|
||||
show: false
|
||||
},
|
||||
toolbar: {
|
||||
show: false
|
||||
},
|
||||
table: {
|
||||
show: false
|
||||
// scroll: { fixed: true }
|
||||
},
|
||||
rowHandle: {
|
||||
fixed: "right"
|
||||
},
|
||||
search: {
|
||||
show: false
|
||||
},
|
||||
pagination: {
|
||||
show: false,
|
||||
pageSize: 100000
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "id",
|
||||
type: "number",
|
||||
form: { show: false }, // 表单配置
|
||||
column: {
|
||||
width: 120,
|
||||
sortable: "custom"
|
||||
}
|
||||
},
|
||||
title: {
|
||||
title: t("certd.permissionName"),
|
||||
type: "text",
|
||||
form: {
|
||||
rules: [
|
||||
{ required: true, message: t("certd.enterPermissionName") },
|
||||
{ max: 50, message: t("certd.max50Chars") }
|
||||
],
|
||||
component: {
|
||||
placeholder: t("certd.permissionName")
|
||||
}
|
||||
},
|
||||
column: {
|
||||
width: 200
|
||||
}
|
||||
},
|
||||
permission: {
|
||||
title: t("certd.permissionCode"),
|
||||
type: "text",
|
||||
column: {
|
||||
width: 170
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
{ required: true, message: t("certd.enterPermissionCode") },
|
||||
{ max: 100, message: t("certd.max100Chars") }
|
||||
],
|
||||
component: {
|
||||
placeholder: t("certd.examplePermissionCode")
|
||||
}
|
||||
}
|
||||
},
|
||||
sort: {
|
||||
title: t("certd.sortOrder"),
|
||||
type: "number",
|
||||
column: {
|
||||
width: 100
|
||||
},
|
||||
form: {
|
||||
value: 100,
|
||||
rules: [{ required: true, type: "number", message: t("certd.sortRequired") }]
|
||||
}
|
||||
},
|
||||
parentId: {
|
||||
title: t("certd.parentNode"),
|
||||
type: "dict-tree",
|
||||
column: {
|
||||
width: 100
|
||||
},
|
||||
dict: permissionTreeDict,
|
||||
form: {
|
||||
value: -1,
|
||||
component: {
|
||||
multiple: false,
|
||||
defaultExpandAll: true,
|
||||
dict: { cache: false },
|
||||
fieldNames: {
|
||||
value: "id",
|
||||
label: "title"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
permission: {
|
||||
title: "权限代码",
|
||||
type: "text",
|
||||
column: {
|
||||
width: 170
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
{ required: true, message: "请输入权限代码" },
|
||||
{ max: 100, message: "最大100个字符" }
|
||||
],
|
||||
component: {
|
||||
placeholder: "例如:sys:user:view"
|
||||
}
|
||||
}
|
||||
},
|
||||
sort: {
|
||||
title: "排序",
|
||||
type: "number",
|
||||
column: {
|
||||
width: 100
|
||||
},
|
||||
form: {
|
||||
value: 100,
|
||||
rules: [{ required: true, type: "number", message: "排序号必填" }]
|
||||
}
|
||||
},
|
||||
parentId: {
|
||||
title: "父节点",
|
||||
type: "dict-tree",
|
||||
column: {
|
||||
width: 100
|
||||
},
|
||||
dict: permissionTreeDict,
|
||||
form: {
|
||||
value: -1,
|
||||
component: {
|
||||
multiple: false,
|
||||
defaultExpandAll: true,
|
||||
dict: { cache: false },
|
||||
fieldNames: {
|
||||
value: "id",
|
||||
label: "title"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+173
-172
@@ -1,193 +1,194 @@
|
||||
<template>
|
||||
<a-tree
|
||||
v-if="computedTree"
|
||||
ref="treeRef"
|
||||
class="fs-permission-tree"
|
||||
:class="{ 'is-editable': editable }"
|
||||
:selectable="false"
|
||||
show-line
|
||||
:show-icon="false"
|
||||
:default-expand-all="true"
|
||||
:tree-data="computedTree"
|
||||
@check="onChecked"
|
||||
>
|
||||
<template #title="scope">
|
||||
<div class="node-title-pane">
|
||||
<div class="node-title">{{ scope.title }}</div>
|
||||
<div v-if="editable === true" class="node-suffix">
|
||||
<fs-icon v-if="actions.add !== false" :icon="$fsui.icons.add" @click.stop="add(scope)" />
|
||||
<fs-icon v-if="actions.edit !== false && scope.id !== -1" :icon="$fsui.icons.edit" @click.stop="edit(scope)" />
|
||||
<fs-icon v-if="actions.remove !== false && scope.id !== -1" :icon="$fsui.icons.remove" @click.stop="remove(scope)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-tree>
|
||||
<a-tree v-if="computedTree" ref="treeRef" class="fs-permission-tree" :class="{ 'is-editable': editable }"
|
||||
:selectable="false" show-line :show-icon="false" :default-expand-all="true" :tree-data="computedTree"
|
||||
@check="onChecked">
|
||||
<template #title="scope">
|
||||
<div class="node-title-pane">
|
||||
<div class="node-title">{{ scope.title }}</div>
|
||||
<div v-if="editable === true" class="node-suffix">
|
||||
<fs-icon v-if="actions.add !== false" :icon="$fsui.icons.add" @click.stop="add(scope)" />
|
||||
<fs-icon v-if="actions.edit !== false && scope.id !== -1" :icon="$fsui.icons.edit"
|
||||
@click.stop="edit(scope)" />
|
||||
<fs-icon v-if="actions.remove !== false && scope.id !== -1" :icon="$fsui.icons.remove"
|
||||
@click.stop="remove(scope)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-tree>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { utils } from "@fast-crud/fast-crud";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
import { computed, defineComponent, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
export default defineComponent({
|
||||
name: "FsPermissionTree",
|
||||
props: {
|
||||
/**
|
||||
* 树形数据
|
||||
* */
|
||||
tree: {},
|
||||
/**
|
||||
* 是否可编辑
|
||||
*/
|
||||
editable: {
|
||||
default: true
|
||||
},
|
||||
actions: {
|
||||
default: {}
|
||||
}
|
||||
} as any,
|
||||
emits: ["add", "edit", "remove"],
|
||||
setup(props: any, ctx) {
|
||||
const treeRef = ref();
|
||||
const computedTree = computed(() => {
|
||||
if (props.tree == null) {
|
||||
return null;
|
||||
}
|
||||
const clone = cloneDeep(props.tree);
|
||||
utils.deepdash.forEachDeep(clone, (value: any, key: any, pNode: any, context: any) => {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
if (!(value instanceof Object) || value instanceof Array) {
|
||||
return;
|
||||
}
|
||||
if (value.class === "is-leaf") {
|
||||
//处理过,无需再次处理
|
||||
return;
|
||||
}
|
||||
value.class = "is-twig";
|
||||
if (value.children != null && value.children.length > 0) {
|
||||
return;
|
||||
}
|
||||
const parents = context.parents;
|
||||
if (parents.length < 2) {
|
||||
return;
|
||||
}
|
||||
const parent = parents[parents.length - 2].value;
|
||||
//看parent下面的children,是否全部都没有children
|
||||
for (const child of parent.children) {
|
||||
if (child.children != null && child.children.length > 0) {
|
||||
//存在child有children
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 所有的子节点都没有children
|
||||
parent.class = "is-twig"; // 连接叶子节点的末梢枝杈节点
|
||||
let i = 0;
|
||||
for (const child of parent.children) {
|
||||
child.class = "is-leaf";
|
||||
if (i !== 0) {
|
||||
child.class += " leaf-after";
|
||||
}
|
||||
i++;
|
||||
}
|
||||
});
|
||||
return [
|
||||
{
|
||||
title: "根节点",
|
||||
id: -1,
|
||||
children: clone
|
||||
}
|
||||
];
|
||||
});
|
||||
function add(scope: any) {
|
||||
ctx.emit("add", scope.dataRef);
|
||||
}
|
||||
function edit(scope: any) {
|
||||
ctx.emit("edit", scope.dataRef);
|
||||
}
|
||||
function remove(scope: any) {
|
||||
ctx.emit("remove", scope.dataRef);
|
||||
}
|
||||
function onChecked(a: any, b: any, c: any) {
|
||||
utils.logger.info("chedcked", a, b, c);
|
||||
}
|
||||
function getChecked() {
|
||||
const checked = treeRef.value.checkedKeys;
|
||||
const halfChecked = treeRef.value.halfCheckedKeys;
|
||||
return {
|
||||
checked,
|
||||
halfChecked
|
||||
};
|
||||
}
|
||||
return {
|
||||
computedTree,
|
||||
add,
|
||||
edit,
|
||||
remove,
|
||||
treeRef,
|
||||
onChecked,
|
||||
getChecked
|
||||
};
|
||||
}
|
||||
name: "FsPermissionTree",
|
||||
props: {
|
||||
/**
|
||||
* 树形数据
|
||||
* */
|
||||
tree: {},
|
||||
/**
|
||||
* 是否可编辑
|
||||
*/
|
||||
editable: {
|
||||
default: true
|
||||
},
|
||||
actions: {
|
||||
default: {}
|
||||
}
|
||||
} as any,
|
||||
emits: ["add", "edit", "remove"],
|
||||
setup(props: any, ctx) {
|
||||
const { t } = useI18n();
|
||||
const treeRef = ref();
|
||||
const computedTree = computed(() => {
|
||||
if (props.tree == null) {
|
||||
return null;
|
||||
}
|
||||
const clone = cloneDeep(props.tree);
|
||||
utils.deepdash.forEachDeep(clone, (value: any, key: any, pNode: any, context: any) => {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
if (!(value instanceof Object) || value instanceof Array) {
|
||||
return;
|
||||
}
|
||||
if (value.class === "is-leaf") {
|
||||
//处理过,无需再次处理
|
||||
return;
|
||||
}
|
||||
value.class = "is-twig";
|
||||
if (value.children != null && value.children.length > 0) {
|
||||
return;
|
||||
}
|
||||
const parents = context.parents;
|
||||
if (parents.length < 2) {
|
||||
return;
|
||||
}
|
||||
const parent = parents[parents.length - 2].value;
|
||||
//看parent下面的children,是否全部都没有children
|
||||
for (const child of parent.children) {
|
||||
if (child.children != null && child.children.length > 0) {
|
||||
//存在child有children
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 所有的子节点都没有children
|
||||
parent.class = "is-twig"; // 连接叶子节点的末梢枝杈节点
|
||||
let i = 0;
|
||||
for (const child of parent.children) {
|
||||
child.class = "is-leaf";
|
||||
if (i !== 0) {
|
||||
child.class += " leaf-after";
|
||||
}
|
||||
i++;
|
||||
}
|
||||
});
|
||||
return [
|
||||
{
|
||||
title: t("certd.rootNode"),
|
||||
id: -1,
|
||||
children: clone
|
||||
}
|
||||
];
|
||||
});
|
||||
function add(scope: any) {
|
||||
ctx.emit("add", scope.dataRef);
|
||||
}
|
||||
function edit(scope: any) {
|
||||
ctx.emit("edit", scope.dataRef);
|
||||
}
|
||||
function remove(scope: any) {
|
||||
ctx.emit("remove", scope.dataRef);
|
||||
}
|
||||
function onChecked(a: any, b: any, c: any) {
|
||||
utils.logger.info("chedcked", a, b, c);
|
||||
}
|
||||
function getChecked() {
|
||||
const checked = treeRef.value.checkedKeys;
|
||||
const halfChecked = treeRef.value.halfCheckedKeys;
|
||||
return {
|
||||
checked,
|
||||
halfChecked
|
||||
};
|
||||
}
|
||||
return {
|
||||
computedTree,
|
||||
add,
|
||||
edit,
|
||||
remove,
|
||||
treeRef,
|
||||
onChecked,
|
||||
getChecked,
|
||||
t
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.fs-permission-tree {
|
||||
.ant-tree-list-holder-inner {
|
||||
flex-direction: row !important;
|
||||
flex-wrap: wrap;
|
||||
.is-twig {
|
||||
width: 100%;
|
||||
}
|
||||
.ant-tree-list-holder-inner {
|
||||
flex-direction: row !important;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.is-leaf {
|
||||
//border-bottom: 1px solid #ddd;
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
.is-twig {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&.leaf-after {
|
||||
.ant-tree-indent-unit {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.is-leaf {
|
||||
|
||||
.node-title-pane {
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
}
|
||||
}
|
||||
//.is-twig ul {
|
||||
// display: flex;
|
||||
// flex-wrap: wrap;
|
||||
//}
|
||||
.node-title-pane {
|
||||
display: flex;
|
||||
.node-title {
|
||||
width: 110px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
//border-bottom: 1px solid #ddd;
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.is-editable {
|
||||
.ant-tree-title {
|
||||
&:hover {
|
||||
.node-suffix {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.leaf-after {
|
||||
.ant-tree-indent-unit {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.node-suffix {
|
||||
visibility: hidden;
|
||||
> * {
|
||||
margin-left: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.node-title-pane {
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//.is-twig ul {
|
||||
// display: flex;
|
||||
// flex-wrap: wrap;
|
||||
//}
|
||||
.node-title-pane {
|
||||
display: flex;
|
||||
|
||||
.node-title {
|
||||
width: 110px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-editable {
|
||||
.ant-tree-title {
|
||||
&:hover {
|
||||
.node-suffix {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.node-suffix {
|
||||
visibility: hidden;
|
||||
|
||||
>* {
|
||||
margin-left: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,76 +1,81 @@
|
||||
<template>
|
||||
<fs-page>
|
||||
<template #header>
|
||||
<div class="title">权限管理</div>
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
<a-button v-permission="'1sys:auth:per:add'" style="margin-left: 20px" @click="addHandle({})">
|
||||
<fs-icon :icon="ui.icons.add"></fs-icon>
|
||||
添加
|
||||
</a-button>
|
||||
<fs-permission-tree class="permission-tree mt-10" :tree="crudBinding.data" :checkable="false" :actions="permission" @add="addHandle" @edit="editHandle" @remove="removeHandle"></fs-permission-tree>
|
||||
</fs-crud>
|
||||
</fs-page>
|
||||
<fs-page>
|
||||
<template #header>
|
||||
<div class="title">{{ t("certd.permissionManagement") }}</div>
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
<a-button v-permission="'1sys:auth:per:add'" style="margin-left: 20px" @click="addHandle({})">
|
||||
<fs-icon :icon="ui.icons.add"></fs-icon>
|
||||
{{ t("certd.adda") }}
|
||||
</a-button>
|
||||
<fs-permission-tree class="permission-tree mt-10" :tree="crudBinding.data" :checkable="false"
|
||||
:actions="permission" @add="addHandle" @edit="editHandle" @remove="removeHandle"></fs-permission-tree>
|
||||
</fs-crud>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onActivated, onMounted, ref } from "vue";
|
||||
import createCrudOptions from "./crud.js";
|
||||
import FsPermissionTree from "./fs-permission-tree.vue";
|
||||
import { usePermission } from "/src/plugin/permission";
|
||||
import { useFs, useUi } from "@fast-crud/fast-crud";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
export default defineComponent({
|
||||
name: "AuthorityManager",
|
||||
components: { FsPermissionTree },
|
||||
setup() {
|
||||
// 此处传入permission进行通用按钮权限设置,会通过commonOptions去设置actionbar和rowHandle的按钮的show属性
|
||||
// 更多关于按钮权限的源代码设置,请参考 ./src/plugin/fast-crud/index.js (75-77行)
|
||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { permission: "sys:auth:per" } });
|
||||
name: "AuthorityManager",
|
||||
components: { FsPermissionTree },
|
||||
setup() {
|
||||
// 此处传入permission进行通用按钮权限设置,会通过commonOptions去设置actionbar和rowHandle的按钮的show属性
|
||||
// 更多关于按钮权限的源代码设置,请参考 ./src/plugin/fast-crud/index.js (75-77行)
|
||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { permission: "sys:auth:per" } });
|
||||
const { t } = useI18n();
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(async () => {
|
||||
await crudExpose.doRefresh();
|
||||
});
|
||||
onActivated(async () => {
|
||||
await crudExpose.doRefresh();
|
||||
});
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(async () => {
|
||||
await crudExpose.doRefresh();
|
||||
});
|
||||
onActivated(async () => {
|
||||
await crudExpose.doRefresh();
|
||||
});
|
||||
|
||||
const { ui } = useUi();
|
||||
const { ui } = useUi();
|
||||
|
||||
//用户业务代码
|
||||
//用户业务代码
|
||||
|
||||
async function addHandle(item: any) {
|
||||
await crudExpose.openAdd({ row: { parentId: item?.id ?? -1 } });
|
||||
}
|
||||
async function editHandle(item: any) {
|
||||
await crudExpose.openEdit({ row: item });
|
||||
}
|
||||
async function removeHandle(item: any) {
|
||||
await crudExpose.doRemove({ row: { id: item.id }, index: null });
|
||||
}
|
||||
async function addHandle(item: any) {
|
||||
await crudExpose.openAdd({ row: { parentId: item?.id ?? -1 } });
|
||||
}
|
||||
async function editHandle(item: any) {
|
||||
await crudExpose.openEdit({ row: item });
|
||||
}
|
||||
async function removeHandle(item: any) {
|
||||
await crudExpose.doRemove({ row: { id: item.id }, index: null });
|
||||
}
|
||||
|
||||
const { hasPermissions } = usePermission();
|
||||
const permission = ref({
|
||||
add: hasPermissions("1sys:auth:per:add"),
|
||||
edit: hasPermissions("1sys:auth:per:edit"),
|
||||
remove: hasPermissions("1sys:auth:per:remove"),
|
||||
});
|
||||
const { hasPermissions } = usePermission();
|
||||
const permission = ref({
|
||||
add: hasPermissions("1sys:auth:per:add"),
|
||||
edit: hasPermissions("1sys:auth:per:edit"),
|
||||
remove: hasPermissions("1sys:auth:per:remove"),
|
||||
});
|
||||
|
||||
return {
|
||||
ui,
|
||||
crudBinding,
|
||||
crudRef,
|
||||
addHandle,
|
||||
editHandle,
|
||||
removeHandle,
|
||||
permission,
|
||||
};
|
||||
},
|
||||
return {
|
||||
ui,
|
||||
crudBinding,
|
||||
crudRef,
|
||||
addHandle,
|
||||
editHandle,
|
||||
removeHandle,
|
||||
permission,
|
||||
t
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
.permission-tree {
|
||||
margin-left: 20px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,84 +1,86 @@
|
||||
import * as api from "./api";
|
||||
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
export default function ({ crudExpose, context: { authz } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
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;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest
|
||||
},
|
||||
rowHandle: {
|
||||
width: 300,
|
||||
buttons: {
|
||||
authz: {
|
||||
type: "link",
|
||||
text: "授权",
|
||||
async click(context) {
|
||||
await authz.authzOpen(context.record.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "id",
|
||||
type: "text",
|
||||
form: { show: false }, // 表单配置
|
||||
column: {
|
||||
width: 70,
|
||||
sorter: true
|
||||
}
|
||||
},
|
||||
name: {
|
||||
title: "角色名称",
|
||||
type: "text",
|
||||
search: { show: true },
|
||||
form: {
|
||||
rules: [
|
||||
{ required: true, message: "请输入角色名称" },
|
||||
{ max: 50, message: "最大50个字符" }
|
||||
]
|
||||
}, // 表单配置
|
||||
column: {
|
||||
sorter: true
|
||||
}
|
||||
},
|
||||
createTime: {
|
||||
title: "创建时间",
|
||||
type: "datetime",
|
||||
column: {
|
||||
sorter: true
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
updateTime: {
|
||||
title: "更新时间",
|
||||
type: "datetime",
|
||||
column: {
|
||||
sorter: true
|
||||
},
|
||||
form: { show: false } // 表单配置
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest
|
||||
},
|
||||
rowHandle: {
|
||||
width: 300,
|
||||
buttons: {
|
||||
authz: {
|
||||
type: "link",
|
||||
text: "授权",
|
||||
async click(context) {
|
||||
await authz.authzOpen(context.record.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "id",
|
||||
type: "text",
|
||||
form: { show: false }, // 表单配置
|
||||
column: {
|
||||
width: 70,
|
||||
sorter: true
|
||||
}
|
||||
},
|
||||
name: {
|
||||
title: t("certd.roleName"),
|
||||
type: "text",
|
||||
search: { show: true },
|
||||
form: {
|
||||
rules: [
|
||||
{ required: true, message: t("certd.enterRoleName") },
|
||||
{ max: 50, message: t("certd.max50Chars") }
|
||||
]
|
||||
}, // 表单配置
|
||||
column: {
|
||||
sorter: true
|
||||
}
|
||||
},
|
||||
createTime: {
|
||||
title: t("certd.createTime"),
|
||||
type: "datetime",
|
||||
column: {
|
||||
sorter: true
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
updateTime: {
|
||||
title: t("certd.updateTime"),
|
||||
type: "datetime",
|
||||
column: {
|
||||
sorter: true
|
||||
},
|
||||
form: { show: false } // 表单配置
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
<template>
|
||||
<fs-page>
|
||||
<template #header>
|
||||
<div class="title">角色管理</div>
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding" />
|
||||
<a-modal v-model:open="authzDialogVisible" width="860px" title="分配权限" @ok="updatePermission">
|
||||
<fs-permission-tree ref="permissionTreeRef" v-model:checked-keys="checkedKeys" :tree="permissionTreeData" :editable="false" checkable :replace-fields="{ key: 'id', label: 'title' }"> </fs-permission-tree>
|
||||
</a-modal>
|
||||
</fs-page>
|
||||
<fs-page>
|
||||
<template #header>
|
||||
<div class="title">{{ t("certd.roleManagement") }}</div>
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding" />
|
||||
<a-modal v-model:open="authzDialogVisible" width="860px" :title="t('certd.assignPermissions')"
|
||||
@ok="updatePermission">
|
||||
<fs-permission-tree ref="permissionTreeRef" v-model:checked-keys="checkedKeys" :tree="permissionTreeData"
|
||||
:editable="false" checkable :replace-fields="{ key: 'id', label: 'title' }"> </fs-permission-tree>
|
||||
</a-modal>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onActivated, onMounted, ref } from "vue";
|
||||
import { useFs } from "@fast-crud/fast-crud";
|
||||
@@ -19,103 +22,106 @@ import * as api from "./api";
|
||||
import { message } from "ant-design-vue";
|
||||
import FsPermissionTree from "../permission/fs-permission-tree.vue";
|
||||
import { UseCrudPermissionCompProps, UseCrudPermissionExtraProps } from "/@/plugin/permission";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
function useAuthz() {
|
||||
const checkedKeys = ref();
|
||||
const checkedKeys = ref();
|
||||
|
||||
const permissionTreeData = ref();
|
||||
const permissionTreeData = ref();
|
||||
|
||||
const permissionTreeRef = ref();
|
||||
const authzDialogVisible = ref(false);
|
||||
const permissionTreeRef = ref();
|
||||
const authzDialogVisible = ref(false);
|
||||
|
||||
const currentRoleId = ref();
|
||||
const currentRoleId = ref();
|
||||
|
||||
// 如果勾选节点中存在非叶子节点,tree组件会将其所有子节点全部勾选
|
||||
// 所以要找出所有叶子节点,仅勾选叶子节点,tree组件会将父节点同步勾选
|
||||
function getAllCheckedLeafNodeId(tree: any, checkedIds: any, temp: any) {
|
||||
for (let i = 0; i < tree.length; i++) {
|
||||
const item = tree[i];
|
||||
if (item.children && item.children.length !== 0) {
|
||||
getAllCheckedLeafNodeId(item.children, checkedIds, temp);
|
||||
} else {
|
||||
if (checkedIds.indexOf(item.id) !== -1) {
|
||||
temp.push(item.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
function authzClose() {
|
||||
authzDialogVisible.value = false;
|
||||
}
|
||||
async function authzOpen(roleId: any) {
|
||||
permissionTreeData.value = await permissionApi.GetTree();
|
||||
checkedKeys.value = [];
|
||||
currentRoleId.value = roleId;
|
||||
// this.treeData = ret.data
|
||||
await updateChecked(roleId);
|
||||
authzDialogVisible.value = true;
|
||||
}
|
||||
async function updateChecked(roleId: any) {
|
||||
let checkedIds = await api.getPermissionIds(roleId);
|
||||
// 找出所有的叶子节点
|
||||
checkedIds = getAllCheckedLeafNodeId(permissionTreeData.value, checkedIds, []);
|
||||
checkedKeys.value = checkedIds;
|
||||
}
|
||||
async function updatePermission() {
|
||||
const roleId = currentRoleId.value;
|
||||
const { checked, halfChecked } = permissionTreeRef.value.getChecked();
|
||||
const allChecked = [...checked, ...halfChecked];
|
||||
await api.DoAuthz(roleId, allChecked);
|
||||
authzClose();
|
||||
//await updateChecked(roleId);
|
||||
// 如果勾选节点中存在非叶子节点,tree组件会将其所有子节点全部勾选
|
||||
// 所以要找出所有叶子节点,仅勾选叶子节点,tree组件会将父节点同步勾选
|
||||
function getAllCheckedLeafNodeId(tree: any, checkedIds: any, temp: any) {
|
||||
for (let i = 0; i < tree.length; i++) {
|
||||
const item = tree[i];
|
||||
if (item.children && item.children.length !== 0) {
|
||||
getAllCheckedLeafNodeId(item.children, checkedIds, temp);
|
||||
} else {
|
||||
if (checkedIds.indexOf(item.id) !== -1) {
|
||||
temp.push(item.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
function authzClose() {
|
||||
authzDialogVisible.value = false;
|
||||
}
|
||||
async function authzOpen(roleId: any) {
|
||||
permissionTreeData.value = await permissionApi.GetTree();
|
||||
checkedKeys.value = [];
|
||||
currentRoleId.value = roleId;
|
||||
// this.treeData = ret.data
|
||||
await updateChecked(roleId);
|
||||
authzDialogVisible.value = true;
|
||||
}
|
||||
async function updateChecked(roleId: any) {
|
||||
let checkedIds = await api.getPermissionIds(roleId);
|
||||
// 找出所有的叶子节点
|
||||
checkedIds = getAllCheckedLeafNodeId(permissionTreeData.value, checkedIds, []);
|
||||
checkedKeys.value = checkedIds;
|
||||
}
|
||||
async function updatePermission() {
|
||||
const roleId = currentRoleId.value;
|
||||
const { checked, halfChecked } = permissionTreeRef.value.getChecked();
|
||||
const allChecked = [...checked, ...halfChecked];
|
||||
await api.DoAuthz(roleId, allChecked);
|
||||
authzClose();
|
||||
//await updateChecked(roleId);
|
||||
|
||||
message.success("授权成功");
|
||||
}
|
||||
message.success("授权成功");
|
||||
}
|
||||
|
||||
return {
|
||||
authzOpen,
|
||||
updatePermission,
|
||||
authzDialogVisible,
|
||||
permissionTreeData,
|
||||
checkedKeys,
|
||||
permissionTreeRef,
|
||||
};
|
||||
return {
|
||||
authzOpen,
|
||||
updatePermission,
|
||||
authzDialogVisible,
|
||||
permissionTreeData,
|
||||
checkedKeys,
|
||||
permissionTreeRef,
|
||||
};
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: "RoleManager",
|
||||
components: { FsPermissionTree },
|
||||
setup() {
|
||||
//授权配置
|
||||
const authz = useAuthz();
|
||||
const permission: UseCrudPermissionCompProps = {
|
||||
prefix: "sys:auth:role", //权限代码前缀
|
||||
extra: ({ hasActionPermission }: UseCrudPermissionExtraProps): any => {
|
||||
//额外按钮权限控制
|
||||
return { rowHandle: { buttons: { authz: { show: hasActionPermission("authz") } } } };
|
||||
},
|
||||
};
|
||||
name: "RoleManager",
|
||||
components: { FsPermissionTree },
|
||||
setup() {
|
||||
//授权配置
|
||||
const { t } = useI18n();
|
||||
const authz = useAuthz();
|
||||
const permission: UseCrudPermissionCompProps = {
|
||||
prefix: "sys:auth:role", //权限代码前缀
|
||||
extra: ({ hasActionPermission }: UseCrudPermissionExtraProps): any => {
|
||||
//额外按钮权限控制
|
||||
return { rowHandle: { buttons: { authz: { show: hasActionPermission("authz") } } } };
|
||||
},
|
||||
};
|
||||
|
||||
// 初始化crud配置
|
||||
// 此处传入permission进行通用按钮权限设置,会通过commonOptions去设置actionbar和rowHandle的按钮的show属性
|
||||
// 更多关于按钮权限的源代码设置,请参考 ./src/plugin/fast-crud/index.js (75-77行)
|
||||
// 初始化crud配置
|
||||
// 此处传入permission进行通用按钮权限设置,会通过commonOptions去设置actionbar和rowHandle的按钮的show属性
|
||||
// 更多关于按钮权限的源代码设置,请参考 ./src/plugin/fast-crud/index.js (75-77行)
|
||||
|
||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { authz, permission } });
|
||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { authz, permission } });
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
|
||||
onActivated(async () => {
|
||||
await crudExpose.doRefresh();
|
||||
});
|
||||
return {
|
||||
crudBinding,
|
||||
crudRef,
|
||||
...authz,
|
||||
};
|
||||
},
|
||||
onActivated(async () => {
|
||||
await crudExpose.doRefresh();
|
||||
});
|
||||
return {
|
||||
crudBinding,
|
||||
crudRef,
|
||||
...authz,
|
||||
t
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -4,282 +4,284 @@ import { useUserStore } from "/@/store/user";
|
||||
import { Modal, notification } from "ant-design-vue";
|
||||
import dayjs from "dayjs";
|
||||
import { useSettingStore } from "/@/store/settings";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
|
||||
export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
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;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
|
||||
const userStore = useUserStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const settingStore = useSettingStore();
|
||||
const userValidTimeEnabled = compute(() => {
|
||||
return settingStore.sysPublic.userValidTimeEnabled === true;
|
||||
});
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
rowHandle: {
|
||||
fixed: "right",
|
||||
buttons: {
|
||||
unlock: {
|
||||
title: "解除登录锁定",
|
||||
text: null,
|
||||
type: "link",
|
||||
icon: "ion:lock-open-outline",
|
||||
click: async ({ row }) => {
|
||||
Modal.confirm({
|
||||
title: "提示",
|
||||
content: "确定要解除该用户的登录锁定吗?",
|
||||
onOk: async () => {
|
||||
await api.Unlock(row.id);
|
||||
notification.success({
|
||||
message: "解除成功",
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
table: {
|
||||
scroll: {
|
||||
//使用固定列时需要设置此值,并且大于等于列宽度之和的值
|
||||
x: 1400,
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "id",
|
||||
type: "text",
|
||||
form: { show: false }, // 表单配置
|
||||
column: {
|
||||
width: 100,
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
createTime: {
|
||||
title: "创建时间",
|
||||
type: "datetime",
|
||||
form: { show: false }, // 表单配置
|
||||
column: {
|
||||
width: 180,
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
// updateTime: {
|
||||
// title: "修改时间",
|
||||
// type: "datetime",
|
||||
// form: { show: false }, // 表单配置
|
||||
// column: {
|
||||
// sortable: "update_time",
|
||||
// width: 180
|
||||
// }
|
||||
// },
|
||||
username: {
|
||||
title: "用户名",
|
||||
type: "text",
|
||||
search: { show: true }, // 开启查询
|
||||
form: {
|
||||
rules: [
|
||||
{ required: true, message: "请输入用户名" },
|
||||
{ max: 50, message: "最大50个字符" },
|
||||
],
|
||||
},
|
||||
editForm: { component: { disabled: false } },
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 200,
|
||||
},
|
||||
},
|
||||
password: {
|
||||
title: "密码",
|
||||
type: "text",
|
||||
key: "password",
|
||||
column: {
|
||||
show: false,
|
||||
},
|
||||
form: {
|
||||
rules: [{ max: 50, message: "最大50个字符" }],
|
||||
component: {
|
||||
showPassword: true,
|
||||
},
|
||||
helper: "填写则修改密码",
|
||||
},
|
||||
},
|
||||
nickName: {
|
||||
title: "昵称",
|
||||
type: "text",
|
||||
search: { show: true }, // 开启查询
|
||||
form: {
|
||||
rules: [{ max: 50, message: "最大50个字符" }],
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
email: {
|
||||
title: "邮箱",
|
||||
type: "text",
|
||||
search: { show: true }, // 开启查询
|
||||
form: {
|
||||
rules: [{ max: 50, message: "最大50个字符" }],
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 160,
|
||||
},
|
||||
},
|
||||
mobile: {
|
||||
title: "手机号",
|
||||
type: "text",
|
||||
search: { show: true }, // 开启查询
|
||||
form: {
|
||||
rules: [{ max: 50, message: "最大50个字符" }],
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 130,
|
||||
},
|
||||
},
|
||||
avatar: {
|
||||
title: "头像",
|
||||
type: "cropper-uploader",
|
||||
column: {
|
||||
width: 70,
|
||||
component: {
|
||||
//设置高度,修复操作列错位的问题
|
||||
style: {
|
||||
height: "30px",
|
||||
width: "auto",
|
||||
},
|
||||
buildUrl(key: string) {
|
||||
return `api/basic/file/download?&key=` + key;
|
||||
},
|
||||
},
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
vModel: "modelValue",
|
||||
valueType: "key",
|
||||
cropper: {
|
||||
aspectRatio: 1,
|
||||
autoCropArea: 1,
|
||||
viewMode: 0,
|
||||
},
|
||||
onReady: null,
|
||||
uploader: {
|
||||
type: "form",
|
||||
action: "/basic/file/upload",
|
||||
name: "file",
|
||||
headers: {
|
||||
Authorization: "Bearer " + userStore.getToken,
|
||||
},
|
||||
successHandle(res: any) {
|
||||
return res;
|
||||
},
|
||||
},
|
||||
buildUrl(key: string) {
|
||||
return `api/basic/file/download?&key=` + key;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
status: {
|
||||
title: "状态",
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "启用", value: 1, color: "green" },
|
||||
{ label: "禁用", value: 0, color: "red" },
|
||||
],
|
||||
}),
|
||||
column: {
|
||||
align: "center",
|
||||
sorter: true,
|
||||
width: 100,
|
||||
},
|
||||
},
|
||||
validTime: {
|
||||
title: "有效期",
|
||||
type: "date",
|
||||
form: {
|
||||
show: userValidTimeEnabled,
|
||||
},
|
||||
column: {
|
||||
align: "center",
|
||||
sorter: true,
|
||||
width: 100,
|
||||
show: userValidTimeEnabled,
|
||||
cellRender({ value }) {
|
||||
if (value == null || value === 0) {
|
||||
return "";
|
||||
}
|
||||
if (value < dayjs().valueOf()) {
|
||||
return <a-tag color={"red"}>已过期</a-tag>;
|
||||
}
|
||||
const date = dayjs(value).format("YYYY-MM-DD");
|
||||
return (
|
||||
<a-tag color={"green"} title={date}>
|
||||
<fs-time-humanize modelValue={value} options={{ largest: 1, units: ["y", "d", "h"] }} useFormatGreater={30000000000} />
|
||||
</a-tag>
|
||||
);
|
||||
},
|
||||
},
|
||||
valueBuilder({ value, row, key }) {
|
||||
if (value != null) {
|
||||
row[key] = dayjs(value);
|
||||
}
|
||||
},
|
||||
valueResolve({ value, row, key }) {
|
||||
if (value != null) {
|
||||
row[key] = value.valueOf();
|
||||
}
|
||||
},
|
||||
},
|
||||
remark: {
|
||||
title: "备注",
|
||||
type: "text",
|
||||
column: {
|
||||
sorter: true,
|
||||
},
|
||||
form: {
|
||||
rules: [{ max: 100, message: "最大100个字符" }],
|
||||
},
|
||||
},
|
||||
roles: {
|
||||
title: "角色",
|
||||
type: "dict-select",
|
||||
dict: dict({
|
||||
url: "/sys/authority/role/list",
|
||||
value: "id",
|
||||
label: "name",
|
||||
}), // 数据字典
|
||||
form: {
|
||||
component: { mode: "multiple" },
|
||||
},
|
||||
column: {
|
||||
width: 250,
|
||||
sortable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const settingStore = useSettingStore();
|
||||
const userValidTimeEnabled = compute(() => {
|
||||
return settingStore.sysPublic.userValidTimeEnabled === true;
|
||||
});
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
rowHandle: {
|
||||
fixed: "right",
|
||||
buttons: {
|
||||
unlock: {
|
||||
title: t("certd.unlockLogin"),
|
||||
text: null,
|
||||
type: "link",
|
||||
icon: "ion:lock-open-outline",
|
||||
click: async ({ row }) => {
|
||||
Modal.confirm({
|
||||
title: t("certd.notice"),
|
||||
content: t("certd.confirmUnlock"),
|
||||
onOk: async () => {
|
||||
await api.Unlock(row.id);
|
||||
notification.success({
|
||||
message: t("certd.unlockSuccess"),
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
table: {
|
||||
scroll: {
|
||||
//使用固定列时需要设置此值,并且大于等于列宽度之和的值
|
||||
x: 1400,
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "id",
|
||||
type: "text",
|
||||
form: { show: false }, // 表单配置
|
||||
column: {
|
||||
width: 100,
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
createTime: {
|
||||
title: t("certd.createTime"),
|
||||
type: "datetime",
|
||||
form: { show: false }, // 表单配置
|
||||
column: {
|
||||
width: 180,
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
// updateTime: {
|
||||
// title: "修改时间",
|
||||
// type: "datetime",
|
||||
// form: { show: false }, // 表单配置
|
||||
// column: {
|
||||
// sortable: "update_time",
|
||||
// width: 180
|
||||
// }
|
||||
// },
|
||||
username: {
|
||||
title: t("certd.username"),
|
||||
type: "text",
|
||||
search: { show: true }, // 开启查询
|
||||
form: {
|
||||
rules: [
|
||||
{ required: true, message: t("certd.enterUsername") },
|
||||
{ max: 50, message: t("certd.max50Chars") },
|
||||
],
|
||||
},
|
||||
editForm: { component: { disabled: false } },
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 200,
|
||||
},
|
||||
},
|
||||
password: {
|
||||
title: t("certd.password"),
|
||||
type: "text",
|
||||
key: "password",
|
||||
column: {
|
||||
show: false,
|
||||
},
|
||||
form: {
|
||||
rules: [{ max: 50, message: t("certd.max50Chars") }],
|
||||
component: {
|
||||
showPassword: true,
|
||||
},
|
||||
helper: t("certd.modifyPasswordIfFilled"),
|
||||
},
|
||||
},
|
||||
nickName: {
|
||||
title: t("certd.nickName"),
|
||||
type: "text",
|
||||
search: { show: true }, // 开启查询
|
||||
form: {
|
||||
rules: [{ max: 50, message: t("certd.max50Chars") }],
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
email: {
|
||||
title: t("certd.emaila"),
|
||||
type: "text",
|
||||
search: { show: true }, // 开启查询
|
||||
form: {
|
||||
rules: [{ max: 50, message: t("certd.max50Chars") }],
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 160,
|
||||
},
|
||||
},
|
||||
mobile: {
|
||||
title: t("certd.mobile"),
|
||||
type: "text",
|
||||
search: { show: true }, // 开启查询
|
||||
form: {
|
||||
rules: [{ max: 50, message: t("certd.max50Chars") }],
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 130,
|
||||
},
|
||||
},
|
||||
avatar: {
|
||||
title: t("certd.avatar"),
|
||||
type: "cropper-uploader",
|
||||
column: {
|
||||
width: 70,
|
||||
component: {
|
||||
style: {
|
||||
height: "30px",
|
||||
width: "auto",
|
||||
},
|
||||
buildUrl(key: string) {
|
||||
return `api/basic/file/download?&key=` + key;
|
||||
},
|
||||
},
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
vModel: "modelValue",
|
||||
valueType: "key",
|
||||
cropper: {
|
||||
aspectRatio: 1,
|
||||
autoCropArea: 1,
|
||||
viewMode: 0,
|
||||
},
|
||||
onReady: null,
|
||||
uploader: {
|
||||
type: "form",
|
||||
action: "/basic/file/upload",
|
||||
name: "file",
|
||||
headers: {
|
||||
Authorization: "Bearer " + userStore.getToken,
|
||||
},
|
||||
successHandle(res: any) {
|
||||
return res;
|
||||
},
|
||||
},
|
||||
buildUrl(key: string) {
|
||||
return `api/basic/file/download?&key=` + key;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
status: {
|
||||
title: t("certd.status"),
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: t("certd.enabled"), value: 1, color: "green" },
|
||||
{ label: t("certd.disabled"), value: 0, color: "red" },
|
||||
],
|
||||
}),
|
||||
column: {
|
||||
align: "center",
|
||||
sorter: true,
|
||||
width: 100,
|
||||
},
|
||||
},
|
||||
validTime: {
|
||||
title: t("certd.validTime"),
|
||||
type: "date",
|
||||
form: {
|
||||
show: userValidTimeEnabled,
|
||||
},
|
||||
column: {
|
||||
align: "center",
|
||||
sorter: true,
|
||||
width: 100,
|
||||
show: userValidTimeEnabled,
|
||||
cellRender({ value }) {
|
||||
if (value == null || value === 0) {
|
||||
return "";
|
||||
}
|
||||
if (value < dayjs().valueOf()) {
|
||||
return <a-tag color={"red"}>{t("certd.expired")}</a-tag>;
|
||||
}
|
||||
const date = dayjs(value).format("YYYY-MM-DD");
|
||||
return (
|
||||
<a-tag color={"green"} title={date}>
|
||||
<fs-time-humanize modelValue={value} options={{ largest: 1, units: ["y", "d", "h"] }} useFormatGreater={30000000000} />
|
||||
</a-tag>
|
||||
);
|
||||
},
|
||||
},
|
||||
valueBuilder({ value, row, key }) {
|
||||
if (value != null) {
|
||||
row[key] = dayjs(value);
|
||||
}
|
||||
},
|
||||
valueResolve({ value, row, key }) {
|
||||
if (value != null) {
|
||||
row[key] = value.valueOf();
|
||||
}
|
||||
},
|
||||
},
|
||||
remark: {
|
||||
title: t("certd.remark"),
|
||||
type: "text",
|
||||
column: {
|
||||
sorter: true,
|
||||
},
|
||||
form: {
|
||||
rules: [{ max: 100, message: t("certd.max100Chars") }],
|
||||
},
|
||||
},
|
||||
roles: {
|
||||
title: t("certd.roles"),
|
||||
type: "dict-select",
|
||||
dict: dict({
|
||||
url: "/sys/authority/role/list",
|
||||
value: "id",
|
||||
label: "name",
|
||||
}), // 数据字典
|
||||
form: {
|
||||
component: { mode: "multiple" },
|
||||
},
|
||||
column: {
|
||||
width: 250,
|
||||
sortable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,244 +8,244 @@ import { useSettingStore } from "/@/store/settings";
|
||||
import { Modal } from "ant-design-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 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 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;
|
||||
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: {
|
||||
minWidth: 200,
|
||||
fixed: "right"
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
column: {
|
||||
width: 100
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
domain: {
|
||||
title: "CNAME域名",
|
||||
type: "text",
|
||||
editForm: {
|
||||
component: {
|
||||
disabled: true
|
||||
}
|
||||
},
|
||||
search: {
|
||||
show: true
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
placeholder: "cname.handsfree.work"
|
||||
},
|
||||
helper: "需要一个右边DNS提供商注册的域名(也可以将其他域名的dns服务器转移到这几家来)。\nCNAME域名一旦确定不可修改,建议使用一级子域名",
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 200
|
||||
}
|
||||
},
|
||||
dnsProviderType: {
|
||||
title: "DNS提供商",
|
||||
type: "dict-select",
|
||||
search: {
|
||||
show: true
|
||||
},
|
||||
dict: dict({
|
||||
url: "pi/dnsProvider/list",
|
||||
value: "key",
|
||||
label: "title"
|
||||
}),
|
||||
form: {
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 150,
|
||||
component: {
|
||||
color: "auto"
|
||||
}
|
||||
}
|
||||
},
|
||||
accessId: {
|
||||
title: "DNS提供商授权",
|
||||
type: "dict-select",
|
||||
dict: dict({
|
||||
url: "/pi/access/list",
|
||||
value: "id",
|
||||
label: "name"
|
||||
}),
|
||||
form: {
|
||||
component: {
|
||||
name: "access-selector",
|
||||
vModel: "modelValue",
|
||||
type: compute(({ form }) => {
|
||||
return form.dnsProviderType;
|
||||
})
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 150,
|
||||
component: {
|
||||
color: "auto"
|
||||
}
|
||||
}
|
||||
},
|
||||
isDefault: {
|
||||
title: "是否默认",
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "是", value: true, color: "success" },
|
||||
{ label: "否", value: false, color: "default" }
|
||||
]
|
||||
}),
|
||||
form: {
|
||||
value: false,
|
||||
rules: [{ required: true, message: "请选择是否默认" }]
|
||||
},
|
||||
column: {
|
||||
align: "center",
|
||||
width: 100
|
||||
}
|
||||
},
|
||||
setDefault: {
|
||||
title: "设置默认",
|
||||
type: "text",
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
align: "center",
|
||||
conditionalRenderDisabled: true,
|
||||
cellRender: ({ row }) => {
|
||||
if (row.isDefault) {
|
||||
return;
|
||||
}
|
||||
const onClick = async () => {
|
||||
Modal.confirm({
|
||||
title: "提示",
|
||||
content: `确定要设置为默认吗?`,
|
||||
onOk: async () => {
|
||||
await api.SetDefault(row.id);
|
||||
await crudExpose.doRefresh();
|
||||
}
|
||||
});
|
||||
};
|
||||
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: {
|
||||
minWidth: 200,
|
||||
fixed: "right"
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
column: {
|
||||
width: 100
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
domain: {
|
||||
title: t("certd.cnameDomain"),
|
||||
type: "text",
|
||||
editForm: {
|
||||
component: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
placeholder: t("certd.cnameDomainPlaceholder"),
|
||||
},
|
||||
helper: t("certd.cnameDomainHelper"),
|
||||
rules: [{ required: true, message: t("certd.requiredField") }],
|
||||
},
|
||||
column: {
|
||||
width: 200,
|
||||
},
|
||||
},
|
||||
dnsProviderType: {
|
||||
title: t("certd.dnsProvider"),
|
||||
type: "dict-select",
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
dict: dict({
|
||||
url: "pi/dnsProvider/list",
|
||||
value: "key",
|
||||
label: "title",
|
||||
}),
|
||||
form: {
|
||||
rules: [{ required: true, message: t("certd.requiredField") }],
|
||||
},
|
||||
column: {
|
||||
width: 150,
|
||||
component: {
|
||||
color: "auto",
|
||||
},
|
||||
},
|
||||
},
|
||||
accessId: {
|
||||
title: t("certd.dnsProviderAuthorization"),
|
||||
type: "dict-select",
|
||||
dict: dict({
|
||||
url: "/pi/access/list",
|
||||
value: "id",
|
||||
label: "name",
|
||||
}),
|
||||
form: {
|
||||
component: {
|
||||
name: "access-selector",
|
||||
vModel: "modelValue",
|
||||
type: compute(({ form }) => {
|
||||
return form.dnsProviderType;
|
||||
}),
|
||||
},
|
||||
rules: [{ required: true, message: t("certd.requiredField") }],
|
||||
},
|
||||
column: {
|
||||
width: 150,
|
||||
component: {
|
||||
color: "auto",
|
||||
},
|
||||
},
|
||||
},
|
||||
isDefault: {
|
||||
title: t("certd.isDefault"),
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: t("certd.yes"), value: true, color: "success" },
|
||||
{ label: t("certd.no"), value: false, color: "default" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
value: false,
|
||||
rules: [{ required: true, message: t("certd.selectIsDefault") }],
|
||||
},
|
||||
column: {
|
||||
align: "center",
|
||||
width: 100,
|
||||
},
|
||||
},
|
||||
setDefault: {
|
||||
title: t("certd.setDefault"),
|
||||
type: "text",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
align: "center",
|
||||
conditionalRenderDisabled: true,
|
||||
cellRender: ({ row }) => {
|
||||
if (row.isDefault) {
|
||||
return;
|
||||
}
|
||||
const onClick = async () => {
|
||||
Modal.confirm({
|
||||
title: t("certd.prompt"),
|
||||
content: t("certd.confirmSetDefault"),
|
||||
onOk: async () => {
|
||||
await api.SetDefault(row.id);
|
||||
await crudExpose.doRefresh();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<a-button type={"link"} size={"small"} onClick={onClick}>
|
||||
设为默认
|
||||
</a-button>
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
title: "禁用/启用",
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "启用", value: false, color: "success" },
|
||||
{ label: "禁用", value: true, color: "error" }
|
||||
]
|
||||
}),
|
||||
form: {
|
||||
value: false
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
component: {
|
||||
title: "点击可禁用/启用",
|
||||
on: {
|
||||
async click({ value, row }) {
|
||||
Modal.confirm({
|
||||
title: "提示",
|
||||
content: `确定要${!value ? "禁用" : "启用"}吗?`,
|
||||
onOk: async () => {
|
||||
await api.SetDisabled(row.id, !value);
|
||||
await crudExpose.doRefresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return (
|
||||
<a-button type={"link"} size={"small"} onClick={onClick}>
|
||||
{t("certd.setAsDefault")}
|
||||
</a-button>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
disabled: {
|
||||
title: t("certd.disabled"),
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: t("certd.enabled"), value: false, color: "success" },
|
||||
{ label: t("certd.disabledLabel"), value: true, color: "error" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
value: false,
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
component: {
|
||||
title: t("certd.clickToToggle"),
|
||||
on: {
|
||||
async click({ value, row }) {
|
||||
Modal.confirm({
|
||||
title: t("certd.prompt"),
|
||||
content: t("certd.confirmToggleStatus", { action: !value ? t("certd.disable") : t("certd.enable") }),
|
||||
onOk: async () => {
|
||||
await api.SetDisabled(row.id, !value);
|
||||
await crudExpose.doRefresh();
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
createTime: {
|
||||
title: t("certd.createTime"),
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 160,
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
updateTime: {
|
||||
title: t("certd.updateTime"),
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
show: true,
|
||||
width: 160,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,60 +1,67 @@
|
||||
<template>
|
||||
<fs-page class="page-cert">
|
||||
<template #header>
|
||||
<div class="title">
|
||||
CNAME服务配置
|
||||
<span class="sub">
|
||||
此处配置的域名作为其他域名校验的代理,当别的域名需要申请证书时,通过CNAME映射到此域名上来验证所有权。好处是任何域名都可以通过此方式申请证书,也无需填写AccessSecret。
|
||||
<a href="https://certd.docmirror.cn/guide/feature/cname/" target="_blank">CNAME功能原理及使用说明</a>
|
||||
</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>
|
||||
<fs-page class="page-cert">
|
||||
<template #header>
|
||||
<div class="title">
|
||||
{{ t("certd.cnameTitle") }}
|
||||
<span class="sub">
|
||||
{{ t("certd.cnameDescription") }}
|
||||
<a href="https://certd.docmirror.cn/guide/feature/cname/" target="_blank">
|
||||
{{ t("certd.cnameLinkText") }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
<template #pagination-left>
|
||||
<a-tooltip :title="t('certd.batchDelete')">
|
||||
<fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</fs-crud>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onActivated, 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";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
defineOptions({
|
||||
name: "CnameProvider",
|
||||
name: "CnameProvider",
|
||||
});
|
||||
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("请先勾选记录");
|
||||
}
|
||||
if (selectedRowKeys.value?.length > 0) {
|
||||
Modal.confirm({
|
||||
title: t("certd.confirmTitle"),
|
||||
content: t("certd.confirmDeleteBatch", { count: selectedRowKeys.value.length }),
|
||||
async onOk() {
|
||||
await DeleteBatch(selectedRowKeys.value);
|
||||
message.info(t("certd.deleteSuccess"));
|
||||
crudExpose.doRefresh();
|
||||
selectedRowKeys.value = [];
|
||||
},
|
||||
});
|
||||
} else {
|
||||
message.error(t("certd.selectRecordsFirst"));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
onActivated(async () => {
|
||||
await crudExpose.doRefresh();
|
||||
await crudExpose.doRefresh();
|
||||
});
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,58 +1,63 @@
|
||||
<template>
|
||||
<fs-page class="page-cert">
|
||||
<template #header>
|
||||
<div class="title">
|
||||
插件管理
|
||||
<span class="sub">自定义插件处于BETA测试版,后续可能会有破坏性变更</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>
|
||||
<fs-page class="page-cert">
|
||||
<template #header>
|
||||
<div class="title">
|
||||
{{ t("certd.pluginManagement") }}
|
||||
<span class="sub">{{ t("certd.pluginBetaWarning") }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
<!-- <template #pagination-left>-->
|
||||
<!-- <a-tooltip :title="t('certd.batchDelete')">-->
|
||||
<!-- <fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>-->
|
||||
<!-- </a-tooltip>-->
|
||||
<!-- </template>-->
|
||||
</fs-crud>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onActivated, 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";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
defineOptions({
|
||||
name: "SysPlugin",
|
||||
name: "SysPlugin",
|
||||
});
|
||||
const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions });
|
||||
|
||||
onActivated(async () => {
|
||||
await crudExpose.doRefresh();
|
||||
await crudExpose.doRefresh();
|
||||
});
|
||||
|
||||
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("请先勾选记录");
|
||||
}
|
||||
if (selectedRowKeys.value?.length > 0) {
|
||||
Modal.confirm({
|
||||
title: t("certd.confirm"),
|
||||
content: t("certd.batchDeleteConfirm", { count: selectedRowKeys.value.length }),
|
||||
async onOk() {
|
||||
await DeleteBatch(selectedRowKeys.value);
|
||||
message.info(t("certd.deleteSuccess"));
|
||||
crudExpose.doRefresh();
|
||||
selectedRowKeys.value = [];
|
||||
},
|
||||
});
|
||||
} else {
|
||||
message.error(t("certd.pleaseSelectRecord"));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
|
||||
@@ -1,73 +1,86 @@
|
||||
<template>
|
||||
<fs-page class="page-setting-email">
|
||||
<template #header>
|
||||
<div class="title">
|
||||
邮件服务器设置
|
||||
<span class="sub">设置邮件发送服务器</span>
|
||||
</div>
|
||||
</template>
|
||||
<fs-page class="page-setting-email">
|
||||
<template #header>
|
||||
<div class="title">
|
||||
{{ t('certd.emailServerSettings') }}
|
||||
<span class="sub">{{ t('certd.setEmailSendingServer') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="flex-o">
|
||||
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" class="email-form-box" @finish="onFinish" @finish-failed="onFinishFailed">
|
||||
<div v-if="!formState.usePlus" class="email-form">
|
||||
<a-form-item label="使用自定义邮件服务器"> </a-form-item>
|
||||
<a-form-item label="SMTP域名" name="host" :rules="[{ required: true, message: '请输入smtp域名或ip' }]">
|
||||
<a-input v-model:value="formState.host" />
|
||||
</a-form-item>
|
||||
<div class="flex-o">
|
||||
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }"
|
||||
autocomplete="off" class="email-form-box" @finish="onFinish" @finish-failed="onFinishFailed">
|
||||
<div v-if="!formState.usePlus" class="email-form">
|
||||
<a-form-item :label="t('certd.useCustomEmailServer')"> </a-form-item>
|
||||
<a-form-item :label="t('certd.smtpDomain')" name="host"
|
||||
:rules="[{ required: true, message: t('certd.pleaseEnterSmtpDomain') }]">
|
||||
<a-input v-model:value="formState.host" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="SMTP端口" name="port" :rules="[{ required: true, message: '请输入smtp端口号' }]">
|
||||
<a-input v-model:value="formState.port" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('certd.smtpPort')" name="port"
|
||||
:rules="[{ required: true, message: t('certd.pleaseEnterSmtpPort') }]">
|
||||
<a-input v-model:value="formState.port" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="用户名" :name="['auth', 'user']" :rules="[{ required: true, message: '请输入用户名' }]">
|
||||
<a-input v-model:value="formState.auth.user" />
|
||||
</a-form-item>
|
||||
<a-form-item label="密码" :name="['auth', 'pass']" :rules="[{ required: true, message: '请输入密码' }]">
|
||||
<a-input-password v-model:value="formState.auth.pass" />
|
||||
<div class="helper">如果是qq邮箱,需要到qq邮箱的设置里面申请授权码作为密码</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="发件邮箱" name="sender" :rules="[{ required: true, message: '请输入发件邮箱' }]">
|
||||
<a-input v-model:value="formState.sender" />
|
||||
</a-form-item>
|
||||
<a-form-item label="是否ssl" name="secure">
|
||||
<a-switch v-model:checked="formState.secure" />
|
||||
<div class="helper">ssl和非ssl的smtp端口是不一样的,注意修改端口</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="忽略证书校验" :name="['tls', 'rejectUnauthorized']">
|
||||
<a-switch v-model:checked="formState.tls.rejectUnauthorized" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('certd.username')" :name="['auth', 'user']"
|
||||
:rules="[{ required: true, message: t('certd.pleaseEnterUsername') }]">
|
||||
<a-input v-model:value="formState.auth.user" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('certd.password')" :name="['auth', 'pass']"
|
||||
:rules="[{ required: true, message: t('certd.pleaseEnterPassword') }]">
|
||||
<a-input-password v-model:value="formState.auth.pass" />
|
||||
<div class="helper">{{ t('certd.qqEmailAuthCodeHelper') }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('certd.senderEmail')" name="sender"
|
||||
:rules="[{ required: true, message: t('certd.pleaseEnterSenderEmail') }]">
|
||||
<a-input v-model:value="formState.sender" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('certd.useSsl')" name="secure">
|
||||
<a-switch v-model:checked="formState.secure" />
|
||||
<div class="helper">{{ t('certd.sslPortNote') }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('certd.ignoreCertValidation')" :name="['tls', 'rejectUnauthorized']">
|
||||
<a-switch v-model:checked="formState.tls.rejectUnauthorized" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
|
||||
<a-button type="primary" html-type="submit">保存</a-button>
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div class="email-form">
|
||||
<a-form-item label="使用官方邮件服务器" name="usePlus">
|
||||
<div class="flex-o">
|
||||
<a-switch v-model:checked="formState.usePlus" :disabled="!settingStore.isPlus" @change="onUsePlusChanged" />
|
||||
<vip-button class="ml-5" mode="button"></vip-button>
|
||||
</div>
|
||||
<div class="helper">使用官方邮箱服务器直接发邮件,免除繁琐的配置</div>
|
||||
</a-form-item>
|
||||
</div>
|
||||
</a-form>
|
||||
</div>
|
||||
<div class="email-form">
|
||||
<a-form :model="testFormState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" @finish="onTestSend">
|
||||
<a-form-item label="测试收件邮箱" name="receiver" :rules="[{ required: true, message: '请输入测试收件邮箱' }]">
|
||||
<a-input v-model:value="testFormState.receiver" />
|
||||
<div class="helper">保存后再点击测试</div>
|
||||
<div class="helper">发送失败???<a href="https://certd.docmirror.cn/guide/use/email/" target="_blank">邮件配置帮助文档</a></div>
|
||||
<div class="helper">您还可以试试使用官方邮件服务器↗↗↗↗↗↗↗↗</div>
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
|
||||
<a-button type="primary" :loading="testFormState.loading" html-type="submit">测试</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</fs-page>
|
||||
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
|
||||
<a-button type="primary" html-type="submit">{{ t('certd.save') }}</a-button>
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div class="email-form">
|
||||
<a-form-item :label="t('certd.useOfficialEmailServer')" name="usePlus">
|
||||
<div class="flex-o">
|
||||
<a-switch v-model:checked="formState.usePlus" :disabled="!settingStore.isPlus"
|
||||
@change="onUsePlusChanged" />
|
||||
<vip-button class="ml-5" mode="button"></vip-button>
|
||||
</div>
|
||||
<div class="helper">{{ t('certd.useOfficialEmailServerHelper') }}</div>
|
||||
</a-form-item>
|
||||
</div>
|
||||
</a-form>
|
||||
</div>
|
||||
<div class="email-form">
|
||||
<a-form :model="testFormState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }"
|
||||
autocomplete="off" @finish="onTestSend">
|
||||
<a-form-item :label="t('certd.testReceiverEmail')" name="receiver"
|
||||
:rules="[{ required: true, message: t('certd.pleaseEnterTestReceiverEmail') }]">
|
||||
<a-input v-model:value="testFormState.receiver" />
|
||||
<div class="helper">{{ t('certd.saveBeforeTest') }}</div>
|
||||
<div class="helper">{{ t('certd.sendFailHelpDoc') }}<a
|
||||
href="https://certd.docmirror.cn/guide/use/email/" target="_blank">{{
|
||||
t('certd.emailConfigHelpDoc') }}</a></div>
|
||||
<div class="helper">{{ t('certd.tryOfficialEmailServer') }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
|
||||
<a-button type="primary" :loading="testFormState.loading" html-type="submit">{{ t('certd.test')
|
||||
}}</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive } from "vue";
|
||||
import * as api from "../api";
|
||||
@@ -75,96 +88,102 @@ import * as emailApi from "./api.email";
|
||||
import { notification } from "ant-design-vue";
|
||||
import { useSettingStore } from "/src/store/settings";
|
||||
import * as _ from "lodash-es";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
defineOptions({
|
||||
name: "EmailSetting",
|
||||
name: "EmailSetting",
|
||||
});
|
||||
|
||||
interface FormState {
|
||||
host: string;
|
||||
port: number;
|
||||
auth: {
|
||||
user: string;
|
||||
pass: string;
|
||||
};
|
||||
secure: boolean; // use TLS
|
||||
tls: {
|
||||
// do not fail on invalid certs
|
||||
rejectUnauthorized?: boolean;
|
||||
};
|
||||
sender: string;
|
||||
usePlus: boolean;
|
||||
host: string;
|
||||
port: number;
|
||||
auth: {
|
||||
user: string;
|
||||
pass: string;
|
||||
};
|
||||
secure: boolean; // use TLS
|
||||
tls: {
|
||||
// do not fail on invalid certs
|
||||
rejectUnauthorized?: boolean;
|
||||
};
|
||||
sender: string;
|
||||
usePlus: boolean;
|
||||
}
|
||||
|
||||
const formState = reactive<Partial<FormState>>({
|
||||
auth: {
|
||||
user: "",
|
||||
pass: "",
|
||||
},
|
||||
tls: {},
|
||||
usePlus: false,
|
||||
auth: {
|
||||
user: "",
|
||||
pass: "",
|
||||
},
|
||||
tls: {},
|
||||
usePlus: false,
|
||||
});
|
||||
|
||||
async function load() {
|
||||
const data: any = await api.EmailSettingsGet();
|
||||
_.merge(formState, data);
|
||||
const data: any = await api.EmailSettingsGet();
|
||||
_.merge(formState, data);
|
||||
}
|
||||
|
||||
load();
|
||||
|
||||
const onFinish = async (form: any) => {
|
||||
console.log("Success:", form);
|
||||
await api.EmailSettingsSave(form);
|
||||
notification.success({
|
||||
message: "保存成功",
|
||||
});
|
||||
console.log("Success:", form);
|
||||
await api.EmailSettingsSave(form);
|
||||
notification.success({
|
||||
message: t("certd.saveSuccess"),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const onFinishFailed = (errorInfo: any) => {
|
||||
// console.log("Failed:", errorInfo);
|
||||
// console.log("Failed:", errorInfo);
|
||||
};
|
||||
|
||||
async function onUsePlusChanged() {
|
||||
await api.EmailSettingsSave(formState);
|
||||
await api.EmailSettingsSave(formState);
|
||||
}
|
||||
|
||||
interface TestFormState {
|
||||
receiver: string;
|
||||
loading: boolean;
|
||||
receiver: string;
|
||||
loading: boolean;
|
||||
}
|
||||
const testFormState = reactive<TestFormState>({
|
||||
receiver: "",
|
||||
loading: false,
|
||||
receiver: "",
|
||||
loading: false,
|
||||
});
|
||||
async function onTestSend() {
|
||||
testFormState.loading = true;
|
||||
try {
|
||||
await emailApi.TestSend(testFormState.receiver);
|
||||
notification.success({
|
||||
message: "发送成功",
|
||||
});
|
||||
} finally {
|
||||
testFormState.loading = false;
|
||||
}
|
||||
testFormState.loading = true;
|
||||
try {
|
||||
await emailApi.TestSend(testFormState.receiver);
|
||||
notification.success({
|
||||
message: t("certd.sendSuccess"),
|
||||
});
|
||||
} finally {
|
||||
testFormState.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const settingStore = useSettingStore();
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.page-setting-email {
|
||||
.email-form-box {
|
||||
display: flex;
|
||||
}
|
||||
.email-form {
|
||||
width: 500px;
|
||||
margin: 20px;
|
||||
}
|
||||
.email-form-box {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.helper {
|
||||
padding: 1px;
|
||||
margin: 0px;
|
||||
color: #999;
|
||||
font-size: 10px;
|
||||
}
|
||||
.email-form {
|
||||
width: 500px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.helper {
|
||||
padding: 1px;
|
||||
margin: 0px;
|
||||
color: #999;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,54 +1,59 @@
|
||||
<template>
|
||||
<div class="sys-settings-form sys-settings-base">
|
||||
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" @finish="onFinish" @finish-failed="onFinishFailed">
|
||||
<a-form-item label="ICP备案号" :name="['public', 'icpNo']">
|
||||
<a-input v-model:value="formState.public.icpNo" placeholder="粤ICP备xxxxxxx号" />
|
||||
</a-form-item>
|
||||
<a-form-item label="网安备案号" :name="['public', 'mpsNo']">
|
||||
<a-input v-model:value="formState.public.mpsNo" placeholder="京公网安备xxxxxxx号" />
|
||||
</a-form-item>
|
||||
<div class="sys-settings-form sys-settings-base">
|
||||
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off"
|
||||
@finish="onFinish" @finish-failed="onFinishFailed">
|
||||
<a-form-item :label="t('certd.icpRegistrationNumber')" :name="['public', 'icpNo']">
|
||||
<a-input v-model:value="formState.public.icpNo" :placeholder="t('certd.icpPlaceholder')" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('certd.publicSecurityRegistrationNumber')" :name="['public', 'mpsNo']">
|
||||
<a-input v-model:value="formState.public.mpsNo" :placeholder="t('certd.publicSecurityPlaceholder')" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="开启小助手" :name="['public', 'aiChatEnabled']">
|
||||
<a-switch v-model:checked="formState.public.aiChatEnabled" />
|
||||
</a-form-item>
|
||||
<a-form-item label="允许爬虫" :name="['public', 'robots']">
|
||||
<a-switch v-model:checked="formState.public.robots" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('certd.enableAssistant')" :name="['public', 'aiChatEnabled']">
|
||||
<a-switch v-model:checked="formState.public.aiChatEnabled" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('certd.allowCrawlers')" :name="['public', 'robots']">
|
||||
<a-switch v-model:checked="formState.public.robots" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="HTTP代理" :name="['private', 'httpProxy']" :rules="urlRules">
|
||||
<a-input v-model:value="formState.private.httpProxy" placeholder="http://192.168.1.2:18010/" />
|
||||
<div class="helper">当某些网站被墙时可以配置</div>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('certd.httpProxy')" :name="['private', 'httpProxy']" :rules="urlRules">
|
||||
<a-input v-model:value="formState.private.httpProxy" :placeholder="t('certd.httpProxyPlaceholder')" />
|
||||
<div class="helper">{{ t('certd.httpProxyHelper') }}</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="HTTPS代理" :name="['private', 'httpsProxy']" :rules="urlRules">
|
||||
<div class="flex">
|
||||
<a-input v-model:value="formState.private.httpsProxy" placeholder="http://192.168.1.2:18010/" />
|
||||
<a-button class="ml-5" type="primary" :loading="testProxyLoading" title="保存后,再点击测试" @click="testProxy">测试</a-button>
|
||||
</div>
|
||||
<div class="helper">一般这两个代理填一样的,保存后再测试</div>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('certd.httpsProxy')" :name="['private', 'httpsProxy']" :rules="urlRules">
|
||||
<div class="flex">
|
||||
<a-input v-model:value="formState.private.httpsProxy"
|
||||
:placeholder="t('certd.httpsProxyPlaceholder')" />
|
||||
<a-button class="ml-5" type="primary" :loading="testProxyLoading"
|
||||
:title="t('certd.saveThenTestTitle')" @click="testProxy">{{ t('certd.testButton') }}</a-button>
|
||||
</div>
|
||||
<div class="helper">{{ t('certd.httpsProxyHelper') }}</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="双栈网络" :name="['private', 'dnsResultOrder']">
|
||||
<a-select v-model:value="formState.private.dnsResultOrder">
|
||||
<a-select-option value="verbatim">默认</a-select-option>
|
||||
<a-select-option value="ipv4first">IPV4优先</a-select-option>
|
||||
<a-select-option value="ipv6first">IPV6优先</a-select-option>
|
||||
</a-select>
|
||||
<div class="helper">如果选择IPv6优先,需要在docker-compose.yaml中启用ipv6</div>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('certd.dualStackNetwork')" :name="['private', 'dnsResultOrder']">
|
||||
<a-select v-model:value="formState.private.dnsResultOrder">
|
||||
<a-select-option value="verbatim">{{ t('certd.default') }}</a-select-option>
|
||||
<a-select-option value="ipv4first">{{ t('certd.ipv4Priority') }}</a-select-option>
|
||||
<a-select-option value="ipv6first">{{ t('certd.ipv6Priority') }}</a-select-option>
|
||||
</a-select>
|
||||
<div class="helper">{{ t('certd.dualStackNetworkHelper') }}</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="启用公共CNAME服务" :name="['private', 'commonCnameEnabled']">
|
||||
<a-switch v-model:checked="formState.private.commonCnameEnabled" />
|
||||
<div class="helper">是否可以使用公共CNAME服务,如果禁用,且没有设置<router-link to="/sys/cname/provider">自定义CNAME服务</router-link>,则无法使用CNAME代理方式申请证书</div>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('certd.enableCommonCnameService')" :name="['private', 'commonCnameEnabled']">
|
||||
<a-switch v-model:checked="formState.private.commonCnameEnabled" />
|
||||
<div class="helper" v-html="t('certd.commonCnameHelper')"></div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label=" " :colon="false" :wrapper-col="{ span: 8 }">
|
||||
<a-button :loading="saveLoading" type="primary" html-type="submit">保存</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
<a-form-item label=" " :colon="false" :wrapper-col="{ span: 8 }">
|
||||
<a-button :loading="saveLoading" type="primary" html-type="submit">{{ t('certd.saveButton')
|
||||
}}</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { reactive, ref } from "vue";
|
||||
import { SysSettings } from "/@/views/sys/settings/api";
|
||||
@@ -57,90 +62,94 @@ import { merge } from "lodash-es";
|
||||
import { useSettingStore } from "/@/store/settings";
|
||||
import { notification } from "ant-design-vue";
|
||||
import { util } from "/@/utils";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
defineOptions({
|
||||
name: "SettingBase",
|
||||
name: "SettingBase",
|
||||
});
|
||||
|
||||
const formState = reactive<Partial<SysSettings>>({
|
||||
public: {
|
||||
icpNo: "",
|
||||
mpsNo: "",
|
||||
},
|
||||
private: {},
|
||||
public: {
|
||||
icpNo: "",
|
||||
mpsNo: "",
|
||||
},
|
||||
private: {},
|
||||
});
|
||||
|
||||
const urlRules = ref({
|
||||
type: "url",
|
||||
message: "请输入正确的URL",
|
||||
type: "url",
|
||||
message: "请输入正确的URL",
|
||||
});
|
||||
|
||||
async function loadSysSettings() {
|
||||
const data: any = await api.SysSettingsGet();
|
||||
merge(formState, data);
|
||||
const data: any = await api.SysSettingsGet();
|
||||
merge(formState, data);
|
||||
}
|
||||
|
||||
const saveLoading = ref(false);
|
||||
loadSysSettings();
|
||||
const settingsStore = useSettingStore();
|
||||
const onFinish = async (form: any) => {
|
||||
try {
|
||||
saveLoading.value = true;
|
||||
await api.SysSettingsSave(form);
|
||||
await settingsStore.loadSysSettings();
|
||||
notification.success({
|
||||
message: "保存成功",
|
||||
});
|
||||
} finally {
|
||||
saveLoading.value = false;
|
||||
}
|
||||
try {
|
||||
saveLoading.value = true;
|
||||
await api.SysSettingsSave(form);
|
||||
await settingsStore.loadSysSettings();
|
||||
notification.success({
|
||||
message: t('certd.saveSuccess'),
|
||||
});
|
||||
} finally {
|
||||
saveLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const onFinishFailed = (errorInfo: any) => {
|
||||
// console.log("Failed:", errorInfo);
|
||||
// console.log("Failed:", errorInfo);
|
||||
};
|
||||
|
||||
async function stopOtherUserTimer() {
|
||||
await api.stopOtherUserTimer();
|
||||
notification.success({
|
||||
message: "停止成功",
|
||||
});
|
||||
await api.stopOtherUserTimer();
|
||||
notification.success({
|
||||
message: t('certd.stopSuccess'),
|
||||
});
|
||||
}
|
||||
|
||||
const testProxyLoading = ref(false);
|
||||
async function testProxy() {
|
||||
testProxyLoading.value = true;
|
||||
try {
|
||||
const res = await api.TestProxy();
|
||||
let success = true;
|
||||
if (res.google !== true || res.baidu !== true) {
|
||||
success = false;
|
||||
}
|
||||
const content = () => {
|
||||
return (
|
||||
<div>
|
||||
<div>Google: {res.google === true ? "成功" : util.maxLength(res.google)}</div>
|
||||
<div>Baidu: {res.baidu === true ? "成功" : util.maxLength(res.google)}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
if (!success) {
|
||||
notification.error({
|
||||
message: "测试失败",
|
||||
description: content,
|
||||
});
|
||||
return;
|
||||
}
|
||||
notification.success({
|
||||
message: "测试完成",
|
||||
description: content,
|
||||
});
|
||||
} finally {
|
||||
testProxyLoading.value = false;
|
||||
}
|
||||
testProxyLoading.value = true;
|
||||
try {
|
||||
const res = await api.TestProxy();
|
||||
let success = true;
|
||||
if (res.google !== true || res.baidu !== true) {
|
||||
success = false;
|
||||
}
|
||||
const content = () => {
|
||||
return (
|
||||
<div>
|
||||
<div>{t('certd.google')}: {res.google === true ? t('certd.success') : util.maxLength(res.google)}</div>
|
||||
<div>{t('certd.baidu')}: {res.baidu === true ? t('certd.success') : util.maxLength(res.baidu)}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
if (!success) {
|
||||
notification.error({
|
||||
message: t('certd.testFailed'),
|
||||
description: content,
|
||||
});
|
||||
return;
|
||||
}
|
||||
notification.success({
|
||||
message: t('certd.testCompleted'),
|
||||
description: content,
|
||||
});
|
||||
} finally {
|
||||
testProxyLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<style lang="less">
|
||||
.sys-settings-base {
|
||||
}
|
||||
.sys-settings-base {}
|
||||
</style>
|
||||
|
||||
@@ -1,67 +1,78 @@
|
||||
<template>
|
||||
<div class="sys-settings-form sys-settings-register">
|
||||
<a-form :model="formState" name="register" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" @finish="onFinish">
|
||||
<a-form-item label="管理其他用户流水线" :name="['public', 'managerOtherUserPipeline']">
|
||||
<a-switch v-model:checked="formState.public.managerOtherUserPipeline" />
|
||||
</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', 'registerEnabled']">
|
||||
<a-switch v-model:checked="formState.public.registerEnabled" />
|
||||
</a-form-item>
|
||||
<a-form-item label="开启用户有效期" :name="['public', 'userValidTimeEnabled']">
|
||||
<div class="flex-o">
|
||||
<a-switch v-model:checked="formState.public.userValidTimeEnabled" :disabled="!settingsStore.isPlus" />
|
||||
<vip-button class="ml-5" mode="button"></vip-button>
|
||||
</div>
|
||||
<div class="helper">有效期内用户可正常使用,失效后流水线将被停用</div>
|
||||
</a-form-item>
|
||||
<template v-if="formState.public.registerEnabled">
|
||||
<a-form-item label="开启用户名注册" :name="['public', 'usernameRegisterEnabled']">
|
||||
<a-switch v-model:checked="formState.public.usernameRegisterEnabled" />
|
||||
</a-form-item>
|
||||
<div class="sys-settings-form sys-settings-register">
|
||||
<a-form :model="formState" name="register" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }"
|
||||
autocomplete="off" @finish="onFinish">
|
||||
<a-form-item :label="t('certd.manageOtherUserPipeline')" :name="['public', 'managerOtherUserPipeline']">
|
||||
<a-switch v-model:checked="formState.public.managerOtherUserPipeline" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('certd.limitUserPipelineCount')" :name="['public', 'limitUserPipelineCount']">
|
||||
<a-input-number v-model:value="formState.public.limitUserPipelineCount" />
|
||||
<div class="helper">{{ t('certd.limitUserPipelineCountHelper') }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('certd.enableSelfRegistration')" :name="['public', 'registerEnabled']">
|
||||
<a-switch v-model:checked="formState.public.registerEnabled" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('certd.enableUserValidityPeriod')" :name="['public', 'userValidTimeEnabled']">
|
||||
<div class="flex-o">
|
||||
<a-switch v-model:checked="formState.public.userValidTimeEnabled"
|
||||
:disabled="!settingsStore.isPlus" />
|
||||
<vip-button class="ml-5" mode="button"></vip-button>
|
||||
</div>
|
||||
<div class="helper">{{ t('certd.userValidityPeriodHelper') }}</div>
|
||||
</a-form-item>
|
||||
<template v-if="formState.public.registerEnabled">
|
||||
<a-form-item :label="t('certd.enableUsernameRegistration')"
|
||||
:name="['public', 'usernameRegisterEnabled']">
|
||||
<a-switch v-model:checked="formState.public.usernameRegisterEnabled" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="开启邮箱注册" :name="['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>
|
||||
</a-form-item>
|
||||
<a-form-item label="开启手机号登录、注册" :name="['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>
|
||||
<template v-if="formState.public.smsLoginEnabled">
|
||||
<a-form-item label="短信提供商" :name="['private', 'sms', 'type']">
|
||||
<a-select v-model:value="formState.private.sms.type" @change="smsTypeChange">
|
||||
<a-select-option value="aliyun">阿里云短信</a-select-option>
|
||||
<a-select-option value="yfysms">易发云短信</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<template v-for="item of smsTypeDefineInputs" :key="item.simpleKey">
|
||||
<fs-form-item v-model="formState.private.sms.config[item.simpleKey]" :path="'private.sms.config' + item.key" :item="item" />
|
||||
</template>
|
||||
<a-form-item :label="t('certd.enableEmailRegistration')" :name="['public', 'emailRegisterEnabled']">
|
||||
<div class="flex-o">
|
||||
<a-switch v-model:checked="formState.public.emailRegisterEnabled"
|
||||
:disabled="!settingsStore.isPlus" :title="t('certd.proFeature')" />
|
||||
<vip-button class="ml-5" mode="button"></vip-button>
|
||||
</div>
|
||||
<div class="helper">
|
||||
<router-link to="/sys/settings/email">{{ t('certd.emailServerSetup') }}</router-link>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('certd.enableSmsLoginRegister')" :name="['public', 'smsLoginEnabled']">
|
||||
<div class="flex-o">
|
||||
<a-switch v-model:checked="formState.public.smsLoginEnabled" :disabled="!settingsStore.isComm"
|
||||
:title="t('certd.commFeature')" />
|
||||
<vip-button class="ml-5" mode="comm"></vip-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<template v-if="formState.public.smsLoginEnabled">
|
||||
<a-form-item :label="t('certd.smsProvider')" :name="['private', 'sms', 'type']">
|
||||
<a-select v-model:value="formState.private.sms.type" @change="smsTypeChange">
|
||||
<a-select-option value="aliyun">{{ t('certd.aliyunSms') }}</a-select-option>
|
||||
<a-select-option value="yfysms">{{ t('certd.yfySms') }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<template v-for="item of smsTypeDefineInputs" :key="item.simpleKey">
|
||||
<fs-form-item v-model="formState.private.sms.config[item.simpleKey]"
|
||||
:path="'private.sms.config' + item.key" :item="item" />
|
||||
</template>
|
||||
|
||||
<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>
|
||||
<a-form-item :label="t('certd.smsTest')">
|
||||
<div class="flex">
|
||||
<a-input v-model:value="testMobile" :placeholder="t('certd.testMobilePlaceholder')" />
|
||||
<loading-button class="ml-5" :title="t('certd.saveThenTest')" type="primary"
|
||||
:click="testSendSms">{{
|
||||
t('certd.testButton') }}</loading-button>
|
||||
</div>
|
||||
<div class="helper">{{ t('certd.saveThenTest') }}</div>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<a-form-item label=" " :colon="false" :wrapper-col="{ span: 16 }">
|
||||
<a-button :loading="saveLoading" type="primary" html-type="submit">保存</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
<a-form-item label=" " :colon="false" :wrapper-col="{ span: 16 }">
|
||||
<a-button :loading="saveLoading" type="primary" html-type="submit">{{ t('certd.saveButton')
|
||||
}}</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
@@ -71,123 +82,127 @@ import * as api from "/@/views/sys/settings/api";
|
||||
import { merge } from "lodash-es";
|
||||
import { useSettingStore } from "/@/store/settings";
|
||||
import { notification } from "ant-design-vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
defineOptions({
|
||||
name: "SettingRegister",
|
||||
name: "SettingRegister",
|
||||
});
|
||||
|
||||
const testMobile = ref("");
|
||||
async function testSendSms() {
|
||||
if (!testMobile.value) {
|
||||
notification.error({
|
||||
message: "请输入测试手机号",
|
||||
});
|
||||
return;
|
||||
}
|
||||
await api.TestSms({
|
||||
mobile: testMobile.value,
|
||||
});
|
||||
notification.success({
|
||||
message: "发送成功",
|
||||
});
|
||||
if (!testMobile.value) {
|
||||
notification.error({
|
||||
message: t('certd.enterTestMobile'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
await api.TestSms({
|
||||
mobile: testMobile.value,
|
||||
});
|
||||
notification.success({
|
||||
message: t('certd.sendSuccess'),
|
||||
});
|
||||
}
|
||||
|
||||
const formState = reactive<Partial<SysSettings>>({
|
||||
public: {
|
||||
registerEnabled: false,
|
||||
},
|
||||
private: {
|
||||
sms: {
|
||||
type: "aliyun",
|
||||
config: {},
|
||||
},
|
||||
},
|
||||
public: {
|
||||
registerEnabled: false,
|
||||
},
|
||||
private: {
|
||||
sms: {
|
||||
type: "aliyun",
|
||||
config: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
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: "此项必填",
|
||||
},
|
||||
leastOneLogin: {
|
||||
validator: (rule: any, value: any) => {
|
||||
if (!formState.public.passwordLoginEnabled && !formState.public.smsLoginEnabled) {
|
||||
return Promise.reject(t('certd.atLeastOneLoginRequired'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
required: {
|
||||
required: true,
|
||||
trigger: "change",
|
||||
message: t('certd.fieldRequired'),
|
||||
},
|
||||
};
|
||||
|
||||
async function smsTypeChange(value: string) {
|
||||
if (formState.private?.sms?.config) {
|
||||
formState.private.sms.config = {};
|
||||
}
|
||||
|
||||
await loadTypeDefine(value);
|
||||
async function smsTypeChange(value: string) {
|
||||
if (formState.private?.sms?.config) {
|
||||
formState.private.sms.config = {};
|
||||
}
|
||||
|
||||
await loadTypeDefine(value);
|
||||
}
|
||||
const smsTypeDefineInputs: Ref = ref({});
|
||||
async function loadTypeDefine(type: string) {
|
||||
const define: any = await api.GetSmsTypeDefine(type);
|
||||
const keys = Object.keys(define.input);
|
||||
const inputs: any = {};
|
||||
keys.forEach(key => {
|
||||
const value = define.input[key];
|
||||
value.simpleKey = key;
|
||||
value.key = "private.sms.config." + key;
|
||||
if (!value.component) {
|
||||
value.component = {
|
||||
name: "a-input",
|
||||
};
|
||||
}
|
||||
if (!value.component.name) {
|
||||
value.component.vModel = "value";
|
||||
}
|
||||
if (!value.rules) {
|
||||
value.rules = [];
|
||||
}
|
||||
if (value.required) {
|
||||
value.rules.push(rules.required);
|
||||
}
|
||||
const define: any = await api.GetSmsTypeDefine(type);
|
||||
const keys = Object.keys(define.input);
|
||||
const inputs: any = {};
|
||||
keys.forEach(key => {
|
||||
const value = define.input[key];
|
||||
value.simpleKey = key;
|
||||
value.key = "private.sms.config." + key;
|
||||
if (!value.component) {
|
||||
value.component = {
|
||||
name: "a-input",
|
||||
};
|
||||
}
|
||||
if (!value.component.name) {
|
||||
value.component.vModel = "value";
|
||||
}
|
||||
if (!value.rules) {
|
||||
value.rules = [];
|
||||
}
|
||||
if (value.required) {
|
||||
value.rules.push(rules.required);
|
||||
}
|
||||
|
||||
inputs[key] = define.input[key];
|
||||
});
|
||||
smsTypeDefineInputs.value = inputs;
|
||||
inputs[key] = define.input[key];
|
||||
});
|
||||
smsTypeDefineInputs.value = inputs;
|
||||
}
|
||||
|
||||
async function loadSysSettings() {
|
||||
const data: any = await api.SysSettingsGet();
|
||||
merge(formState, data);
|
||||
if (data?.private.sms?.type) {
|
||||
await loadTypeDefine(data.private.sms.type);
|
||||
}
|
||||
if (!settingsStore.isPlus) {
|
||||
formState.public.userValidTimeEnabled = false;
|
||||
formState.public.emailRegisterEnabled = false;
|
||||
}
|
||||
const data: any = await api.SysSettingsGet();
|
||||
merge(formState, data);
|
||||
if (data?.private.sms?.type) {
|
||||
await loadTypeDefine(data.private.sms.type);
|
||||
}
|
||||
if (!settingsStore.isPlus) {
|
||||
formState.public.userValidTimeEnabled = false;
|
||||
formState.public.emailRegisterEnabled = false;
|
||||
}
|
||||
|
||||
if (!settingsStore.isComm) {
|
||||
formState.public.smsLoginEnabled = false;
|
||||
}
|
||||
if (!settingsStore.isComm) {
|
||||
formState.public.smsLoginEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
const saveLoading = ref(false);
|
||||
loadSysSettings();
|
||||
const settingsStore = useSettingStore();
|
||||
const onFinish = async (form: any) => {
|
||||
try {
|
||||
saveLoading.value = true;
|
||||
await api.SysSettingsSave(form);
|
||||
await settingsStore.loadSysSettings();
|
||||
notification.success({
|
||||
message: "保存成功",
|
||||
});
|
||||
} finally {
|
||||
saveLoading.value = false;
|
||||
}
|
||||
try {
|
||||
saveLoading.value = true;
|
||||
await api.SysSettingsSave(form);
|
||||
await settingsStore.loadSysSettings();
|
||||
notification.success({
|
||||
message: t('certd.saveSuccess'),
|
||||
});
|
||||
} finally {
|
||||
saveLoading.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="less">
|
||||
.sys-settings-site {
|
||||
}
|
||||
.sys-settings-site {}
|
||||
</style>
|
||||
|
||||
@@ -1,54 +1,63 @@
|
||||
<template>
|
||||
<div class="sys-settings-form sys-settings-safe">
|
||||
<a-form ref="formRef" :model="formState" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off">
|
||||
<h2>站点隐藏</h2>
|
||||
<a-form-item label="启用站点隐藏" :name="['hidden', 'enabled']" :required="true">
|
||||
<div class="flex">
|
||||
<a-switch v-model:checked="formState.hidden.enabled" />
|
||||
</div>
|
||||
<div class="sys-settings-form sys-settings-safe">
|
||||
<a-form ref="formRef" :model="formState" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }"
|
||||
autocomplete="off">
|
||||
<h2>{{ t('certd.siteHide') }}</h2>
|
||||
<a-form-item :label="t('certd.enableSiteHide')" :name="['hidden', 'enabled']" :required="true">
|
||||
<div class="flex">
|
||||
<a-switch v-model:checked="formState.hidden.enabled" />
|
||||
</div>
|
||||
|
||||
<div class="helper">
|
||||
可以在平时关闭站点的可访问性,需要时再打开,增强站点安全性
|
||||
<a href="https://certd.docmirror.cn/guide/feature/safe/hidden" class="flex items-center" target="_blank">
|
||||
<span>帮助说明</span>
|
||||
<fs-icon class="ml-1" icon="mingcute:question-line"></fs-icon
|
||||
></a>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.hidden.enabled" label="随机地址" :name="['hidden', 'openPath']" :required="true">
|
||||
<a-input-search v-model:value="formState.hidden.openPath" :allow-clear="true" @search="changeOpenPath">
|
||||
<template #enterButton>
|
||||
<fs-icon icon="ion:refresh"></fs-icon>
|
||||
</template>
|
||||
</a-input-search>
|
||||
<div class="helper">站点被隐藏后,需要访问此URL解锁,才能正常访问</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.hidden.enabled" label="完整解除隐藏地址" :name="['hidden', 'openPath']" :required="true">
|
||||
<div class="flex"><fs-copyable v-model="openUrl" class="flex-inline"></fs-copyable></div>
|
||||
<div class="helper red">请保存好此地址</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.hidden.enabled" label="解除密码" :name="['hidden', 'openPassword']" :required="false">
|
||||
<a-input-password v-model:value="formState.hidden.openPassword" :allow-clear="true" />
|
||||
<div class="helper">解除隐藏时需要输入密码,第一次需要设置密码,填写则重置密码</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.hidden.enabled" label="自动隐藏时间" :name="['hidden', 'autoHiddenTimes']" :required="true">
|
||||
<a-input-number v-model:value="formState.hidden.autoHiddenTimes" :allow-clear="true" />
|
||||
<div class="helper">多少分钟内无请求自动隐藏</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.hidden.enabled" label="隐藏开放接口" :name="['hidden', 'hiddenOpenApi']" :required="true">
|
||||
<a-switch v-model:checked="formState.hidden.hiddenOpenApi" />
|
||||
<div class="helper">是否隐藏开放接口,是否放开/api/v1开头的接口</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.hidden.enabled" label="立即隐藏站点">
|
||||
<loading-button class="ml-1" type="primary" html-type="button" :click="doHiddenImmediate">立即隐藏</loading-button>
|
||||
</a-form-item>
|
||||
<a-form-item label=" " :colon="false" :wrapper-col="{ span: 16 }">
|
||||
<loading-button type="primary" html-type="button" :click="onClick">保存</loading-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
<div class="helper">
|
||||
{{ t('certd.siteHideDescription') }}
|
||||
<a href="https://certd.docmirror.cn/guide/feature/safe/hidden" class="flex items-center"
|
||||
target="_blank">
|
||||
<span>{{ t('certd.helpDoc') }}</span>
|
||||
<fs-icon class="ml-1" icon="mingcute:question-line"></fs-icon></a>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.hidden.enabled" :label="t('certd.randomAddress')"
|
||||
:name="['hidden', 'openPath']" :required="true">
|
||||
<a-input-search v-model:value="formState.hidden.openPath" :allow-clear="true" @search="changeOpenPath">
|
||||
<template #enterButton>
|
||||
<fs-icon icon="ion:refresh"></fs-icon>
|
||||
</template>
|
||||
</a-input-search>
|
||||
<div class="helper">{{ t('certd.siteHideUrlHelper') }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.hidden.enabled" :label="t('certd.fullUnlockUrl')"
|
||||
:name="['hidden', 'openPath']" :required="true">
|
||||
<div class="flex"><fs-copyable v-model="openUrl" class="flex-inline"></fs-copyable></div>
|
||||
<div class="helper red">{{ t('certd.saveThisUrl') }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.hidden.enabled" :label="t('certd.unlockPassword')"
|
||||
:name="['hidden', 'openPassword']" :required="false">
|
||||
<a-input-password v-model:value="formState.hidden.openPassword" :allow-clear="true" />
|
||||
<div class="helper">{{ t('certd.unlockPasswordHelper') }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.hidden.enabled" :label="t('certd.autoHideTime')"
|
||||
:name="['hidden', 'autoHiddenTimes']" :required="true">
|
||||
<a-input-number v-model:value="formState.hidden.autoHiddenTimes" :allow-clear="true" />
|
||||
<div class="helper">{{ t('certd.autoHideTimeHelper') }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.hidden.enabled" :label="t('certd.hideOpenApi')"
|
||||
:name="['hidden', 'hiddenOpenApi']" :required="true">
|
||||
<a-switch v-model:checked="formState.hidden.hiddenOpenApi" />
|
||||
<div class="helper">{{ t('certd.hideOpenApiHelper') }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.hidden.enabled" :label="t('certd.hideSiteImmediately')">
|
||||
<loading-button class="ml-1" type="primary" html-type="button" :click="doHiddenImmediate">{{
|
||||
t('certd.hideImmediately') }}</loading-button>
|
||||
</a-form-item>
|
||||
<a-form-item label=" " :colon="false" :wrapper-col="{ span: 16 }">
|
||||
<loading-button type="primary" html-type="button" :click="onClick">{{ t('certd.save')
|
||||
}}</loading-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { computed, reactive, ref } from "vue";
|
||||
import { merge } from "lodash-es";
|
||||
@@ -56,106 +65,109 @@ import { Modal, notification } from "ant-design-vue";
|
||||
import { request } from "/@/api/service";
|
||||
import { util, utils } from "/@/utils";
|
||||
import { useSettingStore } from "/@/store/settings";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
defineOptions({
|
||||
name: "SettingSafe",
|
||||
name: "SettingSafe",
|
||||
});
|
||||
const settingsStore = useSettingStore();
|
||||
const api = {
|
||||
async SettingGet() {
|
||||
return await request({
|
||||
url: "/sys/settings/safe/get",
|
||||
method: "post",
|
||||
});
|
||||
},
|
||||
async SettingSave(data: any) {
|
||||
return await request({
|
||||
url: "/sys/settings/safe/save",
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
},
|
||||
async HiddenImmediate() {
|
||||
return await request({
|
||||
url: "/sys/settings/safe/hidden",
|
||||
method: "post",
|
||||
});
|
||||
},
|
||||
async SettingGet() {
|
||||
return await request({
|
||||
url: "/sys/settings/safe/get",
|
||||
method: "post",
|
||||
});
|
||||
},
|
||||
async SettingSave(data: any) {
|
||||
return await request({
|
||||
url: "/sys/settings/safe/save",
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
},
|
||||
async HiddenImmediate() {
|
||||
return await request({
|
||||
url: "/sys/settings/safe/hidden",
|
||||
method: "post",
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const defaultState = {
|
||||
hidden: {
|
||||
enabled: false,
|
||||
autoHiddenTimes: 5,
|
||||
hiddenOpenApi: false,
|
||||
},
|
||||
hidden: {
|
||||
enabled: false,
|
||||
autoHiddenTimes: 5,
|
||||
hiddenOpenApi: false,
|
||||
},
|
||||
};
|
||||
const formRef = ref<any>(defaultState);
|
||||
type SiteHidden = {
|
||||
enabled: boolean;
|
||||
openPath?: string;
|
||||
autoHiddenTimes?: number;
|
||||
openPassword?: string;
|
||||
hiddenOpenApi?: boolean;
|
||||
enabled: boolean;
|
||||
openPath?: string;
|
||||
autoHiddenTimes?: number;
|
||||
openPassword?: string;
|
||||
hiddenOpenApi?: boolean;
|
||||
};
|
||||
|
||||
const formState = reactive<
|
||||
Partial<{
|
||||
hidden: SiteHidden;
|
||||
}>
|
||||
Partial<{
|
||||
hidden: SiteHidden;
|
||||
}>
|
||||
>({
|
||||
hidden: { enabled: false },
|
||||
hidden: { enabled: false },
|
||||
});
|
||||
|
||||
function changeOpenPath() {
|
||||
formState.hidden.openPath = util.randomString(16);
|
||||
formState.hidden.openPath = util.randomString(16);
|
||||
}
|
||||
|
||||
async function loadSettings() {
|
||||
const data: any = await api.SettingGet();
|
||||
merge(formState, defaultState, formState, data);
|
||||
if (!formState.hidden.openPath) {
|
||||
changeOpenPath();
|
||||
}
|
||||
const data: any = await api.SettingGet();
|
||||
merge(formState, defaultState, formState, data);
|
||||
if (!formState.hidden.openPath) {
|
||||
changeOpenPath();
|
||||
}
|
||||
}
|
||||
|
||||
loadSettings();
|
||||
|
||||
const openUrl = computed(() => {
|
||||
const url = new URL(window.location.href);
|
||||
url.pathname = `/api/unhidden/${formState.hidden?.openPath || ""}`;
|
||||
//@ts-ignore
|
||||
url.query = undefined;
|
||||
url.hash = "";
|
||||
return url.href;
|
||||
const url = new URL(window.location.href);
|
||||
url.pathname = `/api/unhidden/${formState.hidden?.openPath || ""}`;
|
||||
//@ts-ignore
|
||||
url.query = undefined;
|
||||
url.hash = "";
|
||||
return url.href;
|
||||
});
|
||||
|
||||
const onClick = async () => {
|
||||
const form = await formRef.value.validateFields();
|
||||
//密码md5
|
||||
// if (form.hidden?.openPassword) {
|
||||
// form.hidden.openPassword = util.hash.md5(form.hidden.openPassword);
|
||||
// }
|
||||
await api.SettingSave(form);
|
||||
await loadSettings();
|
||||
notification.success({
|
||||
message: "保存成功",
|
||||
});
|
||||
const form = await formRef.value.validateFields();
|
||||
//密码md5
|
||||
// if (form.hidden?.openPassword) {
|
||||
// form.hidden.openPassword = util.hash.md5(form.hidden.openPassword);
|
||||
// }
|
||||
await api.SettingSave(form);
|
||||
await loadSettings();
|
||||
notification.success({
|
||||
message: t('certd.saveSuccess'),
|
||||
});
|
||||
};
|
||||
|
||||
async function doHiddenImmediate() {
|
||||
Modal.confirm({
|
||||
title: "确定要立即隐藏站点吗?",
|
||||
content: "隐藏后,将无法访问站点,请谨慎操作",
|
||||
async onOk() {
|
||||
await api.HiddenImmediate();
|
||||
notification.success({
|
||||
message: "站点已隐藏",
|
||||
});
|
||||
},
|
||||
});
|
||||
Modal.confirm({
|
||||
title: t('certd.confirmHideSiteTitle'),
|
||||
content: t('certd.confirmHideSiteContent'),
|
||||
async onOk() {
|
||||
await api.HiddenImmediate();
|
||||
notification.success({
|
||||
message: t('certd.siteHiddenSuccess'),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
<style lang="less">
|
||||
.sys-settings-base {
|
||||
}
|
||||
.sys-settings-base {}
|
||||
</style>
|
||||
|
||||
@@ -4,317 +4,333 @@ import SuiteValue from "./suite-value.vue";
|
||||
import SuiteValueEdit from "./suite-value-edit.vue";
|
||||
import PriceEdit from "./price-edit.vue";
|
||||
import DurationPriceValue from "/@/views/sys/suite/product/duration-price-value.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const emit = context.emit;
|
||||
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 { t } = useI18n();
|
||||
const emit = context.emit;
|
||||
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 addRequest = async ({ form }: AddReq) => {
|
||||
const res = await api.AddObj(form);
|
||||
return res;
|
||||
};
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
table: {
|
||||
onRefreshed: () => {
|
||||
emit("refreshed");
|
||||
}
|
||||
},
|
||||
search: {
|
||||
show: false
|
||||
},
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest
|
||||
},
|
||||
pagination: {
|
||||
show: false,
|
||||
pageSize: 999999
|
||||
},
|
||||
rowHandle: {
|
||||
minWidth: 200,
|
||||
fixed: "right"
|
||||
},
|
||||
form: {
|
||||
group: {
|
||||
groups: {
|
||||
base: {
|
||||
header: "基础信息",
|
||||
columns: ["title", "type", "disabled", "order", "supportBuy", "intro"]
|
||||
},
|
||||
content: {
|
||||
header: "套餐内容",
|
||||
columns: ["content.maxDomainCount", "content.maxPipelineCount", "content.maxDeployCount", "content.maxMonitorCount"]
|
||||
},
|
||||
price: {
|
||||
header: "价格",
|
||||
columns: ["durationPrices"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
columns: {
|
||||
// id: {
|
||||
// title: "ID",
|
||||
// key: "id",
|
||||
// type: "number",
|
||||
// column: {
|
||||
// width: 100
|
||||
// },
|
||||
// form: {
|
||||
// show: false
|
||||
// }
|
||||
// },
|
||||
title: {
|
||||
title: "套餐名称",
|
||||
type: "text",
|
||||
search: {
|
||||
show: true
|
||||
},
|
||||
form: {
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 200
|
||||
}
|
||||
},
|
||||
type: {
|
||||
title: "类型",
|
||||
type: "dict-select",
|
||||
editForm: {
|
||||
component: {
|
||||
disabled: true
|
||||
}
|
||||
},
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "套餐", value: "suite" },
|
||||
{ label: "加量包", value: "addon" }
|
||||
]
|
||||
}),
|
||||
form: {
|
||||
value: "suite",
|
||||
rules: [{ required: true, message: "此项必填" }],
|
||||
helper: "套餐:同一时间只有最新购买的一个生效\n加量包:可购买多个,购买后立即生效,不影响套餐\n套餐和加量包数量可叠加"
|
||||
},
|
||||
column: {
|
||||
width: 80,
|
||||
align: "center"
|
||||
},
|
||||
valueBuilder: ({ row }) => {
|
||||
if (row.content) {
|
||||
row.content = JSON.parse(row.content);
|
||||
}
|
||||
if (row.durationPrices) {
|
||||
row.durationPrices = JSON.parse(row.durationPrices);
|
||||
}
|
||||
},
|
||||
valueResolve: ({ form }) => {
|
||||
if (form.content) {
|
||||
form.content = JSON.stringify(form.content);
|
||||
}
|
||||
if (form.durationPrices) {
|
||||
form.durationPrices = JSON.stringify(form.durationPrices);
|
||||
}
|
||||
}
|
||||
},
|
||||
"content.maxDomainCount": {
|
||||
title: "域名数量",
|
||||
type: "text",
|
||||
form: {
|
||||
key: ["content", "maxDomainCount"],
|
||||
component: {
|
||||
name: SuiteValueEdit,
|
||||
vModel: "modelValue",
|
||||
unit: "个"
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
component: {
|
||||
name: SuiteValue,
|
||||
vModel: "modelValue",
|
||||
unit: "个"
|
||||
}
|
||||
}
|
||||
},
|
||||
"content.maxPipelineCount": {
|
||||
title: "流水线数量",
|
||||
type: "text",
|
||||
form: {
|
||||
key: ["content", "maxPipelineCount"],
|
||||
component: {
|
||||
name: SuiteValueEdit,
|
||||
vModel: "modelValue",
|
||||
unit: "条"
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
component: {
|
||||
name: SuiteValue,
|
||||
vModel: "modelValue",
|
||||
unit: "条"
|
||||
}
|
||||
}
|
||||
},
|
||||
"content.maxDeployCount": {
|
||||
title: "部署次数",
|
||||
type: "text",
|
||||
form: {
|
||||
key: ["content", "maxDeployCount"],
|
||||
component: {
|
||||
name: SuiteValueEdit,
|
||||
vModel: "modelValue",
|
||||
unit: "次"
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
component: {
|
||||
name: SuiteValue,
|
||||
vModel: "modelValue",
|
||||
unit: "次"
|
||||
}
|
||||
}
|
||||
},
|
||||
"content.maxMonitorCount": {
|
||||
title: "证书监控数量",
|
||||
type: "text",
|
||||
form: {
|
||||
key: ["content", "maxMonitorCount"],
|
||||
component: {
|
||||
name: SuiteValueEdit,
|
||||
vModel: "modelValue",
|
||||
unit: "个"
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 120,
|
||||
component: {
|
||||
name: SuiteValue,
|
||||
vModel: "modelValue",
|
||||
unit: "个"
|
||||
}
|
||||
}
|
||||
},
|
||||
durationPrices: {
|
||||
title: "时长及价格",
|
||||
type: "text",
|
||||
form: {
|
||||
title: "选择时长",
|
||||
component: {
|
||||
name: PriceEdit,
|
||||
vModel: "modelValue",
|
||||
edit: true,
|
||||
style: {
|
||||
minHeight: "120px"
|
||||
}
|
||||
},
|
||||
col: {
|
||||
span: 24
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
component: {
|
||||
name: DurationPriceValue,
|
||||
vModel: "modelValue"
|
||||
},
|
||||
width: 350
|
||||
}
|
||||
},
|
||||
supportBuy: {
|
||||
title: "支持购买",
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "支持购买", value: true, color: "success" },
|
||||
{ label: "不能购买", value: false, color: "gray" }
|
||||
]
|
||||
}),
|
||||
form: {
|
||||
value: true
|
||||
},
|
||||
column: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
title: "上下架",
|
||||
type: "dict-radio",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ value: false, label: "上架", color: "green" },
|
||||
{ value: true, label: "下架", color: "gray" }
|
||||
]
|
||||
}),
|
||||
form: {
|
||||
value: false
|
||||
},
|
||||
column: {
|
||||
width: 100
|
||||
}
|
||||
},
|
||||
order: {
|
||||
title: "排序",
|
||||
type: "number",
|
||||
form: {
|
||||
helper: "越小越靠前",
|
||||
value: 0
|
||||
},
|
||||
column: {
|
||||
width: 100
|
||||
}
|
||||
},
|
||||
intro: {
|
||||
title: "说明",
|
||||
type: "textarea",
|
||||
column: {
|
||||
width: 200
|
||||
}
|
||||
},
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return {
|
||||
crudOptions: {
|
||||
table: {
|
||||
onRefreshed: () => {
|
||||
emit("refreshed");
|
||||
}
|
||||
},
|
||||
search: {
|
||||
show: false
|
||||
},
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest
|
||||
},
|
||||
pagination: {
|
||||
show: false,
|
||||
pageSize: 999999
|
||||
},
|
||||
rowHandle: {
|
||||
minWidth: 200,
|
||||
fixed: "right"
|
||||
},
|
||||
form: {
|
||||
group: {
|
||||
groups: {
|
||||
base: {
|
||||
header: t('certd.basicInfo'),
|
||||
columns: [
|
||||
t('certd.titlea'),
|
||||
t('certd.type'),
|
||||
t('certd.disabled'),
|
||||
t('certd.ordera'),
|
||||
t('certd.supportBuy'),
|
||||
t('certd.intro')
|
||||
]
|
||||
},
|
||||
content: {
|
||||
header: t('certd.packageContent'),
|
||||
columns: [
|
||||
t('certd.maxDomainCount'),
|
||||
t('certd.maxPipelineCount'),
|
||||
t('certd.maxDeployCount'),
|
||||
t('certd.maxMonitorCount')
|
||||
]
|
||||
},
|
||||
price: {
|
||||
header: t('certd.price'),
|
||||
columns: [
|
||||
t('certd.durationPrices')
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
columns: {
|
||||
// id: {
|
||||
// title: "ID",
|
||||
// key: "id",
|
||||
// type: "number",
|
||||
// column: {
|
||||
// width: 100
|
||||
// },
|
||||
// form: {
|
||||
// show: false
|
||||
// }
|
||||
// },
|
||||
title: {
|
||||
title: t('certd.packageName'),
|
||||
type: "text",
|
||||
search: {
|
||||
show: true
|
||||
},
|
||||
form: {
|
||||
rules: [{ required: true, message: t('certd.requiredField') }]
|
||||
},
|
||||
column: {
|
||||
width: 200
|
||||
}
|
||||
},
|
||||
type: {
|
||||
title: t('certd.type'),
|
||||
type: "dict-select",
|
||||
editForm: {
|
||||
component: {
|
||||
disabled: true
|
||||
}
|
||||
},
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: t('certd.suite'), value: "suite" },
|
||||
{ label: t('certd.addon'), value: "addon" }
|
||||
]
|
||||
}),
|
||||
form: {
|
||||
value: "suite",
|
||||
rules: [{ required: true, message: t('certd.requiredField') }],
|
||||
helper: t('certd.typeHelper')
|
||||
},
|
||||
column: {
|
||||
width: 80,
|
||||
align: "center"
|
||||
},
|
||||
valueBuilder: ({ row }) => {
|
||||
if (row.content) {
|
||||
row.content = JSON.parse(row.content);
|
||||
}
|
||||
if (row.durationPrices) {
|
||||
row.durationPrices = JSON.parse(row.durationPrices);
|
||||
}
|
||||
},
|
||||
valueResolve: ({ form }) => {
|
||||
if (form.content) {
|
||||
form.content = JSON.stringify(form.content);
|
||||
}
|
||||
if (form.durationPrices) {
|
||||
form.durationPrices = JSON.stringify(form.durationPrices);
|
||||
}
|
||||
}
|
||||
},
|
||||
"content.maxDomainCount": {
|
||||
title: t('certd.domainCount'),
|
||||
type: "text",
|
||||
form: {
|
||||
key: ["content", "maxDomainCount"],
|
||||
component: {
|
||||
name: SuiteValueEdit,
|
||||
vModel: "modelValue",
|
||||
unit: t('certd.unitCount')
|
||||
},
|
||||
rules: [{ required: true, message: t('certd.requiredField') }]
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
component: {
|
||||
name: SuiteValue,
|
||||
vModel: "modelValue",
|
||||
unit: t('certd.unitCount')
|
||||
}
|
||||
}
|
||||
},
|
||||
"content.maxPipelineCount": {
|
||||
title: t('certd.pipelineCount'),
|
||||
type: "text",
|
||||
form: {
|
||||
key: ["content", "maxPipelineCount"],
|
||||
component: {
|
||||
name: SuiteValueEdit,
|
||||
vModel: "modelValue",
|
||||
unit: t('certd.unitPipeline')
|
||||
},
|
||||
rules: [{ required: true, message: t('certd.requiredField') }]
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
component: {
|
||||
name: SuiteValue,
|
||||
vModel: "modelValue",
|
||||
unit: t('certd.unitPipeline')
|
||||
}
|
||||
}
|
||||
},
|
||||
"content.maxDeployCount": {
|
||||
title: t('certd.deployCount'),
|
||||
type: "text",
|
||||
form: {
|
||||
key: ["content", "maxDeployCount"],
|
||||
component: {
|
||||
name: SuiteValueEdit,
|
||||
vModel: "modelValue",
|
||||
unit: t('certd.unitDeploy')
|
||||
},
|
||||
rules: [{ required: true, message: t('certd.requiredField') }]
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
component: {
|
||||
name: SuiteValue,
|
||||
vModel: "modelValue",
|
||||
unit: t('certd.unitDeploy')
|
||||
}
|
||||
}
|
||||
},
|
||||
"content.maxMonitorCount": {
|
||||
title: t('certd.monitorCount'),
|
||||
type: "text",
|
||||
form: {
|
||||
key: ["content", "maxMonitorCount"],
|
||||
component: {
|
||||
name: SuiteValueEdit,
|
||||
vModel: "modelValue",
|
||||
unit: t('certd.unitCount')
|
||||
},
|
||||
rules: [{ required: true, message: t('certd.requiredField') }]
|
||||
},
|
||||
column: {
|
||||
width: 120,
|
||||
component: {
|
||||
name: SuiteValue,
|
||||
vModel: "modelValue",
|
||||
unit: t('certd.unitCount')
|
||||
}
|
||||
}
|
||||
},
|
||||
durationPrices: {
|
||||
title: t('certd.durationPriceTitle'),
|
||||
type: "text",
|
||||
form: {
|
||||
title: t('certd.selectDuration'),
|
||||
component: {
|
||||
name: PriceEdit,
|
||||
vModel: "modelValue",
|
||||
edit: true,
|
||||
style: {
|
||||
minHeight: "120px"
|
||||
}
|
||||
},
|
||||
col: {
|
||||
span: 24
|
||||
},
|
||||
rules: [{ required: true, message: t('certd.requiredField') }]
|
||||
},
|
||||
column: {
|
||||
component: {
|
||||
name: DurationPriceValue,
|
||||
vModel: "modelValue"
|
||||
},
|
||||
width: 350
|
||||
}
|
||||
},
|
||||
supportBuy: {
|
||||
title: t('certd.supportBuy'),
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: t('certd.supportPurchase'), value: true, color: "success" },
|
||||
{ label: t('certd.cannotPurchase'), value: false, color: "gray" }
|
||||
]
|
||||
}),
|
||||
form: {
|
||||
value: true
|
||||
},
|
||||
column: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
title: t('certd.shelfStatus'),
|
||||
type: "dict-radio",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ value: false, label: t('certd.onShelf'), color: "green" },
|
||||
{ value: true, label: t('certd.offShelf'), color: "gray" }
|
||||
]
|
||||
}),
|
||||
form: {
|
||||
value: false
|
||||
},
|
||||
column: {
|
||||
width: 100
|
||||
}
|
||||
},
|
||||
order: {
|
||||
title: t('certd.ordera'),
|
||||
type: "number",
|
||||
form: {
|
||||
helper: t('certd.orderHelper'),
|
||||
value: 0
|
||||
},
|
||||
column: {
|
||||
width: 100
|
||||
}
|
||||
},
|
||||
intro: {
|
||||
title: t('certd.description'),
|
||||
type: "textarea",
|
||||
column: {
|
||||
width: 200
|
||||
}
|
||||
},
|
||||
createTime: {
|
||||
title: t('certd.createTime'),
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 160,
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
updateTime: {
|
||||
title: t('certd.updateTime'),
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
show: true,
|
||||
width: 160
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,392 +8,397 @@ import createCrudOptionsUser from "/@/views/sys/authority/user/crud";
|
||||
import UserSuiteStatus from "/@/views/certd/suite/mine/user-suite-status.vue";
|
||||
import SuiteDurationSelector from "../setting/suite-duration-selector.vue";
|
||||
import dayjs from "dayjs";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
|
||||
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const api = sysUserSuiteApi;
|
||||
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 { t } = useI18n();
|
||||
const api = sysUserSuiteApi;
|
||||
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.PresentSuite(form);
|
||||
return res;
|
||||
};
|
||||
const addRequest = async (req: AddReq) => {
|
||||
const { form } = req;
|
||||
const res = await api.PresentSuite(form);
|
||||
return res;
|
||||
};
|
||||
|
||||
const router = useRouter();
|
||||
const router = useRouter();
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest
|
||||
},
|
||||
form: {
|
||||
labelCol: {
|
||||
//固定label宽度
|
||||
span: null,
|
||||
style: {
|
||||
width: "100px"
|
||||
}
|
||||
},
|
||||
col: {
|
||||
span: 22
|
||||
},
|
||||
wrapper: {
|
||||
width: 600
|
||||
}
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: { text: "赠送套餐" }
|
||||
}
|
||||
},
|
||||
toolbar: { show: false },
|
||||
rowHandle: {
|
||||
width: 200,
|
||||
fixed: "right",
|
||||
buttons: {
|
||||
view: { show: true },
|
||||
copy: { show: false },
|
||||
edit: { show: false },
|
||||
remove: { show: true }
|
||||
// continue:{
|
||||
// text:"续期",
|
||||
// type:"link",
|
||||
// click(){
|
||||
// console.log("续期");
|
||||
// }
|
||||
// }
|
||||
}
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
search: {
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
editable: {
|
||||
disabled: true
|
||||
}
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
title: {
|
||||
title: "套餐名称",
|
||||
type: "text",
|
||||
search: {
|
||||
show: true
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
width: 200
|
||||
}
|
||||
},
|
||||
userId: {
|
||||
title: "用户",
|
||||
type: "table-select",
|
||||
search: {
|
||||
show: true
|
||||
},
|
||||
dict: dict({
|
||||
async getNodesByValues(ids: number[]) {
|
||||
return await api.GetSimpleUserByIds(ids);
|
||||
},
|
||||
value: "id",
|
||||
label: "nickName"
|
||||
}),
|
||||
form: {
|
||||
component: {
|
||||
crossPage: true,
|
||||
multiple: false,
|
||||
select: {
|
||||
placeholder: "点击选择"
|
||||
},
|
||||
createCrudOptions: createCrudOptionsUser
|
||||
// crudOptionsOverride: crudOptionsOverride
|
||||
}
|
||||
}
|
||||
},
|
||||
//赠送
|
||||
presentSuiteId: {
|
||||
title: "赠送套餐",
|
||||
type: "dict-select",
|
||||
column: { show: false },
|
||||
addForm: {
|
||||
show: true,
|
||||
component: {
|
||||
name: SuiteDurationSelector,
|
||||
vModel: "modelValue"
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
validator: async (rule, value) => {
|
||||
if (value && value.productId) {
|
||||
return true;
|
||||
}
|
||||
throw new Error("请选择套餐");
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
valueResolve({ form, value }) {
|
||||
if (value && value.productId) {
|
||||
form.productId = value.productId;
|
||||
form.duration = value.duration;
|
||||
}
|
||||
},
|
||||
form: { show: false }
|
||||
},
|
||||
productType: {
|
||||
title: "类型",
|
||||
type: "dict-select",
|
||||
editForm: {
|
||||
component: {
|
||||
disabled: true
|
||||
}
|
||||
},
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "套餐", value: "suite", color: "green" },
|
||||
{ label: "加量包", value: "addon", color: "blue" }
|
||||
]
|
||||
}),
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
width: 80,
|
||||
align: "center"
|
||||
},
|
||||
valueBuilder: ({ row }) => {
|
||||
if (row.content) {
|
||||
row.content = JSON.parse(row.content);
|
||||
}
|
||||
},
|
||||
valueResolve: ({ form }) => {
|
||||
if (form.content) {
|
||||
form.content = JSON.stringify(form.content);
|
||||
}
|
||||
}
|
||||
},
|
||||
"content.maxDomainCount": {
|
||||
title: "域名数量",
|
||||
type: "text",
|
||||
form: {
|
||||
show: false,
|
||||
key: ["content", "maxDomainCount"],
|
||||
component: {
|
||||
name: SuiteValueEdit,
|
||||
vModel: "modelValue",
|
||||
unit: "个"
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
component: {
|
||||
name: SuiteValue,
|
||||
vModel: "modelValue",
|
||||
unit: "个"
|
||||
},
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
"content.maxPipelineCount": {
|
||||
title: "流水线数量",
|
||||
type: "text",
|
||||
form: {
|
||||
show: false,
|
||||
key: ["content", "maxPipelineCount"],
|
||||
component: {
|
||||
name: SuiteValueEdit,
|
||||
vModel: "modelValue",
|
||||
unit: "条"
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
component: {
|
||||
name: SuiteValue,
|
||||
vModel: "modelValue",
|
||||
unit: "条"
|
||||
},
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
"content.maxDeployCount": {
|
||||
title: "部署次数",
|
||||
type: "text",
|
||||
form: {
|
||||
show: false,
|
||||
key: ["content", "maxDeployCount"],
|
||||
component: {
|
||||
name: SuiteValueEdit,
|
||||
vModel: "modelValue",
|
||||
unit: "次"
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
component: {
|
||||
name: SuiteValue,
|
||||
vModel: "modelValue",
|
||||
unit: "次",
|
||||
used: compute(({ row }) => {
|
||||
return row.deployCountUsed;
|
||||
})
|
||||
},
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
"content.maxMonitorCount": {
|
||||
title: "证书监控数量",
|
||||
type: "text",
|
||||
form: {
|
||||
show: false,
|
||||
key: ["content", "maxMonitorCount"],
|
||||
component: {
|
||||
name: SuiteValueEdit,
|
||||
vModel: "modelValue",
|
||||
unit: "个"
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 120,
|
||||
component: {
|
||||
name: SuiteValue,
|
||||
vModel: "modelValue",
|
||||
unit: "个"
|
||||
},
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
duration: {
|
||||
title: "时长",
|
||||
type: "text",
|
||||
form: { show: false },
|
||||
column: {
|
||||
component: {
|
||||
name: DurationValue,
|
||||
vModel: "modelValue"
|
||||
},
|
||||
width: 100,
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
status: {
|
||||
title: "状态",
|
||||
type: "text",
|
||||
form: { show: false },
|
||||
column: {
|
||||
width: 100,
|
||||
align: "center",
|
||||
component: {
|
||||
name: UserSuiteStatus,
|
||||
userSuite: compute(({ row }) => {
|
||||
return row;
|
||||
})
|
||||
},
|
||||
conditionalRender: {
|
||||
match() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
activeTime: {
|
||||
title: "激活时间",
|
||||
type: "date",
|
||||
column: {
|
||||
width: 150
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
expiresTime: {
|
||||
title: "过期时间",
|
||||
type: "date",
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
width: 150,
|
||||
component: {
|
||||
name: "expires-time-text",
|
||||
vModel: "value",
|
||||
mode: "tag",
|
||||
title: compute(({ value }) => {
|
||||
return dayjs(value).format("YYYY-MM-DD HH:mm:ss");
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
isPresent: {
|
||||
title: "是否赠送",
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "是", value: true, color: "success" },
|
||||
{ label: "否", value: false, color: "blue" }
|
||||
]
|
||||
}),
|
||||
form: {
|
||||
value: true,
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest
|
||||
},
|
||||
form: {
|
||||
labelCol: {
|
||||
//固定label宽度
|
||||
span: null,
|
||||
style: {
|
||||
width: "100px"
|
||||
}
|
||||
},
|
||||
col: {
|
||||
span: 22
|
||||
},
|
||||
wrapper: {
|
||||
width: 600
|
||||
}
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: { text: t('certd.gift_package') }
|
||||
}
|
||||
},
|
||||
|
||||
toolbar: { show: false },
|
||||
rowHandle: {
|
||||
width: 200,
|
||||
fixed: "right",
|
||||
buttons: {
|
||||
view: { show: true },
|
||||
copy: { show: false },
|
||||
edit: { show: false },
|
||||
remove: { show: true }
|
||||
// continue:{
|
||||
// text:"续期",
|
||||
// type:"link",
|
||||
// click(){
|
||||
// console.log("续期");
|
||||
// }
|
||||
// }
|
||||
}
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
search: {
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
editable: {
|
||||
disabled: true
|
||||
}
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
title: {
|
||||
title: t('certd.package_name'),
|
||||
type: "text",
|
||||
search: {
|
||||
show: true
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
width: 200
|
||||
}
|
||||
},
|
||||
userId: {
|
||||
title: t('certd.usera'),
|
||||
type: "table-select",
|
||||
search: {
|
||||
show: true
|
||||
},
|
||||
dict: dict({
|
||||
async getNodesByValues(ids: number[]) {
|
||||
return await api.GetSimpleUserByIds(ids);
|
||||
},
|
||||
value: "id",
|
||||
label: "nickName"
|
||||
}),
|
||||
form: {
|
||||
component: {
|
||||
crossPage: true,
|
||||
multiple: false,
|
||||
select: {
|
||||
placeholder: t('certd.click_to_select')
|
||||
},
|
||||
createCrudOptions: createCrudOptionsUser
|
||||
// crudOptionsOverride: crudOptionsOverride
|
||||
}
|
||||
}
|
||||
},
|
||||
//赠送
|
||||
presentSuiteId: {
|
||||
title: t('certd.gift_package'),
|
||||
type: "dict-select",
|
||||
column: { show: false },
|
||||
addForm: {
|
||||
show: true,
|
||||
component: {
|
||||
name: SuiteDurationSelector,
|
||||
vModel: "modelValue"
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
validator: async (rule, value) => {
|
||||
if (value && value.productId) {
|
||||
return true;
|
||||
}
|
||||
throw new Error(t('certd.please_select_package'));
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
valueResolve({ form, value }) {
|
||||
if (value && value.productId) {
|
||||
form.productId = value.productId;
|
||||
form.duration = value.duration;
|
||||
}
|
||||
},
|
||||
form: { show: false }
|
||||
},
|
||||
productType: {
|
||||
title: t('certd.type'),
|
||||
type: "dict-select",
|
||||
editForm: {
|
||||
component: {
|
||||
disabled: true
|
||||
}
|
||||
},
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: t('certd.package'), value: "suite", color: "green" },
|
||||
{ label: t('certd.addon_package'), value: "addon", color: "blue" }
|
||||
]
|
||||
}),
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
width: 80,
|
||||
align: "center"
|
||||
},
|
||||
valueBuilder: ({ row }) => {
|
||||
if (row.content) {
|
||||
row.content = JSON.parse(row.content);
|
||||
}
|
||||
},
|
||||
valueResolve: ({ form }) => {
|
||||
if (form.content) {
|
||||
form.content = JSON.stringify(form.content);
|
||||
}
|
||||
}
|
||||
},
|
||||
"content.maxDomainCount": {
|
||||
title: t('certd.domain_count'),
|
||||
type: "text",
|
||||
form: {
|
||||
show: false,
|
||||
key: ["content", "maxDomainCount"],
|
||||
component: {
|
||||
name: SuiteValueEdit,
|
||||
vModel: "modelValue",
|
||||
unit: t('certd.unit_count')
|
||||
},
|
||||
rules: [{ required: true, message: t('certd.field_required') }]
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
component: {
|
||||
name: SuiteValue,
|
||||
vModel: "modelValue",
|
||||
unit: t('certd.unit_count')
|
||||
},
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
"content.maxPipelineCount": {
|
||||
title: t('certd.pipeline_count'),
|
||||
type: "text",
|
||||
form: {
|
||||
show: false,
|
||||
key: ["content", "maxPipelineCount"],
|
||||
component: {
|
||||
name: SuiteValueEdit,
|
||||
vModel: "modelValue",
|
||||
unit: t('certd.unit_item')
|
||||
},
|
||||
rules: [{ required: true, message: t('certd.field_required') }]
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
component: {
|
||||
name: SuiteValue,
|
||||
vModel: "modelValue",
|
||||
unit: t('certd.unit_item')
|
||||
},
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
"content.maxDeployCount": {
|
||||
title: t('certd.deploy_count'),
|
||||
type: "text",
|
||||
form: {
|
||||
show: false,
|
||||
key: ["content", "maxDeployCount"],
|
||||
component: {
|
||||
name: SuiteValueEdit,
|
||||
vModel: "modelValue",
|
||||
unit: t('certd.unit_times')
|
||||
},
|
||||
rules: [{ required: true, message: t('certd.field_required') }]
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
component: {
|
||||
name: SuiteValue,
|
||||
vModel: "modelValue",
|
||||
unit: t('certd.unit_times'),
|
||||
used: compute(({ row }) => {
|
||||
return row.deployCountUsed;
|
||||
})
|
||||
},
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
"content.maxMonitorCount": {
|
||||
title: t('certd.monitor_count'),
|
||||
type: "text",
|
||||
form: {
|
||||
show: false,
|
||||
key: ["content", "maxMonitorCount"],
|
||||
component: {
|
||||
name: SuiteValueEdit,
|
||||
vModel: "modelValue",
|
||||
unit: t('certd.unit_count')
|
||||
},
|
||||
rules: [{ required: true, message: t('certd.field_required') }]
|
||||
},
|
||||
column: {
|
||||
width: 120,
|
||||
component: {
|
||||
name: SuiteValue,
|
||||
vModel: "modelValue",
|
||||
unit: t('certd.unit_count')
|
||||
},
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
duration: {
|
||||
title: t('certd.duration'),
|
||||
type: "text",
|
||||
form: { show: false },
|
||||
column: {
|
||||
component: {
|
||||
name: DurationValue,
|
||||
vModel: "modelValue"
|
||||
},
|
||||
width: 100,
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
status: {
|
||||
title: t('certd.status'),
|
||||
type: "text",
|
||||
form: { show: false },
|
||||
column: {
|
||||
width: 100,
|
||||
align: "center",
|
||||
component: {
|
||||
name: UserSuiteStatus,
|
||||
userSuite: compute(({ row }) => {
|
||||
return row;
|
||||
})
|
||||
},
|
||||
conditionalRender: {
|
||||
match() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
activeTime: {
|
||||
title: t('certd.active_time'),
|
||||
type: "date",
|
||||
column: {
|
||||
width: 150
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
expiresTime: {
|
||||
title: t('certd.expires_time'),
|
||||
type: "date",
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
width: 150,
|
||||
component: {
|
||||
name: "expires-time-text",
|
||||
vModel: "value",
|
||||
mode: "tag",
|
||||
title: compute(({ value }) => {
|
||||
return dayjs(value).format("YYYY-MM-DD HH:mm:ss");
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
isPresent: {
|
||||
title: t('certd.is_present'),
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: t('certd.is_present_yes'), value: true, color: "success" },
|
||||
{ label: t('certd.is_present_no'), value: false, color: "blue" }
|
||||
]
|
||||
}),
|
||||
form: {
|
||||
value: true,
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
createTime: {
|
||||
title: t('certd.create_time'),
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 160,
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
updateTime: {
|
||||
title: t('certd.update_time'),
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
show: true,
|
||||
width: 160
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user