feat: 域名验证方法支持CNAME间接方式,此方式支持所有域名注册商,且无需提供Access授权,但是需要手动添加cname解析

This commit is contained in:
xiaojunnuo
2024-10-07 03:21:16 +08:00
parent 0c8e83e125
commit f3d35084ed
123 changed files with 2373 additions and 456 deletions
@@ -1,11 +1,11 @@
<template>
<div class="pi-access-selector">
<div class="access-selector">
<span v-if="target.name" class="mr-5 cd-flex-inline">
<span class="mr-5">{{ target.name }}</span>
<fs-icon class="cd-icon-button" icon="ion:close-circle-outline" @click="clear"></fs-icon>
</span>
<span v-else class="mlr-5 gray">请选择</span>
<a-button class="ml-5" @click="chooseForm.open">选择</a-button>
<span v-else class="mlr-5 text-gray">{{ placeholder }}</span>
<a-button class="ml-5" :size="size" @click="chooseForm.open">选择</a-button>
<a-form-item-rest v-if="chooseForm.show">
<a-modal v-model:open="chooseForm.show" title="选择授权提供者" width="900px" @ok="chooseForm.ok">
<div style="height: 400px; position: relative">
@@ -23,7 +23,7 @@ import CertAccessModal from "./access/index.vue";
import { GetProviderDefineByAccessType } from "../api";
export default defineComponent({
name: "PiAccessSelector",
name: "AccessSelector",
components: { CertAccessModal },
props: {
modelValue: {
@@ -33,6 +33,14 @@ export default defineComponent({
type: {
type: String,
default: "aliyun"
},
placeholder: {
type: String,
default: "请选择"
},
size: {
type: String,
default: "middle"
}
},
emits: ["update:modelValue"],
@@ -16,7 +16,7 @@ import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
export default defineComponent({
name: "CertdAccess",
name: "AccessManager",
setup() {
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} });
@@ -0,0 +1,59 @@
import { request } from "/src/api/service";
const apiPrefix = "/cname/record";
export async function GetList(query: any) {
return await request({
url: apiPrefix + "/page",
method: "post",
data: query
});
}
export async function AddObj(obj: any) {
return await request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
}
export async function UpdateObj(obj: any) {
return await request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
}
export async function DelObj(id: any) {
return await request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
}
export async function GetObj(id: any) {
return await request({
url: apiPrefix + "/info",
method: "post",
params: { id }
});
}
export async function GetDetail(id: any) {
return await request({
url: apiPrefix + "/detail",
method: "post",
params: { id }
});
}
export async function DeleteBatch(ids: any[]) {
return await request({
url: apiPrefix + "/deleteByIds",
method: "post",
data: { ids }
});
}
@@ -0,0 +1,202 @@
import * as api from "./api";
import { useI18n } from "vue-i18n";
import { computed, Ref, ref } from "vue";
import { useRouter } from "vue-router";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes, utils } from "@fast-crud/fast-crud";
import { useUserStore } from "/@/store/modules/user";
import { useSettingStore } from "/@/store/modules/settings";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter();
const { t } = useI18n();
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }: EditReq) => {
form.id = row.id;
const res = await api.UpdateObj(form);
return res;
};
const delRequest = async ({ row }: DelReq) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }: AddReq) => {
form.content = JSON.stringify({
title: form.title
});
const res = await api.AddObj(form);
return res;
};
const userStore = useUserStore();
const settingStore = useSettingStore();
const selectedRowKeys: Ref<any[]> = ref([]);
context.selectedRowKeys = selectedRowKeys;
return {
crudOptions: {
settings: {
plugins: {
//这里使用行选择插件,生成行选择crudOptions配置,最终会与crudOptions合并
rowSelection: {
enabled: true,
order: -2,
before: true,
// handle: (pluginProps,useCrudProps)=>CrudOptions,
props: {
multiple: true,
crossPage: true,
selectedRowKeys
}
}
}
},
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
rowHandle: {
minWidth: 200,
fixed: "right"
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 100
},
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 }) => {
if (!form.cnameProviderId) {
const item = dict.data.find((item) => item.isDefault);
if (item) {
form.cnameProviderId = item.id;
}
}
}
}
},
column: {
show: false
}
},
status: {
title: "状态",
type: "dict-select",
dict: dict({
data: [
{ label: "待设置CNAME", value: "cname", color: "warning" },
{ label: "验证中", value: "validating", color: "primary" },
{ label: "验证成功", value: "valid", color: "success" },
{ label: "验证失败", value: "failed", color: "error" }
]
}),
addForm: {
show: false
},
column: {
width: 120,
align: "center"
}
},
triggerValidate: {
title: "验证",
type: "text",
form: {
show: false
},
column: {
conditionalRenderDisabled: true,
width: 100,
align: "center",
cellRender({ row, value }) {
if (row.status === "valid") {
return "-";
}
return (
<a-button size={"small"} type={"primary"}>
</a-button>
);
}
}
},
createTime: {
title: "创建时间",
type: "datetime",
form: {
show: false
},
column: {
sorter: true,
width: 160,
align: "center"
}
},
updateTime: {
title: "更新时间",
type: "datetime",
form: {
show: false
},
column: {
show: true
}
}
}
}
};
}
@@ -0,0 +1,51 @@
<template>
<fs-page class="page-cert">
<template #header>
<div class="title">CNAME记录管理</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding">
<template #pagination-left>
<a-tooltip title="批量删除">
<fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>
</a-tooltip>
</template>
</fs-crud>
</fs-page>
</template>
<script lang="ts" setup>
import { onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { message, Modal } from "ant-design-vue";
import { DeleteBatch } from "/@/views/certd/history/api";
defineOptions({
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("请先勾选记录");
}
};
// 页面打开后获取列表数据
onMounted(() => {
crudExpose.doRefresh();
});
</script>
<style lang="less"></style>
@@ -23,6 +23,10 @@ import { Ref, ref } from "vue";
import { CrudOptions, useColumns, useFormWrapper } from "@fast-crud/fast-crud";
import { notification } from "ant-design-vue";
defineOptions({
name: "UserProfile"
});
const userInfo: Ref = ref({});
const getUserInfo = async () => {
@@ -3,10 +3,12 @@ import { PluginGroup } from "@certd/pipeline";
import { useReference } from "/@/use/use-refrence";
import _ from "lodash-es";
import { useUserStore } from "/@/store/modules/user";
import { useSettingStore } from "/@/store/modules/settings";
export default function (certPluginGroup: PluginGroup, formWrapperRef: any): CreateCrudOptionsRet {
const inputs: any = {};
const userStore = useUserStore();
const settingStore = useSettingStore();
for (const plugin of certPluginGroup.plugins) {
for (const inputKey in plugin.input) {
if (inputs[inputKey]) {
@@ -66,8 +68,8 @@ export default function (certPluginGroup: PluginGroup, formWrapperRef: any): Cre
render: () => {
return (
<ul>
<li>JS-ACMEDNS属于阿里云Cloudflare西</li>
<li>Lego-ACMELego实现DNS提供商LEGO的用户可以使用</li>
<li>JS-ACME使便</li>
<li>Lego-ACMELego实现DNS提供商LEGO的用户可以使用</li>
</ul>
);
}
@@ -313,6 +313,9 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
name: "a-input"
}
},
form: {
rules: [{ required: true, message: "此项必填" }]
},
column: {
width: 350,
sorter: true,
@@ -1,73 +0,0 @@
<template>
<a-select class="pi-output-selector" :value="modelValue" :options="options" @update:value="onChanged"> </a-select>
</template>
<script lang="ts">
import { inject, onMounted, Ref, ref, watch } from "vue";
import { pluginManager } from "../../plugin";
export default {
name: "PiOutputSelector",
props: {
modelValue: {
type: String,
default: undefined
},
from: {
type: [String, Array]
}
},
emits: ["update:modelValue"],
setup(props: any, ctx: any) {
const options = ref<any[]>([]);
const pipeline = inject("pipeline") as Ref<any>;
const currentStageIndex = inject("currentStageIndex") as Ref<number>;
const currentStepIndex = inject("currentStepIndex") as Ref<number>;
const currentTask = inject("currentTask") as Ref<any>;
const getPluginGroups = inject("getPluginGroups") as any;
const pluginGroups = getPluginGroups();
function onCreate() {
options.value = pluginGroups.getPreStepOutputOptions({
pipeline: pipeline.value,
currentStageIndex: currentStageIndex.value,
currentStepIndex: currentStepIndex.value,
currentTask: currentTask.value
});
if (props.from) {
if (typeof props.from === "string") {
options.value = options.value.filter((item: any) => item.type === props.from);
} else {
options.value = options.value.filter((item: any) => props.from.includes(item.type));
}
}
if (props.modelValue == null && options.value.length > 0) {
ctx.emit("update:modelValue", options.value[0].value);
}
}
onMounted(() => {
onCreate();
});
watch(
() => {
return pluginGroups.value?.map;
},
() => {
onCreate();
}
);
function onChanged(value: any) {
ctx.emit("update:modelValue", value);
}
return {
options,
onChanged
};
}
};
</script>
<style lang="less"></style>
@@ -73,27 +73,28 @@
<a-alert type="info" :message="currentPlugin.title" :description="currentPlugin.desc"> </a-alert>
</div>
</template>
<a-form ref="stepFormRef" class="step-form" :model="currentStep" :label-col="labelCol" :wrapper-col="wrapperCol">
<fs-form-item
v-model="currentStep.title"
:item="{
title: '任务名称',
key: 'title',
component: {
name: 'a-input',
vModel: 'value'
},
rules: [{ required: true, message: '此项必填' }]
}"
:get-context-fn="blankFn"
/>
<template v-for="(item, key) in currentPlugin.input" :key="key">
<fs-form-item v-if="item.show !== false" v-model="currentStep.input[key]" :item="item" :get-context-fn="blankFn" />
</template>
<fs-form-item v-model="currentStep.strategy.runStrategy" :item="runStrategyProps" :get-context-fn="blankFn" />
</a-form>
<div class="w-100 h-100">
<a-form ref="stepFormRef" class="step-form" :model="currentStep" :label-col="labelCol" :wrapper-col="wrapperCol">
<fs-form-item
v-model="currentStep.title"
:item="{
title: '任务名称',
key: 'title',
component: {
name: 'a-input',
vModel: 'value'
},
rules: [{ required: true, message: '此项必填' }]
}"
:get-context-fn="blankFn"
/>
<template v-for="(item, key) in currentPlugin.input" :key="key">
<fs-form-item v-if="item.show !== false" v-model="currentStep.input[key]" :item="item" :get-context-fn="blankFn" />
</template>
<fs-form-item v-model="currentStep.strategy.runStrategy" :item="runStrategyProps" :get-context-fn="blankFn" />
</a-form>
</div>
<template #footer>
<div v-if="editMode" class="bottom-button">
<a-button type="primary" @click="stepSave"> 确定 </a-button>
@@ -394,17 +395,24 @@ export default {
&.fullscreen {
.pi-step-form {
.body {
margin: auto;
.step-plugin {
width: 16.666666%;
}
.step-form {
display: flex;
flex-wrap: wrap;
width: 1300px;
.fs-form-item {
width: 50%;
width: 100%;
}
}
}
.footer {
.bottom-button {
text-align: center;
}
}
}
}
@@ -3,7 +3,7 @@
<template #header>
<div class="title">
<fs-button class="back" icon="ion:chevron-back-outline" @click="goBack"></fs-button>
<pi-editable v-model="pipeline.title" :hover-show="false" :disabled="!editMode"></pi-editable>
<text-editable v-model="pipeline.title" :hover-show="false" :disabled="!editMode"></text-editable>
</div>
<div class="more">
<template v-if="editMode">
@@ -24,7 +24,7 @@
<template #header>
<div class="stage first-stage">
<div class="title stage-move-handle">
<pi-editable model-value="触发源" :disabled="true" />
<text-editable model-value="触发源" :disabled="true" />
</div>
<div class="tasks">
<div class="task-container first-task">
@@ -68,7 +68,7 @@
<template #item="{ element: stage, index }">
<div :key="stage.id" class="stage" :class="{ 'last-stage': isLastStage(index) }">
<div class="title">
<pi-editable v-model="stage.title" :disabled="!editMode"></pi-editable>
<text-editable v-model="stage.title" :disabled="!editMode"></text-editable>
<div v-plus class="icon-box stage-move-handle">
<fs-icon v-if="editMode" title="拖动排序" icon="ion:move-outline"></fs-icon>
</div>
@@ -146,7 +146,7 @@
<template #footer>
<div v-if="editMode" class="stage last-stage">
<div class="title">
<pi-editable model-value="新阶段" :disabled="true" />
<text-editable model-value="新阶段" :disabled="true" />
</div>
<div class="tasks">
<div class="task-container first-task">
@@ -188,7 +188,7 @@
</div>
<div v-else class="stage last-stage">
<div class="title">
<pi-editable model-value="结束" :disabled="true" />
<text-editable model-value="结束" :disabled="true" />
</div>
<div v-if="pipeline.notifications?.length > 0" class="tasks">
<div v-for="(item, index) of pipeline.notifications" :key="index" class="task-container" :class="{ 'first-task': index == 0 }">
@@ -704,7 +704,7 @@ export default defineComponent({
.back {
margin-right: 10px;
}
.pi-editable {
.text-editable {
width: 300px;
}
}
@@ -79,12 +79,15 @@
<script setup lang="ts">
import { reactive } from "vue";
import * as api from "./api";
import * as emailApi from "./api.email";
import { SettingKeys } from "./api";
import * as emailApi from "./api.email";
import { notification } from "ant-design-vue";
import { useSettingStore } from "/@/store/modules/settings";
defineOptions({
name: "EmailSetting"
});
interface FormState {
host: string;
port: number;
@@ -18,6 +18,10 @@ import { useUserStore } from "/@/store/modules/user";
import { useSettingStore } from "/@/store/modules/settings";
import * as api from "./api";
import { notification } from "ant-design-vue";
defineOptions({
name: "AccountBind"
});
const iframeRef = ref();
const userStore = useUserStore();
@@ -29,7 +29,7 @@ import { usePermission } from "/src/plugin/permission";
import { useFs, useUi } from "@fast-crud/fast-crud";
export default defineComponent({
name: "AuthorityPermission",
name: "AuthorityManager",
components: { FsPermissionTree },
setup() {
// 此处传入permission进行通用按钮权限设置,会通过commonOptions去设置actionbar和rowHandle的按钮的show属性
@@ -5,7 +5,15 @@
</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:checkedKeys="checkedKeys" :tree="permissionTreeData" :editable="false" checkable :replace-fields="{ key: 'id', label: 'title' }"> </fs-permission-tree>
<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>
@@ -84,7 +92,7 @@ function useAuthz() {
}
export default defineComponent({
name: "AuthorityRole",
name: "RoleManager",
components: { FsPermissionTree },
setup() {
//授权配置
@@ -12,7 +12,7 @@ import { defineComponent, ref, onMounted } from "vue";
import { useCrud, useExpose, useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
export default defineComponent({
name: "AuthorityUser",
name: "UserManager",
setup() {
// 初始化crud配置
// 此处传入权限前缀进行通用按钮权限设置,会通过commonOptions去设置actionbar和rowHandle的按钮的show属性
@@ -0,0 +1,75 @@
import { request } from "/src/api/service";
const apiPrefix = "/sys/cname/provider";
export async function GetList(query: any) {
return await request({
url: apiPrefix + "/page",
method: "post",
data: query
});
}
export async function AddObj(obj: any) {
return await request({
url: apiPrefix + "/add",
method: "post",
data: obj
});
}
export async function UpdateObj(obj: any) {
return await request({
url: apiPrefix + "/update",
method: "post",
data: obj
});
}
export async function DelObj(id: any) {
return await request({
url: apiPrefix + "/delete",
method: "post",
params: { id }
});
}
export async function GetObj(id: any) {
return await request({
url: apiPrefix + "/info",
method: "post",
params: { id }
});
}
export async function GetDetail(id: any) {
return await request({
url: apiPrefix + "/detail",
method: "post",
params: { id }
});
}
export async function DeleteBatch(ids: any[]) {
return await request({
url: apiPrefix + "/deleteByIds",
method: "post",
data: { ids }
});
}
export async function SetDefault(id: any) {
return await request({
url: apiPrefix + "/setDefault",
method: "post",
data: { id }
});
}
export async function SetDisabled(id: any, disabled: boolean) {
return await request({
url: apiPrefix + "/setDisabled",
method: "post",
data: { id, disabled }
});
}
@@ -0,0 +1,245 @@
import * as api from "./api";
import { useI18n } from "vue-i18n";
import { computed, Ref, ref } from "vue";
import { useRouter } from "vue-router";
import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes, utils } from "@fast-crud/fast-crud";
import { useUserStore } from "/@/store/modules/user";
import { useSettingStore } from "/@/store/modules/settings";
import { Modal } from "ant-design-vue";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter();
const { t } = useI18n();
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }: EditReq) => {
form.id = row.id;
const res = await api.UpdateObj(form);
return res;
};
const delRequest = async ({ row }: DelReq) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }: AddReq) => {
form.content = JSON.stringify({
title: form.title
});
const res = await api.AddObj(form);
return res;
};
const userStore = useUserStore();
const settingStore = useSettingStore();
const selectedRowKeys: Ref<any[]> = ref([]);
context.selectedRowKeys = selectedRowKeys;
return {
crudOptions: {
settings: {
plugins: {
//这里使用行选择插件,生成行选择crudOptions配置,最终会与crudOptions合并
rowSelection: {
enabled: true,
order: -2,
before: true,
// handle: (pluginProps,useCrudProps)=>CrudOptions,
props: {
multiple: true,
crossPage: true,
selectedRowKeys
}
}
}
},
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
rowHandle: {
minWidth: 200,
fixed: "right"
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 100
},
form: {
show: false
}
},
domain: {
title: "域名",
type: "text",
editForm: {
component: {
disabled: true
}
},
form: {
helper: "CNAME域名一旦确定不可修改",
rules: [{ required: true, message: "此项必填" }]
},
column: {
width: 200
}
},
dnsProviderType: {
title: "DNS提供商",
type: "dict-select",
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 (
<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
}
}
}
}
};
}
@@ -0,0 +1,56 @@
<template>
<fs-page class="page-cert">
<template #header>
<div class="title">
CNAME服务配置
<span class="sub">
此处配置的域名作为其他域名校验的代理当别的域名需要申请证书时通过CNAME映射到此域名上来验证所有权好处是任何域名都可以通过此方式申请证书也无需填写AccessSecret
</span>
</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding">
<template #pagination-left>
<a-tooltip title="批量删除">
<fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>
</a-tooltip>
</template>
</fs-crud>
</fs-page>
</template>
<script lang="ts" setup>
import { onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { message, Modal } from "ant-design-vue";
import { DeleteBatch } from "/@/views/certd/history/api";
defineOptions({
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("请先勾选记录");
}
};
// 页面打开后获取列表数据
onMounted(() => {
crudExpose.doRefresh();
});
</script>
<style lang="less"></style>
@@ -47,6 +47,10 @@ import { PublicSettingsSave, SettingKeys } from "./api";
import { notification } from "ant-design-vue";
import { useSettingStore } from "/@/store/modules/settings";
defineOptions({
name: "SysSettings"
});
interface FormState {
registerEnabled: boolean;
managerOtherUserPipeline: boolean;
@@ -68,6 +68,10 @@ import { notification } from "ant-design-vue";
import { useSettingStore } from "/src/store/modules/settings";
import { useUserStore } from "/@/store/modules/user";
defineOptions({
name: "SiteSetting"
});
interface FormState {
title: string;
slogan: string;