前端更新 (#189)

* fix:add package

* fix:notice_info为null的情况

* fix:2 bugs

* feat:1.common增加通知配置 2.cmdb预定义值webhook&其他模型

* fix:json 不支持预定义值

* fix:json 不支持预定义值
This commit is contained in:
wang-liang0615 2023-10-09 17:43:34 +08:00 committed by GitHub
parent 297270063c
commit b8fed4e655
29 changed files with 4835 additions and 3197 deletions

View File

@ -54,6 +54,48 @@
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe88e;</span>
<div class="name">wechatApp</div>
<div class="code-name">&amp;#xe88e;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe88b;</span>
<div class="name">robot</div>
<div class="code-name">&amp;#xe88b;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe88c;</span>
<div class="name">feishuApp</div>
<div class="code-name">&amp;#xe88c;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe88d;</span>
<div class="name">dingdingApp</div>
<div class="code-name">&amp;#xe88d;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe88a;</span>
<div class="name">email</div>
<div class="code-name">&amp;#xe88a;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe887;</span>
<div class="name">setting-feishu</div>
<div class="code-name">&amp;#xe887;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe888;</span>
<div class="name">setting-feishu-selected</div>
<div class="code-name">&amp;#xe888;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe886;</span>
<div class="name">cmdb-histogram</div>
@ -2100,6 +2142,12 @@
<div class="code-name">&amp;#xe738;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe889;</span>
<div class="name">ops-setting-notice-email-selected</div>
<div class="code-name">&amp;#xe889;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe72f;</span>
<div class="name">ops-setting-notice</div>
@ -3954,9 +4002,9 @@
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1694508259411') format('woff2'),
url('iconfont.woff?t=1694508259411') format('woff'),
url('iconfont.ttf?t=1694508259411') format('truetype');
src: url('iconfont.woff2?t=1696815443987') format('woff2'),
url('iconfont.woff?t=1696815443987') format('woff'),
url('iconfont.ttf?t=1696815443987') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@ -3982,6 +4030,69 @@
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont wechatApp"></span>
<div class="name">
wechatApp
</div>
<div class="code-name">.wechatApp
</div>
</li>
<li class="dib">
<span class="icon iconfont robot"></span>
<div class="name">
robot
</div>
<div class="code-name">.robot
</div>
</li>
<li class="dib">
<span class="icon iconfont feishuApp"></span>
<div class="name">
feishuApp
</div>
<div class="code-name">.feishuApp
</div>
</li>
<li class="dib">
<span class="icon iconfont dingdingApp"></span>
<div class="name">
dingdingApp
</div>
<div class="code-name">.dingdingApp
</div>
</li>
<li class="dib">
<span class="icon iconfont email"></span>
<div class="name">
email
</div>
<div class="code-name">.email
</div>
</li>
<li class="dib">
<span class="icon iconfont ops-setting-notice-feishu"></span>
<div class="name">
setting-feishu
</div>
<div class="code-name">.ops-setting-notice-feishu
</div>
</li>
<li class="dib">
<span class="icon iconfont ops-setting-notice-feishu-selected"></span>
<div class="name">
setting-feishu-selected
</div>
<div class="code-name">.ops-setting-notice-feishu-selected
</div>
</li>
<li class="dib">
<span class="icon iconfont cmdb-bar"></span>
<div class="name">
@ -7051,6 +7162,15 @@
</div>
</li>
<li class="dib">
<span class="icon iconfont ops-setting-notice-email-selected-copy"></span>
<div class="name">
ops-setting-notice-email-selected
</div>
<div class="code-name">.ops-setting-notice-email-selected-copy
</div>
</li>
<li class="dib">
<span class="icon iconfont ops-setting-notice"></span>
<div class="name">
@ -9832,6 +9952,62 @@
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#wechatApp"></use>
</svg>
<div class="name">wechatApp</div>
<div class="code-name">#wechatApp</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#robot"></use>
</svg>
<div class="name">robot</div>
<div class="code-name">#robot</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#feishuApp"></use>
</svg>
<div class="name">feishuApp</div>
<div class="code-name">#feishuApp</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#dingdingApp"></use>
</svg>
<div class="name">dingdingApp</div>
<div class="code-name">#dingdingApp</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#email"></use>
</svg>
<div class="name">email</div>
<div class="code-name">#email</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#ops-setting-notice-feishu"></use>
</svg>
<div class="name">setting-feishu</div>
<div class="code-name">#ops-setting-notice-feishu</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#ops-setting-notice-feishu-selected"></use>
</svg>
<div class="name">setting-feishu-selected</div>
<div class="code-name">#ops-setting-notice-feishu-selected</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#cmdb-bar"></use>
@ -12560,6 +12736,14 @@
<div class="code-name">#ops-dot</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#ops-setting-notice-email-selected-copy"></use>
</svg>
<div class="name">ops-setting-notice-email-selected</div>
<div class="code-name">#ops-setting-notice-email-selected-copy</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#ops-setting-notice"></use>

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 3857903 */
src: url('iconfont.woff2?t=1694508259411') format('woff2'),
url('iconfont.woff?t=1694508259411') format('woff'),
url('iconfont.ttf?t=1694508259411') format('truetype');
src: url('iconfont.woff2?t=1696815443987') format('woff2'),
url('iconfont.woff?t=1696815443987') format('woff'),
url('iconfont.ttf?t=1696815443987') format('truetype');
}
.iconfont {
@ -13,6 +13,34 @@
-moz-osx-font-smoothing: grayscale;
}
.wechatApp:before {
content: "\e88e";
}
.robot:before {
content: "\e88b";
}
.feishuApp:before {
content: "\e88c";
}
.dingdingApp:before {
content: "\e88d";
}
.email:before {
content: "\e88a";
}
.ops-setting-notice-feishu:before {
content: "\e887";
}
.ops-setting-notice-feishu-selected:before {
content: "\e888";
}
.cmdb-bar:before {
content: "\e886";
}
@ -1377,6 +1405,10 @@
content: "\e738";
}
.ops-setting-notice-email-selected-copy:before {
content: "\e889";
}
.ops-setting-notice:before {
content: "\e72f";
}

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,55 @@
"css_prefix_text": "",
"description": "",
"glyphs": [
{
"icon_id": "37590786",
"name": "wechatApp",
"font_class": "wechatApp",
"unicode": "e88e",
"unicode_decimal": 59534
},
{
"icon_id": "37590798",
"name": "robot",
"font_class": "robot",
"unicode": "e88b",
"unicode_decimal": 59531
},
{
"icon_id": "37590794",
"name": "feishuApp",
"font_class": "feishuApp",
"unicode": "e88c",
"unicode_decimal": 59532
},
{
"icon_id": "37590791",
"name": "dingdingApp",
"font_class": "dingdingApp",
"unicode": "e88d",
"unicode_decimal": 59533
},
{
"icon_id": "37590776",
"name": "email",
"font_class": "email",
"unicode": "e88a",
"unicode_decimal": 59530
},
{
"icon_id": "37537876",
"name": "setting-feishu",
"font_class": "ops-setting-notice-feishu",
"unicode": "e887",
"unicode_decimal": 59527
},
{
"icon_id": "37537859",
"name": "setting-feishu-selected",
"font_class": "ops-setting-notice-feishu-selected",
"unicode": "e888",
"unicode_decimal": 59528
},
{
"icon_id": "37334642",
"name": "cmdb-histogram",
@ -2392,6 +2441,13 @@
"unicode": "e738",
"unicode_decimal": 59192
},
{
"icon_id": "37575490",
"name": "ops-setting-notice-email-selected",
"font_class": "ops-setting-notice-email-selected-copy",
"unicode": "e889",
"unicode_decimal": 59529
},
{
"icon_id": "34108346",
"name": "ops-setting-notice",

Binary file not shown.

View File

@ -79,13 +79,20 @@ export function updatePasswordByUid(uid, data) {
})
}
export function bindWxByUid(uid) {
export function bindPlatformByUid(platform, uid) {
return axios({
url: `/common-setting/v1/employee/by_uid/bind_work_wechat/${uid}`,
url: `/common-setting/v1/employee/by_uid/bind_notice/${platform}/${uid}`,
method: 'put',
})
}
export function unbindPlatformByUid(platform, uid) {
return axios({
url: `/common-setting/v1/employee/by_uid/bind_notice/${platform}/${uid}`,
method: 'delete',
})
}
export function getAllPosition() {
return axios({
url: `/common-setting/v1/employee/position`,

View File

@ -0,0 +1,40 @@
import { axios } from '@/utils/request'
export function sendTestEmail(receive_address, data) {
return axios({
url: `/common-setting/v1/notice_config/send_test_email?receive_address=${receive_address}`,
method: 'post',
data
})
}
export const getNoticeConfigByPlatform = (platform) => {
return axios({
url: '/common-setting/v1/notice_config',
method: 'get',
params: { ...platform },
})
}
export const postNoticeConfigByPlatform = (data) => {
return axios({
url: '/common-setting/v1/notice_config',
method: 'post',
data
})
}
export const putNoticeConfigByPlatform = (id, info) => {
return axios({
url: `/common-setting/v1/notice_config/${id}`,
method: 'put',
data: info
})
}
export const getNoticeConfigAppBot = () => {
return axios({
url: `/common-setting/v1/notice_config/app_bot`,
method: 'get',
})
}

View File

@ -40,6 +40,8 @@
}
}
"
appendToBody
:zIndex="1050"
>
<div
:title="node.label"
@ -76,6 +78,8 @@
}
"
@select="(value) => handleChangeExp(value, item, index)"
appendToBody
:zIndex="1050"
>
</treeselect>
<treeselect
@ -97,6 +101,8 @@
}
}
"
appendToBody
:zIndex="1050"
>
<div
:title="node.label"
@ -135,6 +141,8 @@
}
}
"
appendToBody
:zIndex="1050"
>
</treeselect>
<a-input class="ops-input" v-model="item.value" size="small" style="width: 113px" />

View File

@ -83,10 +83,10 @@ export default {
getContent() {
const html = _.cloneDeep(this.editor.getHtml())
const _html = html.replace(
/<span data-w-e-type="attachment" data-w-e-is-void data-w-e-is-inline.*?<\/span>/gm,
/<span data-w-e-type="attachment" (data-w-e-is-void|data-w-e-is-void="") (data-w-e-is-inline|data-w-e-is-inline="").*?<\/span>/gm,
(value) => {
const _match = value.match(/(?<=data-attachmentValue=").*?(?=")/)
return `{{${_match}}}`
const _match = value.match(/(?<=data-attachment(V|v)alue=").*?(?=")/)
return `{{${_match[0]}}}`
}
)
return { body_html: html, body: _html }

View File

@ -74,6 +74,9 @@ export default {
}
div.jsoneditor {
border-color: #f3f4f6;
.jsoneditor-outer {
border-color: #f3f4f6;
}
}
}
</style>

View File

@ -307,9 +307,9 @@
<FontArea ref="fontArea" />
</a-form-item>
</a-col>
<a-col :span="24">
<a-col :span="24" v-if="currentValueType !== '6'">
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" label="预定义值">
<PreValueArea ref="preValueArea" :disabled="isShowComputedArea" />
<PreValueArea v-if="drawerVisible" ref="preValueArea" :disabled="isShowComputedArea" />
</a-form-item>
</a-col>
<a-col :span="24" v-if="currentValueType !== '6'">
@ -573,10 +573,13 @@ export default {
})
}
const _find = attributes.find((item) => item.id === record.id)
if (record.value_type !== '6') {
this.$refs.preValueArea.setData({
choice_value: (_find || {}).choice_value || [],
choice_web_hook: record.choice_web_hook,
choice_other: record.choice_other || undefined,
})
}
this.$refs.fontArea.setData({
fontOptions: _find?.option?.fontOptions || {},
})
@ -630,9 +633,11 @@ export default {
values = { ...values, ...computedAreaData }
} else {
// 如果是非计算属性就看看有没有预定义值
if (values.value_type !== '6') {
const preValueAreaData = this.$refs.preValueArea.getData()
values = { ...values, ...preValueAreaData }
}
}
const fontOptions = this.$refs.fontArea.getData()
if (values.id) {

View File

@ -357,7 +357,6 @@ export default {
})
},
handleAddGroupAttr(index) {
console.log(111, index)
let group = null
if (index === 0 || index) {
group = this.CITypeGroups[index]
@ -416,7 +415,7 @@ export default {
console.log('changess')
if (e.hasOwnProperty('moved') && e.moved.oldIndex !== e.moved.newIndex) {
if (group === -1) {
this.$message.error('更多属性不能进行排序, 如需排序需添加入其他分组中')
this.$message.error('其他分组中的属性不能进行排序,如需排序请先拖至自定义的分组')
} else {
transferCITypeAttrIndex(this.CITypeId, {
from: { attr_id: e.moved.element.id, group_id: group > -1 ? group : null },

View File

@ -294,7 +294,7 @@
<FontArea ref="fontArea" />
</a-form-item>
</a-col>
<a-col :span="24">
<a-col :span="24" v-if="currentValueType !== '6'">
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" label="预定义值">
<PreValueArea ref="preValueArea" :disabled="isShowComputedArea" />
</a-form-item>
@ -428,9 +428,11 @@ export default {
values = { ...values, ...computedAreaData }
} else {
// 如果是非计算属性就看看有没有预定义值
if (values.value_type !== '6') {
const preValueAreaData = this.$refs.preValueArea.getData()
values = { ...values, ...preValueAreaData }
}
}
const fontOptions = this.$refs.fontArea.getData()
// is_index进行操作除了文本 索引隐藏掉 文本 索引默认是true
@ -445,7 +447,9 @@ export default {
this.form.resetFields()
this.currentValueType = '2'
if (values.value_type !== '6') {
this.$refs.preValueArea.valueList = []
}
this.$emit('done', attr_id, data, isCloseModal)
} else {
throw new Error()

View File

@ -28,32 +28,8 @@
</a-tab-pane>
<a-tab-pane key="webhook" :disabled="disabled">
<span style="font-size:12px;" slot="tab">Webhook</span>
<Webhook ref="webhook" style="margin-top:10px" />
<a-form-model :model="form">
<a-row :gutter="24">
<a-col :span="24">
<a-form-model-item label="地址" prop="url" :labelCol="{ span: 3 }" :wrapperCol="{ span: 16 }">
<a-input v-model="form.url" :disabled="disabled">
<a-select
:showArrow="false"
slot="addonBefore"
style="width:60px;"
v-model="form.method"
:disabled="disabled"
>
<a-select-option value="get">
GET
</a-select-option>
<a-select-option value="post">
POST
</a-select-option>
<a-select-option value="put">
PUT
</a-select-option>
</a-select>
</a-input>
</a-form-model-item>
</a-col>
</a-row>
<a-col :span="24">
<a-form-model-item prop="ret_key" :labelCol="{ span: 3 }" :wrapperCol="{ span: 18 }">
<template slot="label">
@ -76,6 +52,124 @@
</a-col>
</a-form-model>
</a-tab-pane>
<a-tab-pane key="choice_other" :disabled="disabled">
<span style="font-size:12px;" slot="tab">其他模型属性</span>
<a-row :gutter="[24, 24]">
<a-col :span="12">
<a-form-item
:style="{ lineHeight: '24px', marginBottom: '5px' }"
label="模型"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 20 }"
>
<treeselect
:disable-branch-nodes="true"
:class="{
'custom-treeselect': true,
'custom-treeselect-bgcAndBorder': true,
}"
:style="{
'--custom-height': '32px',
lineHeight: '32px',
'--custom-bg-color': '#fff',
'--custom-border': '1px solid #d9d9d9',
'--custom-multiple-lineHeight': '14px',
}"
v-model="choice_other.type_ids"
:multiple="true"
:clearable="true"
searchable
:options="ciTypeGroup"
value-consists-of="LEAF_PRIORITY"
placeholder="请选择CMDB模型"
:normalizer="
(node) => {
return {
id: node.id || -1,
label: node.alias || node.name || '其他',
title: node.alias || node.name || '其他',
children: node.ci_types,
}
}
"
appendToBody
:zIndex="1050"
@select="
() => {
choice_other.attr_id = undefined
}
"
>
<div
:title="node.label"
slot="option-label"
slot-scope="{ node }"
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
>
{{ node.label }}
</div>
</treeselect>
</a-form-item>
</a-col>
<a-col :span="12" v-if="choice_other.type_ids && choice_other.type_ids.length">
<a-form-item
:style="{ marginBottom: '5px' }"
label="属性"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 20 }"
>
<treeselect
:disable-branch-nodes="true"
class="ops-setting-treeselect"
v-model="choice_other.attr_id"
:multiple="false"
:clearable="true"
searchable
:options="typeAttrs"
value-consists-of="LEAF_PRIORITY"
placeholder="请选择模型属性"
:normalizer="
(node) => {
return {
id: node.id || -1,
label: node.alias || node.name || '其他',
title: node.alias || node.name || '其他',
}
}
"
appendToBody
:zIndex="1050"
>
<div
:title="node.label"
slot="option-label"
slot-scope="{ node }"
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
>
{{ node.label }}
</div>
</treeselect>
</a-form-item>
</a-col>
<a-col :span="24" v-if="choice_other.type_ids && choice_other.type_ids.length">
<a-form-item
:style="{ marginBottom: '5px' }"
class="pre-value-filter"
label="筛选"
:label-col="{ span: 2 }"
:wrapper-col="{ span: 22 }"
>
<FilterComp
ref="filterComp"
:isDropdown="false"
:canSearchPreferenceAttrList="typeAttrs"
@setExpFromFilter="setExpFromFilter"
:expression="filterExp ? `q=${filterExp}` : ''"
/>
</a-form-item>
</a-col>
</a-row>
</a-tab-pane>
</a-tabs>
</template>
@ -85,10 +179,14 @@ import draggable from 'vuedraggable'
import PreValueTag from './preValueTag.vue'
import { defautValueColor } from '../../utils/const'
import ColorPicker from '../../components/colorPicker/index.vue'
import Webhook from '../../components/webhook'
import { getCITypeGroups } from '../../api/ciTypeGroup'
import { getCITypeCommonAttributesByTypeIds } from '../../api/CITypeAttr'
import FilterComp from '@/components/CMDBFilterComp'
export default {
name: 'PreValueArea',
components: { draggable, PreValueTag, ColorPicker },
components: { draggable, PreValueTag, ColorPicker, Webhook, FilterComp },
props: {
disabled: {
type: Boolean,
@ -101,10 +199,15 @@ export default {
activeKey: 'define', // define webhook
valueList: [],
form: {
url: '',
method: 'get',
ret_key: '',
},
choice_other: {
type_ids: undefined,
attr_id: undefined,
},
ciTypeGroup: [],
typeAttrs: [],
filterExp: '',
}
},
watch: {
@ -120,6 +223,25 @@ export default {
}
},
},
'choice_other.type_ids': {
handler(newValue) {
if (newValue && newValue.length) {
getCITypeCommonAttributesByTypeIds({ type_ids: newValue.join(',') }).then((res) => {
this.typeAttrs = res.attributes
})
}
},
},
},
created() {
getCITypeGroups({ need_other: true }).then((res) => {
this.ciTypeGroup = res
.filter((item) => item.ci_types && item.ci_types.length)
.map((item) => {
item.id = `parent_${item.id || -1}`
return { ..._.cloneDeep(item) }
})
})
},
methods: {
addNewValue(newValue, newStyle, newIcon) {
@ -153,15 +275,42 @@ export default {
return {
choice_value: this.valueList,
choice_web_hook: null,
choice_other: null,
}
} else if (this.activeKey === 'webhook') {
const choice_web_hook = this.$refs.webhook.getParams()
choice_web_hook.ret_key = this.form.ret_key
return { choice_value: [], choice_web_hook, choice_other: null }
} else {
return { choice_value: [], choice_web_hook: this.form }
let choice_other = {}
if (this.choice_other.type_ids && this.choice_other.type_ids.length) {
this.$refs.filterComp.handleSubmit()
choice_other = { ...this.choice_other, filter: this.filterExp }
}
return {
choice_value: [],
choice_web_hook: null,
choice_other,
}
}
},
setData({ choice_value, choice_web_hook }) {
setData({ choice_value, choice_web_hook, choice_other }) {
if (choice_web_hook) {
this.form = choice_web_hook
this.activeKey = 'webhook'
this.$nextTick(() => {
this.$refs.webhook.setParams(choice_web_hook)
this.form.ret_key = choice_web_hook.ret_key ?? ''
})
} else if (choice_other) {
this.activeKey = 'choice_other'
const { type_ids, attr_id, filter } = choice_other
this.choice_other = { type_ids, attr_id }
this.filterExp = filter
if (type_ids && type_ids.length) {
this.$nextTick(() => {
this.$refs.filterComp.visibleChange(true, false)
})
}
} else {
this.valueList = choice_value
this.activeKey = 'define'
@ -174,6 +323,13 @@ export default {
dom.style.backgroundColor = '#2f54eb'
}
},
setExpFromFilter(filterExp) {
if (filterExp) {
this.filterExp = `${filterExp}`
} else {
this.filterExp = ''
}
},
},
}
</script>
@ -193,3 +349,14 @@ export default {
}
}
</style>
<style lang="less">
.pre-value-filter {
.ant-form-item-control {
line-height: 24px;
}
.table-filter-add {
line-height: 40px;
}
}
</style>

View File

@ -156,12 +156,74 @@
</a-form-model-item>
<a-form-model-item label="通知方式" prop="method">
<a-checkbox-group v-model="notifies.method">
<a-row :style="{ marginTop: '4px' }" :gutter="[0, 12]">
<a-col :span="6">
<a-checkbox value="email"> <ops-icon type="email" style="margin-right:5px" />邮件 </a-checkbox>
</a-col>
<a-col :span="6">
<a-checkbox value="wechatApp">
微信
<ops-icon type="wechatApp" style="margin-right:5px" />企业微信
</a-checkbox>
<a-checkbox value="email">
邮件
</a-col>
<a-col :span="6">
<a-checkbox value="dingdingApp">
<ops-icon type="dingdingApp" style="margin-right:5px" />钉钉
</a-checkbox>
</a-col>
<a-col :span="6">
<a-checkbox value="feishuApp"> <ops-icon type="feishuApp" style="margin-right:5px" />飞书 </a-checkbox>
</a-col>
<a-col :span="4" :style="{ lineHeight: '32px' }">
<ops-icon type="robot" style="margin-right:5px" />机器人
</a-col>
<a-col :span="18">
<treeselect
:disable-branch-nodes="true"
:class="{
'custom-treeselect': true,
'custom-treeselect-bgcAndBorder': true,
}"
:style="{
'--custom-height': '32px',
lineHeight: '32px',
'--custom-bg-color': '#fff',
'--custom-border': '1px solid #d9d9d9',
'--custom-multiple-lineHeight': '14px',
}"
v-model="selectedBot"
:multiple="true"
:clearable="true"
searchable
:options="appBot"
value-consists-of="LEAF_PRIORITY"
placeholder="请选择机器人"
:normalizer="
(node) => {
return {
id: node.name,
label: node.label || node.name,
children: node.bot,
}
}
"
appendToBody
:zIndex="1050"
noChildrenText="暂无数据"
>
<div
:title="node.label"
slot="option-label"
slot-scope="{ node }"
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
>
<ops-icon :type="node.id" v-if="node.children" />{{ node.label }}
</div>
<div slot="value-label" slot-scope="{ node }">
<ops-icon :type="node.parentNode.id" />{{ node.label }}
</div>
</treeselect>
</a-col>
</a-row>
</a-checkbox-group>
</a-form-model-item>
</a-form-model>
@ -200,6 +262,7 @@ import EmployeeTreeSelect from '@/views/setting/components/employeeTreeSelect.vu
import Webhook from '../../components/webhook'
import NoticeContent from '../../components/noticeContent'
import { getNoticeByEmployeeIds } from '@/api/employee'
import { getNoticeConfigAppBot } from '@/api/noticeSetting'
export default {
name: 'TriggerForm',
@ -260,6 +323,8 @@ export default {
isShow: false,
dag_id: null,
showCustomEmail: false,
appBot: [],
selectedBot: undefined,
}
},
computed: {
@ -286,9 +351,15 @@ export default {
this.dags = res.map((dag) => ({ id: dag[1], label: dag[0] }))
})
},
async getNoticeConfigAppBot() {
await getNoticeConfigAppBot().then((res) => {
this.appBot = res
})
},
createFromTriggerTable(attrList) {
this.visible = true
// this.getDags()
this.getDags()
this.getNoticeConfigAppBot()
this.attrList = attrList
this.triggerId = null
this.title = '新增触发器'
@ -307,7 +378,8 @@ export default {
},
async open(property, attrList) {
this.visible = true
// await this.getDags()
await this.getDags()
this.getNoticeConfigAppBot()
this.attrList = attrList
if (property.has_trigger) {
this.triggerId = property.trigger.id
@ -348,7 +420,7 @@ export default {
const employee_ids = property?.trigger?.option?.employee_ids ?? undefined
const custom_email =
tos
.filter((t) => !t.employee_id)
.filter((t) => !t.employee_id && t.email)
.map((t) => t.email)
.join(';') ?? ''
@ -360,7 +432,16 @@ export default {
this.$refs.noticeContent.setContent(body_html)
}, 100)
}
this.notifies = { employee_ids, custom_email, subject, method }
const _method = method.filter((item) => ['email', 'wechatApp', 'dingdingApp', 'feishuApp'].includes(item))
const _flatAppBot = []
this.appBot.forEach((item) => {
_flatAppBot.push(...item.bot.map((b) => b.name))
})
const selectedBot = method.filter(
(item) => !['email', 'wechatApp', 'dingdingApp', 'feishuApp'].includes(item) && _flatAppBot.includes(item)
)
this.selectedBot = selectedBot
this.notifies = { employee_ids, custom_email, subject, method: _method }
}
} else {
this.title = `新增触发器 ${property.alias || property.name}`
@ -378,6 +459,7 @@ export default {
this.category = 1
this.triggerAction = '1'
this.filterExp = ''
this.selectedBot = undefined
this.visible = false
},
filterChange(value) {
@ -415,11 +497,30 @@ export default {
tos.push({ email })
})
}
if (this.selectedBot && this.selectedBot.length) {
this.selectedBot.forEach((bot) => {
tos.push({ [`${bot}`]: bot })
})
}
if (this.category === 2) {
const { before_days, notify_at } = this.dateForm
params.option.notifies = { tos, subject, body, body_html, method, before_days, notify_at }
params.option.notifies = {
tos,
subject,
body,
body_html,
method: [...method, ...(this.selectedBot ?? [])],
before_days,
notify_at,
}
} else {
params.option.notifies = { tos, subject, body, body_html, method }
params.option.notifies = {
tos,
subject,
body,
body_html,
method: [...method, ...(this.selectedBot ?? [])],
}
}
break
case '2':

View File

@ -29,6 +29,12 @@
<span v-else-if="row.notify">通知</span>
</template>
</vxe-column>
<vxe-column title="状态">
<template #default="{ row }">
<a-tag color="green" v-if="row.is_ok">已完成</a-tag>
<a-tag color="red" v-else>未完成</a-tag>
</template>
</vxe-column>
<vxe-column title="触发时间">
<template #default="{row}">
{{ row.updated_at || row.created_at }}

View File

@ -65,6 +65,34 @@ export const generatorDynamicRouter = async () => {
meta: { title: '公司架构', appName: 'backend', icon: 'ops-setting-companyStructure', selectedIcon: 'ops-setting-companyStructure-selected', permission: ['acl_admin', 'backend_admin'] },
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/companyStructure/index')
},
{
path: '/setting/notice',
name: 'notice',
component: RouteView,
meta: { title: '通知设置', appName: 'backend', icon: 'ops-setting-notice', selectedIcon: 'ops-setting-notice-selected', permission: ['通知设置', 'backend_admin'] },
redirect: '/setting/notice/email',
children: [{
path: '/setting/notice/email',
name: 'notice_email',
meta: { title: '邮件设置', icon: 'ops-setting-notice-email', selectedIcon: 'ops-setting-notice-email-selected' },
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/notice/email/index')
}, {
path: '/setting/notice/wx',
name: 'notice_wx',
meta: { title: '企业微信', icon: 'ops-setting-notice-wx', selectedIcon: 'ops-setting-notice-wx-selected' },
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/notice/wx')
}, {
path: '/setting/notice/dingding',
name: 'notice_dingding',
meta: { title: '钉钉', icon: 'ops-setting-notice-dingding', selectedIcon: 'ops-setting-notice-dingding-selected' },
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/notice/dingding')
}, {
path: '/setting/notice/feishu',
name: 'notice_feishu',
meta: { title: '飞书', icon: 'ops-setting-notice-feishu', selectedIcon: 'ops-setting-notice-feishu-selected' },
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/notice/feishu')
}]
}
]
},])
return routes

View File

@ -35,6 +35,9 @@
<a-input v-model="infoData.email" :disabled="!isEditable" />
</a-form-model-item>
<SpanTitle>公司标识</SpanTitle>
<a-form-model-item label="Messenger地址" prop="messenger">
<a-input v-model="infoData.messenger" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item label="部署域名" prop="domainName">
<a-input v-model="infoData.domainName" :disabled="!isEditable" />
</a-form-model-item>
@ -150,6 +153,8 @@ export default {
email: '',
logoName: '',
smallLogoName: '',
messenger: '',
domainName: '',
},
rule: {
name: [{ required: true, whitespace: true, message: '请输入名称', trigger: 'blur' }],
@ -251,6 +256,8 @@ export default {
email: '',
logoName: '',
smallLogoName: '',
messenger: '',
domainName: '',
}
},
customRequest(file) {

View File

@ -0,0 +1,122 @@
<template>
<div>
<vxe-table
ref="xTable"
:data="tableData"
size="mini"
stripe
class="ops-stripe-table"
show-overflow
:edit-config="{ showIcon: false, trigger: 'manual', mode: 'row' }"
>
<vxe-column v-for="col in columns" :key="col.field" :field="col.field" :title="col.title" :edit-render="{}">
<template #header> <span v-if="col.required" :style="{ color: 'red' }">* </span>{{ col.title }} </template>
<template #edit="{ row }">
<vxe-input v-model="row[col.field]" type="text"></vxe-input>
</template>
</vxe-column>
<vxe-column title="操作" width="80" v-if="!disabled">
<template #default="{ row }">
<template v-if="$refs.xTable.isActiveByRow(row)">
<a @click="saveRowEvent(row)"><a-icon type="save"/></a>
</template>
<a-space v-else>
<a @click="editRowEvent(row)"><ops-icon type="icon-xianxing-edit"/></a>
<a style="color:red" @click="deleteRowEvent(row)"><ops-icon type="icon-xianxing-delete"/></a>
</a-space>
</template>
</vxe-column>
</vxe-table>
<div :style="{ color: '#f5222d' }" v-if="errorFlag">请完整填写机器人配置</div>
<a-button
v-if="!disabled"
icon="plus-circle"
class="ops-button-primary"
type="primary"
@click="insertEvent"
>添加</a-button
>
</div>
</template>
<script>
export default {
name: 'Bot',
props: {
columns: {
type: Array,
default: () => [
{
field: 'name',
title: '名称',
required: true,
},
{
field: 'url',
title: 'Webhook地址',
required: true,
},
],
},
disabled: {
type: Boolean,
default: false,
},
},
data() {
return {
tableData: [],
errorFlag: false,
}
},
methods: {
async insertEvent() {
const $table = this.$refs.xTable
const record = {
name: '',
url: '',
}
const { row: newRow } = await $table.insertAt(record, -1)
await $table.setActiveRow(newRow)
},
saveRowEvent(row) {
const $table = this.$refs.xTable
$table.clearActived()
},
editRowEvent(row) {
const $table = this.$refs.xTable
$table.setActiveRow(row)
},
deleteRowEvent(row) {
const $table = this.$refs.xTable
$table.remove(row)
},
getData(callback) {
const $table = this.$refs.xTable
const { fullData: _tableData } = $table.getTableData()
const requiredObj = {}
this.columns.forEach((col) => {
if (col.required) {
requiredObj[col.field] = true
}
})
let flag = true
_tableData.forEach((td) => {
Object.keys(requiredObj).forEach((key) => {
if (requiredObj[key]) {
flag = !!(flag && td[`${key}`])
}
})
})
this.errorFlag = !flag
callback(flag, _tableData)
},
setData(value) {
this.tableData = value
this.errorFlag = false
},
},
}
</script>
<style></style>

View File

@ -0,0 +1,151 @@
<template>
<div class="notice-dingding-wrapper" :style="{ height: `${windowHeight - 64}px` }">
<a-form-model ref="dingdingForm" :model="dingdingData" :label-col="labelCol" :wrapper-col="wrapperCol">
<SpanTitle>基础设置</SpanTitle>
<a-form-model-item label="应用Key">
<a-input v-model="dingdingData.appKey" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item label="应用密码">
<a-input v-model="dingdingData.appSecret" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item label="机器人码">
<a-input v-model="dingdingData.robotCode" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item label="机器人">
<Bot
ref="bot"
:disabled="!isEditable"
:columns="[
{
field: 'name',
title: '名称',
required: true,
},
{
field: 'url',
title: 'Webhook地址',
required: true,
},
{
field: 'token',
title: 'token',
required: false,
},
]"
/>
</a-form-model-item>
<!-- <a-form-model-item label="测试邮件设置">
<a-button type="primary" ghost>测试回收箱</a-button>
<br />
<span
class="notice-dingding-wrapper-tips"
><ops-icon type="icon-shidi-quxiao" :style="{ color: '#D81E06' }" /> 邮件接收失败</span
>
<br />
<span>邮箱服务器未配置请配置一个邮箱服务器 | <a>故障诊断</a></span>
</a-form-model-item> -->
<a-row v-if="isEditable">
<a-col :span="16" :offset="3">
<a-form-model-item :label-col="labelCol" :wrapper-col="wrapperCol">
<a-button type="primary" @click="onSubmit"> 保存 </a-button>
<a-button ghost type="primary" style="margin-left: 28px;" @click="resetForm"> 重置 </a-button>
</a-form-model-item>
</a-col>
</a-row>
</a-form-model>
</div>
</template>
<script>
import { mapState } from 'vuex'
import SpanTitle from '../components/spanTitle.vue'
import { getNoticeConfigByPlatform, postNoticeConfigByPlatform, putNoticeConfigByPlatform } from '@/api/noticeSetting'
import { mixinPermissions } from '@/utils/mixin'
import Bot from './bot.vue'
export default {
name: 'NoticeDingding',
components: { SpanTitle, Bot },
mixins: [mixinPermissions],
data() {
return {
labelCol: { lg: 3, md: 5, sm: 8 },
wrapperCol: { lg: 15, md: 19, sm: 16 },
id: null,
dingdingData: {
appKey: '',
appSecret: '',
robotCode: '',
},
}
},
computed: {
...mapState({
windowHeight: (state) => state.windowHeight,
}),
isEditable() {
return this.hasDetailPermission('backend', '通知设置', ['update'])
},
},
mounted() {
this.getData()
},
methods: {
getData() {
getNoticeConfigByPlatform({ platform: 'dingdingApp' }).then((res) => {
this.id = res?.id ?? null
if (this.id) {
this.dingdingData = res.info
this.$refs.bot.setData(res?.info?.bot)
}
})
},
onSubmit() {
this.$refs.dingdingForm.validate(async (valid) => {
if (valid) {
this.$refs.bot.getData(async (flag, bot) => {
if (flag) {
if (this.id) {
await putNoticeConfigByPlatform(this.id, { info: { ...this.dingdingData, bot, label: '钉钉' } })
} else {
await postNoticeConfigByPlatform({
platform: 'dingdingApp',
info: { ...this.dingdingData, bot, label: '钉钉' },
})
}
this.$message.success('保存成功')
this.getData()
}
})
}
})
},
resetForm() {
this.dingdingData = {
appKey: '',
appSecret: '',
robotCode: '',
}
},
},
}
</script>
<style lang="less" scoped>
.notice-dingding-wrapper {
background-color: #fff;
padding-top: 15px;
overflow: auto;
margin-bottom: -24px;
border-radius: 15px;
.notice-dingding-wrapper-tips {
display: inline-block;
background-color: #ffdfdf;
border-radius: 4px;
padding: 0 12px;
width: 300px;
color: #000000;
margin-top: 8px;
}
}
</style>

View File

@ -0,0 +1,17 @@
.notice-email-wrapper {
background-color: #fff;
padding-top: 24px;
overflow: auto;
.notice-email-error-tips {
display: inline-block;
background-color: #ffdfdf;
border-radius: 4px;
padding: 0 12px;
width: 300px;
color: #000000;
margin-top: 8px;
}
.ant-form-item {
margin-bottom: 10px;
}
}

View File

@ -0,0 +1,33 @@
<template>
<div :style="{ marginBottom: '-24px' }">
<a-tabs :activeKey="activeKey" @change="changeTab" class="ops-tab" type="card">
<!-- <a-tab-pane key="1" tab="接收服务器">
<Receive />
</a-tab-pane> -->
<a-tab-pane key="2" tab="发送服务器">
<Send />
</a-tab-pane>
</a-tabs>
</div>
</template>
<script>
import Receive from './receive.vue'
import Send from './send.vue'
export default {
name: 'NoticeEmail',
components: { Receive, Send },
data() {
return {
activeKey: '2',
}
},
methods: {
changeTab(activeKey) {
this.activeKey = activeKey
},
},
}
</script>
<style></style>

View File

@ -0,0 +1,196 @@
<template>
<div class="notice-email-wrapper" :style="{ height: `${windowHeight - 104}px` }">
<a-form-model :model="settingData" :label-col="labelCol" :wrapper-col="wrapperCol">
<SpanTitle>基础设置</SpanTitle>
<a-form-model-item label="连接协议">
<a-radio-group v-model="settingData.connectProtocol" :default-value="1" @change="changeConnectProtocol">
<a-radio :value="1" :default-checked="true"> POP/IMAP/POPS/IMAPS </a-radio>
<a-radio :value="2"> EWS(Exchange Web服务) </a-radio>
</a-radio-group>
</a-form-model-item>
<a-form-model-item label="认证类型">
<a-select v-model="settingData.authentication">
<a-select-option value="Base"> 基本 </a-select-option>
<a-select-option value="OAuth"> OAuth </a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item label="服务器名/IP地址" prop="IP">
<a-input v-model="settingData.IP" />
</a-form-model-item>
<a-form-model-item label="用户名">
<a-input v-model="settingData.username" />
</a-form-model-item>
<a-form-model-item label="密码">
<a-input v-model="settingData.password" />
</a-form-model-item>
<a-form-model-item label="邮件地址">
<a-input v-model="settingData.email" />
</a-form-model-item>
<template v-if="settingData.connectProtocol === 1">
<a-form-model-item label="邮件类型">
<a-select v-model="settingData.emailType">
<a-select-option value="POP"> POP </a-select-option>
<a-select-option value="IMAP"> IMAP </a-select-option>
<a-select-option value="POPS"> POPS </a-select-option>
<a-select-option value="IMAPS"> IMAPS </a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item label="端口">
<a-input v-model="settingData.port" />
</a-form-model-item>
</template>
<a-form-model-item label="测试邮件设置">
<a-button type="primary" ghost>测试回收箱</a-button>
<br />
<span class="notice-email-error-tips">
<ops-icon type="icon-shidi-quxiao" :style="{ color: '#D81E06' }" />
邮件接收失败
</span>
<br />
<span
>邮箱服务器未配置请配置一个邮箱服务器 <a-divider type="vertical" :style="{ backgroundColor: '#2F54EB' }" />
<a>故障诊断</a></span
>
</a-form-model-item>
<SpanTitle>邮件设置</SpanTitle>
<a-form-model-item label="获取邮件间隔" :wrapperCol="{ span: 4 }">
<a-input class="ant-input-after" v-model="settingData.getEmailTimeout" />
<span :style="{ position: 'absolute', marginLeft: '8px' }"></span>
</a-form-model-item>
<a-row>
<a-col :span="16" :offset="3">
<a-checkbox :default-checked="false" disabled>启动代理服务器</a-checkbox>
<a-icon type="info-circle" :style="{ color: '#FF9E58', fontSize: '16px' }" />
<a-divider type="vertical" :style="{ backgroundColor: '#2F54EB' }" />
<a @click="configProxySetting">配置代理设置</a>
<br />
<a-checkbox :default-checked="false">启动邮件测试</a-checkbox>
<br /><br />
<a-checkbox :default-checked="false" @change="changeCreateReqByEmail">禁用通过邮件创建请求</a-checkbox>
<br />
<template v-if="settingData.banReqByEmail">
<strong>指定允许的邮件/域名,逗号分隔多个值</strong>
<a-input type="textarea" :style="{ borderRadius: '8px', borderColor: '#2F54EB' }" />
<p :style="{ fontSize: '12px' }">例如:user@domain.com,*@domain.com</p>
<p :style="{ fontSize: '12px' }">限制不能适用于已在会话中的请求,它将聚集到它的上级工单中</p>
</template>
</a-col>
</a-row>
<SpanTitle>消息设置</SpanTitle>
<a-row>
<a-col :span="16" :offset="3">
<a-checkbox :default-checked="false">将消息移动到错误的文件夹</a-checkbox>
<a-icon type="info-circle" :style="{ color: '#FF9E58', fontSize: '16px' }" />
<a-divider type="vertical" :style="{ backgroundColor: '#2F54EB' }" />
<a href="#">了解更多</a>
</a-col>
</a-row>
<br /><br />
<a-row>
<a-col :span="16" :offset="3">
<a-form-model-item :label-col="labelCol" :wrapper-col="wrapperCol">
<a-button type="primary" @click="onSubmit"> 保存 </a-button>
<a-button ghost type="primary" style="margin-left: 28px;" @click="resetForm"> 重置 </a-button>
</a-form-model-item>
</a-col>
</a-row>
</a-form-model>
<a-modal dialogClass="ops-modal" width="500px" v-model="visible" title="配置代理设置">
<a-form-model v-model="proxySetting" :label-col="{ span: 4 }" :wrapper-col="{ span: 19 }">
<a-form-model-item label="主机">
<a-input v-model="proxySetting.host" />
</a-form-model-item>
<a-form-model-item label="端口">
<a-input v-model="proxySetting.port" />
</a-form-model-item>
<a-form-model-item label="用户名">
<a-input v-model="proxySetting.username" />
</a-form-model-item>
<a-form-model-item label="密码">
<a-input v-model="proxySetting.password" />
</a-form-model-item>
</a-form-model>
</a-modal>
</div>
</template>
<script>
import { mapState } from 'vuex'
import SpanTitle from '../../components/spanTitle.vue'
export default {
name: 'Receive',
components: { SpanTitle },
data() {
return {
labelCol: { span: 3 },
wrapperCol: { span: 10 },
settingData: {
connectProtocol: 1,
authentication: 'Base',
IP: '',
username: '',
password: '',
email: '',
emailType: '',
port: '',
getEmailTimeout: '',
activeProxy: false,
activeEmailDebug: false,
banReqByEmail: false,
transfromMessage: false,
},
visible: false,
proxySetting: {
host: '',
post: '',
username: '',
password: '',
},
}
},
computed: {
...mapState({
windowHeight: (state) => state.windowHeight,
}),
},
methods: {
changeConnectProtocol(e) {
console.log(e.target.value)
},
changeCreateReqByEmail(e) {
this.settingData.banReqByEmail = e.target.checked
},
configProxySetting() {
this.visible = true
},
onSubmit() {
console.log(this.settingData)
},
resetForm() {
this.settingData = {
connectProtocol: 1,
authentication: '',
IP: '',
username: '',
password: '',
email: '',
emailType: '',
port: '',
getEmailTimeout: '',
activeProxy: false,
activeEmailDebug: false,
banReqByEmail: false,
transfromMessage: false,
}
},
},
}
</script>
<style lang="less" scoped>
@import './index.less';
</style>

View File

@ -0,0 +1,169 @@
<template>
<div class="notice-email-wrapper" :style="{ height: `${windowHeight - 104}px` }">
<a-form-model ref="sendForm" :model="settingData" :label-col="labelCol" :rules="rules" :wrapper-col="wrapperCol">
<SpanTitle>基础设置</SpanTitle>
<a-form-model-item label="是否加密">
<a-radio-group v-model="settingData.tls" :disabled="!isEditable">
<a-radio :value="true">
</a-radio>
<a-radio :value="false">
</a-radio>
</a-radio-group>
</a-form-model-item>
<a-form-model-item label="端口" prop="port">
<a-input v-model="settingData.port" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item label="邮件服务器" prop="host">
<a-input v-model="settingData.host" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item label="用户名" prop="account">
<a-input v-model="settingData.account" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item label="密码" prop="password">
<a-input-password v-model="settingData.password" :disabled="!isEditable" />
</a-form-model-item>
<SpanTitle>邮件测试</SpanTitle>
<a-form-model-item label="测试发送邮件地址" prop="receive_address">
<a-input v-model="settingData.receive_address" :disabled="!isEditable">
<span
v-if="isEditable"
:style="{ cursor: 'pointer' }"
@click="testSendEmail"
slot="addonAfter"
>测试邮件发送</span
>
</a-input>
</a-form-model-item>
<a-row v-if="isEditable">
<a-col :span="16" :offset="3">
<a-form-model-item :label-col="labelCol" :wrapper-col="wrapperCol">
<a-button type="primary" @click="onSubmit"> 保存 </a-button>
<a-button ghost type="primary" style="margin-left: 28px;" @click="resetForm"> 重置 </a-button>
</a-form-model-item>
</a-col>
</a-row>
</a-form-model>
</div>
</template>
<script>
import { mapState } from 'vuex'
import SpanTitle from '../../components/spanTitle.vue'
import {
getNoticeConfigByPlatform,
postNoticeConfigByPlatform,
putNoticeConfigByPlatform,
sendTestEmail,
} from '@/api/noticeSetting'
import { mixinPermissions } from '@/utils/mixin'
export default {
name: 'Send',
mixins: [mixinPermissions],
components: { SpanTitle },
data() {
return {
labelCol: { lg: 3, md: 5, sm: 8 },
wrapperCol: { lg: 10, md: 12, sm: 12 },
id: null,
settingData: {
tls: true,
host: '',
account: '',
password: '',
port: '',
receive_address: '',
},
rules: {
port: [{ required: true, message: '请输入端口', trigger: 'blur' }],
host: [{ required: true, whitespace: true, message: '请输入服务器', trigger: 'blur' }],
account: [
{ required: true, whitespace: true, message: '请输入用户名', trigger: 'blur' },
{
pattern: /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/,
message: '邮箱格式错误',
trigger: 'blur',
},
],
password: [{ required: false, whitespace: true, message: '请输入密码', trigger: 'blur' }],
receive_address: [
{ required: false, whitespace: true, message: '请输入测试发送邮件地址', trigger: 'blur' },
{
pattern: /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/,
message: '邮箱格式错误',
trigger: 'blur',
},
],
},
}
},
computed: {
...mapState({
windowHeight: (state) => state.windowHeight,
}),
isEditable() {
return this.hasDetailPermission('backend', '通知设置', ['update'])
},
},
watch: {
'settingData.tls': {
handler(newV, oldV) {
if (newV === false) {
this.settingData.port = 25
}
if (newV === true) {
this.settingData.port = 465
}
},
immediate: true,
},
},
mounted() {
this.getData()
},
methods: {
getData() {
getNoticeConfigByPlatform({ platform: 'email' }).then((res) => {
this.id = res?.id ?? null
if (this.id) {
this.settingData = res.info
}
})
},
async testSendEmail() {
await sendTestEmail(this.settingData.receive_address, {
info: { ...this.settingData, receive_address: undefined },
})
this.$message.success('已发送邮件,请查收')
},
onSubmit() {
this.$refs.sendForm.validate(async (valid) => {
if (valid) {
if (this.id) {
await putNoticeConfigByPlatform(this.id, { info: { ...this.settingData, label: '邮箱' } })
} else {
await postNoticeConfigByPlatform({ platform: 'email', info: { ...this.settingData, label: '邮箱' } })
}
this.$message.success('保存成功')
this.getData()
}
})
},
resetForm() {
this.settingData = {
tls: true,
host: '',
account: '',
password: '',
port: 25,
receive_address: '',
}
},
},
}
</script>
<style lang="less" scoped>
@import './index.less';
</style>

View File

@ -0,0 +1,131 @@
<template>
<div class="notice-feishu-wrapper" :style="{ height: `${windowHeight - 64}px` }">
<a-form-model ref="feishuForm" :model="feishuData" :label-col="labelCol" :wrapper-col="wrapperCol">
<SpanTitle>基础设置</SpanTitle>
<a-form-model-item label="应用ID">
<a-input v-model="feishuData.id" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item label="应用密码">
<a-input v-model="feishuData.password" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item label="机器人">
<Bot
ref="bot"
:disabled="!isEditable"
:columns="[
{
field: 'name',
title: '名称',
required: true,
},
{
field: 'url',
title: 'Webhook地址',
required: true,
},
]"
/>
</a-form-model-item>
<a-row v-if="isEditable">
<a-col :span="16" :offset="3">
<a-form-model-item :label-col="labelCol" :wrapper-col="wrapperCol">
<a-button type="primary" @click="onSubmit"> 保存 </a-button>
<a-button ghost type="primary" style="margin-left: 28px;" @click="resetForm"> 重置 </a-button>
</a-form-model-item>
</a-col>
</a-row>
</a-form-model>
</div>
</template>
<script>
import { mapState } from 'vuex'
import SpanTitle from '../components/spanTitle.vue'
import { getNoticeConfigByPlatform, postNoticeConfigByPlatform, putNoticeConfigByPlatform } from '@/api/noticeSetting'
import { mixinPermissions } from '@/utils/mixin'
import Bot from './bot.vue'
export default {
name: 'NoticeFeishu',
components: { SpanTitle, Bot },
mixins: [mixinPermissions],
data() {
return {
labelCol: { lg: 3, md: 5, sm: 8 },
wrapperCol: { lg: 15, md: 19, sm: 16 },
id: null,
feishuData: {
id: '',
password: '',
},
}
},
computed: {
...mapState({
windowHeight: (state) => state.windowHeight,
}),
isEditable() {
return this.hasDetailPermission('backend', '通知设置', ['update'])
},
},
mounted() {
this.getData()
},
methods: {
getData() {
getNoticeConfigByPlatform({ platform: 'feishuApp' }).then((res) => {
this.id = res?.id ?? null
if (this.id) {
this.feishuData = res.info
this.$refs.bot.setData(res?.info?.bot)
}
})
},
onSubmit() {
this.$refs.feishuForm.validate(async (valid) => {
if (valid) {
this.$refs.bot.getData(async (flag, bot) => {
if (flag) {
if (this.id) {
await putNoticeConfigByPlatform(this.id, { info: { ...this.feishuData, bot, label: '飞书' } })
} else {
await postNoticeConfigByPlatform({
platform: 'feishuApp',
info: { ...this.feishuData, bot, label: '飞书' },
})
}
this.$message.success('保存成功')
this.getData()
}
})
}
})
},
resetForm() {
this.feishuData = {
id: '',
password: '',
}
},
},
}
</script>
<style lang="less" scoped>
.notice-feishu-wrapper {
background-color: #fff;
padding-top: 15px;
overflow: auto;
margin-bottom: -24px;
border-radius: 15px;
.notice-feishu-wrapper-tips {
display: inline-block;
background-color: #ffdfdf;
border-radius: 4px;
padding: 0 12px;
width: 300px;
color: #000000;
margin-top: 8px;
}
}
</style>

View File

@ -0,0 +1,135 @@
<template>
<div class="notice-wx-wrapper" :style="{ height: `${windowHeight - 64}px` }">
<a-form-model ref="wxForm" :model="wxData" :label-col="labelCol" :wrapper-col="wrapperCol">
<SpanTitle>基础设置</SpanTitle>
<a-form-model-item label="企业ID">
<a-input v-model="wxData.corpid" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item label="自建应用ID">
<a-input v-model="wxData.agentid" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item label="自建应用密码">
<a-input-password v-model="wxData.corpsecret" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item label="ITSM AppId">
<a-input v-model="wxData.itsm_app_id" :disabled="!isEditable" />
</a-form-model-item>
<a-form-model-item label="机器人">
<Bot ref="bot" :disabled="!isEditable" />
</a-form-model-item>
<!-- <a-form-model-item label="测试邮件设置">
<a-button type="primary" ghost>测试回收箱</a-button>
<br />
<span
class="notice-wx-wrapper-tips"
><ops-icon type="icon-shidi-quxiao" :style="{ color: '#D81E06' }" /> 邮件接收失败</span
>
<br />
<span>邮箱服务器未配置请配置一个邮箱服务器 | <a>故障诊断</a></span>
</a-form-model-item> -->
<a-row v-if="isEditable">
<a-col :span="16" :offset="3">
<a-form-model-item :label-col="labelCol" :wrapper-col="wrapperCol">
<a-button type="primary" @click="onSubmit"> 保存 </a-button>
<a-button ghost type="primary" style="margin-left: 28px;" @click="resetForm"> 重置 </a-button>
</a-form-model-item>
</a-col>
</a-row>
</a-form-model>
</div>
</template>
<script>
import { mapState } from 'vuex'
import SpanTitle from '../components/spanTitle.vue'
import { getNoticeConfigByPlatform, postNoticeConfigByPlatform, putNoticeConfigByPlatform } from '@/api/noticeSetting'
import { mixinPermissions } from '@/utils/mixin'
import Bot from './bot.vue'
export default {
name: 'NoticeWx',
mixins: [mixinPermissions],
components: { SpanTitle, Bot },
data() {
return {
labelCol: { lg: 3, md: 5, sm: 8 },
wrapperCol: { lg: 15, md: 19, sm: 16 },
id: null,
wxData: {
corpid: '',
agentid: '',
corpsecret: '',
itsm_app_id: '',
},
}
},
computed: {
...mapState({
windowHeight: (state) => state.windowHeight,
}),
isEditable() {
return this.hasDetailPermission('backend', '通知设置', ['update'])
},
},
mounted() {
this.getData()
},
methods: {
getData() {
getNoticeConfigByPlatform({ platform: 'wechatApp' }).then((res) => {
this.id = res?.id ?? null
if (this.id) {
this.wxData = res.info
this.$refs.bot.setData(res?.info?.bot)
}
})
},
onSubmit() {
this.$refs.wxForm.validate(async (valid) => {
if (valid) {
this.$refs.bot.getData(async (flag, bot) => {
if (flag) {
if (this.id) {
await putNoticeConfigByPlatform(this.id, { info: { ...this.wxData, bot, label: '企业微信' } })
} else {
await postNoticeConfigByPlatform({
platform: 'wechatApp',
info: { ...this.wxData, bot, label: '企业微信' },
})
}
this.$message.success('保存成功')
this.getData()
}
})
}
})
},
resetForm() {
this.wxData = {
corpid: '',
agentid: '',
corpsecret: '',
itsm_app_id: '',
}
},
},
}
</script>
<style lang="less" scoped>
.notice-wx-wrapper {
background-color: #fff;
padding-top: 15px;
overflow: auto;
margin-bottom: -24px;
border-radius: 15px;
.notice-wx-wrapper-tips {
display: inline-block;
background-color: #ffdfdf;
border-radius: 4px;
padding: 0 12px;
width: 300px;
color: #000000;
margin-top: 8px;
}
}
</style>

View File

@ -90,12 +90,39 @@
</a-form-model-item>
<a-form-model-item label="绑定信息">
<a-space>
<div :class="{ 'setting-person-bind': true, 'setting-person-bind-existed': form.wx_id }">
<a-tooltip title="企业微信">
<div
@click="handleBind('wechatApp', form.notice_info && form.notice_info.wechatApp)"
:class="{
'setting-person-bind': true,
'setting-person-bind-existed': form.notice_info && form.notice_info.wechatApp,
}"
>
<ops-icon type="ops-setting-notice-wx" />
</div>
<div @click="handleBindWx" class="setting-person-bind-button">
{{ form.notice_info && form.notice_info.wechatApp ? '重新绑定' : '绑定' }}
</a-tooltip>
<a-tooltip title="飞书">
<div
@click="handleBind('feishuApp', form.notice_info && form.notice_info.feishuApp)"
:class="{
'setting-person-bind': true,
'setting-person-bind-existed': form.notice_info && form.notice_info.feishuApp,
}"
>
<ops-icon type="ops-setting-notice-feishu" />
</div>
</a-tooltip>
<a-tooltip title="钉钉">
<div
@click="handleBind('dingdingApp', form.notice_info && form.notice_info.dingdingApp)"
:class="{
'setting-person-bind': true,
'setting-person-bind-existed': form.notice_info && form.notice_info.dingdingApp,
}"
>
<ops-icon type="ops-setting-notice-dingding" />
</div>
</a-tooltip>
</a-space>
</a-form-model-item>
</div>
@ -116,7 +143,6 @@
</div>
<EditImage
v-if="showEditImage"
:fixed-number="eidtImageOption.fixedNumber"
:show="showEditImage"
:image="editImage"
:title="eidtImageOption.title"
@ -140,7 +166,8 @@ import {
getEmployeeByUid,
updateEmployeeByUid,
updatePasswordByUid,
bindWxByUid,
bindPlatformByUid,
unbindPlatformByUid,
} from '@/api/employee'
import { getDepartmentName, getDirectorName } from '@/utils/util'
import EditImage from '../components/EditImage.vue'
@ -267,13 +294,30 @@ export default {
}
})
},
async handleBindWx() {
async handleBind(platform, isBind) {
if (isBind) {
const that = this
this.$confirm({
title: '警告',
content: `确认解绑`,
onOk() {
unbindPlatformByUid(platform, that.uid)
.then(() => {
that.$message.success('解绑成功!')
})
.finally(() => {
that.getEmployeeByUid()
that.GetInfo()
})
},
})
} else {
await this.$refs.personForm.validate(async (valid) => {
if (valid) {
const { nickname, mobile, sex, avatar } = this.form
const params = { nickname, mobile, sex, avatar }
await updateEmployeeByUid(this.uid, params)
bindWxByUid(this.uid)
bindPlatformByUid(platform, this.uid)
.then(() => {
this.$message.success('绑定成功!')
})
@ -283,6 +327,7 @@ export default {
})
}
})
}
},
},
}
@ -342,19 +387,11 @@ export default {
color: #fff;
font-size: 30px;
text-align: center;
cursor: pointer;
}
.setting-person-bind-existed {
background: #008cee;
}
.setting-person-bind-button {
height: 40px;
width: 72px;
background: #f0f5ff;
border-radius: 4px;
padding: 0 8px;
text-align: center;
cursor: pointer;
}
}
}
</style>