feat: 通知配置详情表单

This commit is contained in:
JiangQiming 2023-01-12 16:48:39 +08:00
parent 3135213a05
commit c05cf5d961
10 changed files with 833 additions and 3 deletions

32
components.d.ts vendored
View File

@ -7,12 +7,44 @@ export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
AAlert: typeof import('ant-design-vue/es')['Alert']
ABadge: typeof import('ant-design-vue/es')['Badge']
AButton: typeof import('ant-design-vue/es')['Button']
ACard: typeof import('ant-design-vue/es')['Card']
ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
ACheckboxGroup: typeof import('ant-design-vue/es')['CheckboxGroup']
ACol: typeof import('ant-design-vue/es')['Col']
ADatePicker: typeof import('ant-design-vue/es')['DatePicker']
AEmpty: typeof import('ant-design-vue/es')['Empty']
AForm: typeof import('ant-design-vue/es')['Form']
AFormItem: typeof import('ant-design-vue/es')['FormItem']
AInput: typeof import('ant-design-vue/es')['Input']
AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
AModal: typeof import('ant-design-vue/es')['Modal']
APagination: typeof import('ant-design-vue/es')['Pagination']
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
ARadioButton: typeof import('ant-design-vue/es')['RadioButton']
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
ARow: typeof import('ant-design-vue/es')['Row']
ASelect: typeof import('ant-design-vue/es')['Select']
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
ASpace: typeof import('ant-design-vue/es')['Space']
ASpin: typeof import('ant-design-vue/es')['Spin']
ASwitch: typeof import('ant-design-vue/es')['Switch']
ATable: typeof import('ant-design-vue/es')['Table']
ATextarea: typeof import('ant-design-vue/es')['Textarea']
ATimePicker: typeof import('ant-design-vue/es')['TimePicker']
ATooltip: typeof import('ant-design-vue/es')['Tooltip']
ATreeSelect: typeof import('ant-design-vue/es')['TreeSelect']
AUpload: typeof import('ant-design-vue/es')['Upload']
BadgeStatus: typeof import('./src/components/BadgeStatus/index.vue')['default']
CardBox: typeof import('./src/components/CardBox/index.vue')['default']
FormFormBuilder: typeof import('./src/components/Form/FormBuilder.vue')['default']
GeoComponent: typeof import('./src/components/GeoComponent/index.vue')['default']
MonacoEditor: typeof import('./src/components/MonacoEditor/index.vue')['default']
PermissionButton: typeof import('./src/components/PermissionButton/index.vue')['default']
RadioCard: typeof import('./src/components/RadioCard/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SearchItem: typeof import('./src/components/Search/Item.vue')['default']

View File

@ -0,0 +1,71 @@
<!-- 单选卡片 -->
<template>
<div class="m-radio">
<div
class="m-radio-item"
:class="{ active: myValue === item.value }"
v-for="(item, index) in options"
:key="index"
@click="myValue = item.value"
>
<img class="img" :src="item.logo" alt="" />
<span>{{ item.label }}</span>
</div>
</div>
</template>
<script setup lang="ts">
import { PropType } from 'vue';
interface IOption {
label: string;
value: string;
logo: string;
}
type Emits = {
(e: 'update:modelValue', data: string): void;
};
const emit = defineEmits<Emits>();
const props = defineProps({
options: {
type: Array as PropType<IOption[]>,
default: () => [],
},
modelValue: {
type: String,
default: '',
},
});
const myValue = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val),
});
</script>
<style lang="less" scoped>
.m-radio {
display: flex;
&-item {
padding: 10px 15px;
margin-right: 15px;
border: 1px solid #d9d9d9;
border-radius: 2px;
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
cursor: pointer;
.img {
width: 100px;
height: 100px;
}
&.active {
color: #1d39c4;
border-color: #1d39c4;
}
}
}
</style>

View File

@ -48,6 +48,22 @@ export default [
path: '/search',
component: () => import('@/views/demo/Search.vue')
},
{
path: '/notice/Config',
component: () => import('@/views/notice/Config/index.vue')
},
{
path: '/notice/Config/detail/:id',
component: () => import('@/views/notice/Config/Detail/index.vue')
},
{
path: '/notice/Template',
component: () => import('@/views/notice/Template/index.vue')
},
{
path: '/notice/Template/detail/:id',
component: () => import('@/views/notice/Template/Detail/index.vue')
},
// end: 测试用, 可删除
// link 运维管理
@ -73,7 +89,7 @@ export default [
},
// 初始化
{
path: '/init-home',
component: () => import('@/views/init-home/index.vue')
},
path: '/init-home',
component: () => import('@/views/init-home/index.vue')
},
]

View File

@ -0,0 +1,405 @@
<!-- 通知配置详情 -->
<template>
<div class="page-container">
<a-card>
<a-row>
<a-col :span="10">
<a-form layout="vertical">
<a-form-item
label="通知方式"
v-bind="validateInfos.type"
>
<a-select
v-model:value="formData.type"
placeholder="请选择通知方式"
>
<a-select-option
v-for="(item, index) in NOTICE_METHOD"
:key="index"
:value="item.value"
>
{{ item.label }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="名称" v-bind="validateInfos.name">
<a-input
v-model:value="formData.name"
placeholder="请输入名称"
/>
</a-form-item>
<a-form-item
label="类型"
v-bind="validateInfos.provider"
v-if="formData.type !== 'email'"
>
<RadioCard
:options="msgType"
v-model="formData.provider"
/>
</a-form-item>
<!-- 钉钉 -->
<template v-if="formData.type === 'dingTalk'">
<template
v-if="formData.provider === 'dingTalkMessage'"
>
<a-form-item
label="AppKey"
v-bind="
validateInfos['configuration.appKey']
"
>
<a-input
v-model:value="
formData.configuration.appKey
"
placeholder="请输入AppKey"
/>
</a-form-item>
<a-form-item
label="AppSecret"
v-bind="
validateInfos['configuration.appSecret']
"
>
<a-input
v-model:value="
formData.configuration.appSecret
"
placeholder="请输入AppSecret"
/>
</a-form-item>
</template>
<template
v-if="
formData.provider === 'dingTalkRobotWebHook'
"
>
<a-form-item
label="webHook"
v-bind="validateInfos['configuration.url']"
>
<a-input
v-model:value="
formData.configuration.url
"
placeholder="请输入webHook"
/>
</a-form-item>
</template>
</template>
<!-- 微信 -->
<template v-if="formData.type === 'weixin'">
<a-form-item
label="corpId"
v-bind="validateInfos['configuration.corpId']"
>
<a-input
v-model:value="
formData.configuration.corpId
"
placeholder="请输入corpId"
/>
</a-form-item>
<a-form-item
label="corpSecret"
v-bind="
validateInfos['configuration.corpSecret']
"
>
<a-input
v-model:value="
formData.configuration.corpSecret
"
placeholder="请输入corpSecret"
/>
</a-form-item>
</template>
<!-- 邮件 -->
<template v-if="formData.type === 'email'">
<a-form-item
label="服务器地址"
v-bind="validateInfos['configuration.host']"
>
<a-space>
<a-input
v-model:value="
formData.configuration.host
"
placeholder="请输入服务器地址"
/>
<a-input-number
v-model:value="
formData.configuration.port
"
/>
<a-checkbox
v-model:value="
formData.configuration.ssl
"
>
开启SSL
</a-checkbox>
</a-space>
</a-form-item>
<a-form-item
label="发件人"
v-bind="validateInfos['configuration.sender']"
>
<a-input
v-model:value="
formData.configuration.sender
"
placeholder="请输入发件人"
/>
</a-form-item>
<a-form-item
label="用户名"
v-bind="validateInfos['configuration.username']"
>
<a-input
v-model:value="
formData.configuration.username
"
placeholder="请输入用户名"
/>
</a-form-item>
<a-form-item
label="密码"
v-bind="validateInfos['configuration.password']"
>
<a-input
v-model:value="
formData.configuration.password
"
placeholder="请输入密码"
/>
</a-form-item>
</template>
<!-- 语音/短信 -->
<template
v-if="
formData.type === 'voice' ||
formData.type === 'sms'
"
>
<a-form-item
label="RegionId"
v-bind="validateInfos['configuration.regionId']"
>
<a-select
v-model:value="
formData.configuration.regionId
"
placeholder="请选择RegionId"
>
<a-select-option
v-for="(item, index) in regionList"
:key="index"
:value="item.value"
>
{{ item.label }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item
label="AccessKeyId"
v-bind="
validateInfos['configuration.accessKeyId']
"
>
<a-input
v-model:value="
formData.configuration.accessKeyId
"
placeholder="请输入AccessKeyId"
/>
</a-form-item>
<a-form-item
label="Secret"
v-bind="validateInfos['configuration.secret']"
>
<a-input
v-model:value="
formData.configuration.secret
"
placeholder="Secret"
/>
</a-form-item>
</template>
<!-- webhook -->
<template v-if="formData.type === 'webhook'">
<a-form-item
label="Webhook"
v-bind="validateInfos['configuration.url']"
>
<a-input
v-model:value="formData.configuration.url"
placeholder="请输入Webhook"
/>
</a-form-item>
</template>
<a-form-item label="说明">
<a-textarea
v-model:value="formData.description"
show-count
:maxlength="200"
:rows="5"
placeholder="请输入说明"
/>
</a-form-item>
<a-form-item :wrapper-col="{ offset: 0, span: 3 }">
<a-button
type="primary"
@click="handleSubmit"
style="width: 100%"
>
保存
</a-button>
</a-form-item>
</a-form>
</a-col>
<a-col :span="12" :push="2"></a-col>
</a-row>
</a-card>
</div>
</template>
<script setup lang="ts">
import { getImage, LocalStore } from '@/utils/comm';
import { Form } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { ConfigFormData } from '../types';
import {
NOTICE_METHOD,
CONFIG_FIELD_MAP,
MSG_TYPE,
} from '@/views/notice/const';
import regionList from './regionId';
const useForm = Form.useForm;
//
const msgType = ref([
{
label: '钉钉消息',
value: 'dingTalkMessage',
logo: getImage('/notice/dingtalk.png'),
},
{
label: '群机器人消息',
value: 'dingTalkRobotWebHook',
logo: getImage('/notice/dingTalk-rebot.png'),
},
]);
//
const formData = ref<ConfigFormData>({
configuration: {
appKey: '',
appSecret: '',
url: '',
},
description: '',
name: '',
provider: 'dingTalkMessage',
type: NOTICE_METHOD[0].value,
});
//
watch(
() => formData.value.type,
(val) => {
formData.value.configuration = CONFIG_FIELD_MAP[val];
msgType.value = MSG_TYPE[val];
formData.value.provider = msgType.value[0].value;
},
);
//
const formRules = ref({
type: [{ required: true, message: '请选择通知方式' }],
name: [
{ required: true, message: '请输入名称' },
{ max: 64, message: '最多可输入64个字符' },
],
provider: [{ required: true, message: '请选择类型' }],
//
'configuration.appKey': [
{ required: true, message: '请输入AppKey' },
{ max: 64, message: '最多可输入64个字符' },
],
'configuration.appSecret': [
{ required: true, message: '请输入AppSecret' },
{ max: 64, message: '最多可输入64个字符' },
],
// 'configuration.url': [{ required: true, message: 'WebHook' }],
//
'configuration.corpId': [
{ required: true, message: '请输入corpId' },
{ max: 64, message: '最多可输入64个字符' },
],
'configuration.corpSecret': [
{ required: true, message: '请输入corpSecret' },
{ max: 64, message: '最多可输入64个字符' },
],
// /
'configuration.regionId': [
{ required: true, message: '请输入RegionId' },
{ max: 64, message: '最多可输入64个字符' },
],
'configuration.accessKeyId': [
{ required: true, message: '请输入AccessKeyId' },
{ max: 64, message: '最多可输入64个字符' },
],
'configuration.secret': [
{ required: true, message: '请输入Secret' },
{ max: 64, message: '最多可输入64个字符' },
],
//
'configuration.host': [{ required: true, message: '请输入服务器地址' }],
'configuration.sender': [{ required: true, message: '请输入发件人' }],
'configuration.username': [
{ required: true, message: '请输入用户名' },
{ max: 64, message: '最多可输入64个字符' },
],
'configuration.password': [
{ required: true, message: '请输入密码' },
{ max: 64, message: '最多可输入64个字符' },
],
// webhook
'configuration.url': [
{ required: true, message: '请输入Webhook' },
{
pattern:
/^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/,
message: 'Webhook需要是一个合法的URL',
trigger: 'blur',
},
],
description: [{ max: 200, message: '最多可输入200个字符' }],
});
const { resetFields, validate, validateInfos } = useForm(
formData.value,
formRules.value,
);
console.log('validateInfos: ', validateInfos);
/**
* 表单提交
*/
const handleSubmit = () => {
validate()
.then(async () => {})
.catch((err) => {});
};
</script>
<style lang="less" scoped>
.page-container {
background: #f0f2f5;
padding: 24px;
}
</style>

View File

@ -0,0 +1,119 @@
// 数据来源 https://help.aliyun.com/document_detail/188196.html
export default [
/** 公共云 */
//中国地区(包含中国香港、中国澳门,不包含中国台湾)
{
value: 'cn-qingdao',
label: '华北1青岛',
},
{
value: 'cn-beijing',
label: '华北2北京',
},
{
value: 'cn-zhangjiakou',
label: '华北3张家口',
},
{
value: 'cn-huhehaote',
label: '华北5呼和浩特',
},
{
value: 'cn-wulanchabu',
label: '华北6乌兰察布',
},
{
value: 'cn-hangzhou',
label: '华东1杭州',
},
{
value: 'cn-shanghai',
label: '华东2上海',
},
{
value: 'cn-nanjing',
label: '华东5 (南京-本地地域)',
},
{
value: 'cn-fuzhou',
label: '华东6福州-本地地域)',
},
{
value: 'cn-shenzhen',
label: '华南1深圳',
},
{
value: 'cn-heyuan',
label: '华南2河源',
},
{
value: 'cn-guangzhou',
label: '华南3广州',
},
{
value: 'cn-chengdu',
label: '西南1成都',
},
{
value: 'cn-hongkong',
label: '中国香港',
},
//其他国家和地区
{
value: 'ap-southeast-1',
label: '新加坡',
},
{
value: 'ap-southeast-2',
label: '澳大利亚(悉尼)',
},
{
value: 'ap-southeast-3',
label: '马来西亚(吉隆坡)',
},
{
value: 'ap-southeast-5',
label: '印度尼西亚(雅加达)',
},
{
value: 'ap-southeast-6',
label: '菲律宾(马尼拉)',
},
{
value: 'ap-southeast-7',
label: '泰国(曼谷)',
},
{
value: 'ap-south-1',
label: '印度(孟买)',
},
{
value: 'ap-northeast-1',
label: '日本(东京)',
},
{
value: 'ap-northeast-2',
label: '韩国(首尔)',
},
{
value: 'us-west-1',
label: '美国(硅谷)',
},
{
value: 'us-east-1',
label: '美国(弗吉尼亚)',
},
{
value: 'eu-central-1',
label: '德国(法兰克福)',
},
{
value: 'eu-west-1',
label: '英国(伦敦)',
},
{
value: 'me-east-1',
label: '阿联酋(迪拜)',
},
];

View File

@ -0,0 +1,8 @@
<!-- 通知配置 -->
<template>
<div class="page-container">通知配置</div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped></style>

37
src/views/notice/Config/types.d.ts vendored Normal file
View File

@ -0,0 +1,37 @@
interface IHeaders {
key: string;
value: string;
}
export type ConfigFormData = {
configuration: {
// 钉钉
appKey?: string;
appSecret?: string;
url?: string;
// 微信
corpId?: string;
corpSecret?: string;
// 邮件
host?: string;
port?: number;
ssl?: boolean;
sender?: string;
username?: string;
password?: string;
// 语音
regionId?: string;
accessKeyId?: string;
secret?: string;
// 短信
regionId?: string;
accessKeyId?: string;
secret?: string;
// webhook
// url?: string;
headers?: IHeaders[];
};
description: string;
name: string;
provider: string;
type: string;
};

View File

@ -0,0 +1,8 @@
<!-- 通知模板详情 -->
<template>
<div class="page-container">通知模板详情</div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,8 @@
<!-- 通知模板 -->
<template>
<div class="page-container">通知模板</div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped></style>

126
src/views/notice/const.ts Normal file
View File

@ -0,0 +1,126 @@
import { getImage } from '@/utils/comm';
interface INoticeMethod {
label: string;
value: string;
}
// 通知方式
export const NOTICE_METHOD: INoticeMethod[] = [
{
label: '钉钉',
value: 'dingTalk',
},
{
label: '微信',
value: 'weixin',
},
{
label: '邮件',
value: 'email',
},
{
label: '语音',
value: 'voice',
},
{
label: '短信',
value: 'sms',
},
{
label: 'webhook',
value: 'webhook',
},
];
// 消息类型
export const MSG_TYPE = {
dingTalk: [
{
label: '钉钉消息',
value: 'dingTalkMessage',
logo: getImage('/notice/dingtalk.png'),
},
{
label: '群机器人消息',
value: 'dingTalkRobotWebHook',
logo: getImage('/notice/dingTalk-rebot.png'),
},
],
weixin: [
{
label: '企业消息',
value: 'corpMessage',
logo: getImage('/notice/weixin-corp.png'),
},
// {
// label: '服务号消息',
// value: 'officialMessage'
// logo: getImage('/notice/weixin-official.png'),
// }
],
voice: [
{
label: '阿里云语音',
value: 'aliyun',
logo: getImage('/notice/voice.png'),
},
],
sms: [
{
label: '阿里云短信',
value: 'aliyunSms',
logo: getImage('/notice/sms.png'),
},
],
webhook: [
{
label: 'webhook',
value: 'http',
logo: getImage('/notice/webhook.png'),
},
],
email: [
{
label: 'email',
value: 'embedded',
logo: getImage('/notice/email.png'),
},
],
}
// 字段关系映射
// 配置
export const CONFIG_FIELD_MAP = {
dingTalk: {
appKey: undefined,
appSecret: undefined,
url: undefined,
},
weixin: {
corpId: undefined,
corpSecret: undefined,
},
email: {
host: undefined,
port: 25,
ssl: false,
sender: undefined,
username: undefined,
password: undefined,
},
voice: {
regionId: undefined,
accessKeyId: undefined,
secret: undefined,
},
sms: {
regionId: undefined,
accessKeyId: undefined,
secret: undefined,
},
webhook: {
url: undefined,
headers: [],
},
};