前端更新 (#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;"> <div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box"> <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"> <li class="dib">
<span class="icon iconfont">&#xe886;</span> <span class="icon iconfont">&#xe886;</span>
<div class="name">cmdb-histogram</div> <div class="name">cmdb-histogram</div>
@ -2100,6 +2142,12 @@
<div class="code-name">&amp;#xe738;</div> <div class="code-name">&amp;#xe738;</div>
</li> </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"> <li class="dib">
<span class="icon iconfont">&#xe72f;</span> <span class="icon iconfont">&#xe72f;</span>
<div class="name">ops-setting-notice</div> <div class="name">ops-setting-notice</div>
@ -3954,9 +4002,9 @@
<pre><code class="language-css" <pre><code class="language-css"
>@font-face { >@font-face {
font-family: 'iconfont'; font-family: 'iconfont';
src: url('iconfont.woff2?t=1694508259411') format('woff2'), src: url('iconfont.woff2?t=1696815443987') format('woff2'),
url('iconfont.woff?t=1694508259411') format('woff'), url('iconfont.woff?t=1696815443987') format('woff'),
url('iconfont.ttf?t=1694508259411') format('truetype'); url('iconfont.ttf?t=1696815443987') format('truetype');
} }
</code></pre> </code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3> <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@ -3982,6 +4030,69 @@
<div class="content font-class"> <div class="content font-class">
<ul class="icon_lists dib-box"> <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"> <li class="dib">
<span class="icon iconfont cmdb-bar"></span> <span class="icon iconfont cmdb-bar"></span>
<div class="name"> <div class="name">
@ -7051,6 +7162,15 @@
</div> </div>
</li> </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"> <li class="dib">
<span class="icon iconfont ops-setting-notice"></span> <span class="icon iconfont ops-setting-notice"></span>
<div class="name"> <div class="name">
@ -9832,6 +9952,62 @@
<div class="content symbol"> <div class="content symbol">
<ul class="icon_lists dib-box"> <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"> <li class="dib">
<svg class="icon svg-icon" aria-hidden="true"> <svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#cmdb-bar"></use> <use xlink:href="#cmdb-bar"></use>
@ -12560,6 +12736,14 @@
<div class="code-name">#ops-dot</div> <div class="code-name">#ops-dot</div>
</li> </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"> <li class="dib">
<svg class="icon svg-icon" aria-hidden="true"> <svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#ops-setting-notice"></use> <use xlink:href="#ops-setting-notice"></use>

View File

@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 3857903 */ font-family: "iconfont"; /* Project id 3857903 */
src: url('iconfont.woff2?t=1694508259411') format('woff2'), src: url('iconfont.woff2?t=1696815443987') format('woff2'),
url('iconfont.woff?t=1694508259411') format('woff'), url('iconfont.woff?t=1696815443987') format('woff'),
url('iconfont.ttf?t=1694508259411') format('truetype'); url('iconfont.ttf?t=1696815443987') format('truetype');
} }
.iconfont { .iconfont {
@ -13,6 +13,34 @@
-moz-osx-font-smoothing: grayscale; -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 { .cmdb-bar:before {
content: "\e886"; content: "\e886";
} }
@ -1377,6 +1405,10 @@
content: "\e738"; content: "\e738";
} }
.ops-setting-notice-email-selected-copy:before {
content: "\e889";
}
.ops-setting-notice:before { .ops-setting-notice:before {
content: "\e72f"; content: "\e72f";
} }

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,55 @@
"css_prefix_text": "", "css_prefix_text": "",
"description": "", "description": "",
"glyphs": [ "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", "icon_id": "37334642",
"name": "cmdb-histogram", "name": "cmdb-histogram",
@ -2392,6 +2441,13 @@
"unicode": "e738", "unicode": "e738",
"unicode_decimal": 59192 "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", "icon_id": "34108346",
"name": "ops-setting-notice", "name": "ops-setting-notice",

Binary file not shown.

View File

@ -1,127 +1,134 @@
import { axios } from '@/utils/request' import { axios } from '@/utils/request'
export function getEmployeeList(params) { export function getEmployeeList(params) {
return axios({ return axios({
url: '/common-setting/v1/employee', url: '/common-setting/v1/employee',
method: 'get', method: 'get',
params: params, params: params,
}) })
} }
// export function getEmployeeList(params, orderBy) { // export function getEmployeeList(params, orderBy) {
// return axios({ // return axios({
// url: '/common-setting/v1/employee' + '/' + orderBy, // url: '/common-setting/v1/employee' + '/' + orderBy,
// method: 'get', // method: 'get',
// params: params, // params: params,
// }) // })
// } // }
export function postEmployee(data) { export function postEmployee(data) {
return axios({ return axios({
url: '/common-setting/v1/employee', url: '/common-setting/v1/employee',
method: 'post', method: 'post',
data: data, data: data,
}) })
} }
export function getEmployeeCount(params) { export function getEmployeeCount(params) {
return axios({ return axios({
url: '/common-setting/v1/employee/count', url: '/common-setting/v1/employee/count',
method: 'get', method: 'get',
params: params, params: params,
}) })
} }
export function deleteEmployee(_id) { export function deleteEmployee(_id) {
return axios({ return axios({
url: `/common-setting/v1/employee/${_id}`, url: `/common-setting/v1/employee/${_id}`,
method: 'delete', method: 'delete',
}) })
} }
export function putEmployee(_id, data) { export function putEmployee(_id, data) {
return axios({ return axios({
url: `/common-setting/v1/employee/${_id}`, url: `/common-setting/v1/employee/${_id}`,
method: 'put', method: 'put',
data: data, data: data,
}) })
} }
export function batchEditEmployee(data) { export function batchEditEmployee(data) {
return axios({ return axios({
url: '/common-setting/v1/employee/batch', url: '/common-setting/v1/employee/batch',
method: 'post', method: 'post',
data: data, data: data,
}) })
} }
export function importEmployee(data) { export function importEmployee(data) {
return axios({ return axios({
url: '/common-setting/v1/employee/import', url: '/common-setting/v1/employee/import',
method: 'post', method: 'post',
data data
}) })
} }
export function getEmployeeByUid(uid) { export function getEmployeeByUid(uid) {
return axios({ return axios({
url: `/common-setting/v1/employee/by_uid/${uid}`, url: `/common-setting/v1/employee/by_uid/${uid}`,
method: 'get', method: 'get',
}) })
} }
export function updateEmployeeByUid(uid, data) { export function updateEmployeeByUid(uid, data) {
return axios({ return axios({
url: `/common-setting/v1/employee/by_uid/${uid}`, url: `/common-setting/v1/employee/by_uid/${uid}`,
method: 'put', method: 'put',
data data
}) })
} }
export function updatePasswordByUid(uid, data) { export function updatePasswordByUid(uid, data) {
return axios({ return axios({
url: `/common-setting/v1/employee/by_uid/change_password/${uid}`, url: `/common-setting/v1/employee/by_uid/change_password/${uid}`,
method: 'put', method: 'put',
data data
}) })
} }
export function bindWxByUid(uid) { export function bindPlatformByUid(platform, uid) {
return axios({ 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', method: 'put',
}) })
} }
export function getAllPosition() { export function unbindPlatformByUid(platform, uid) {
return axios({ return axios({
url: `/common-setting/v1/employee/position`, url: `/common-setting/v1/employee/by_uid/bind_notice/${platform}/${uid}`,
method: 'get', method: 'delete',
}) })
} }
export function getEmployeeByEmployeeId(employee_id) { export function getAllPosition() {
return axios({ return axios({
url: `/common-setting/v1/employee/${employee_id}`, url: `/common-setting/v1/employee/position`,
method: 'get', method: 'get',
}) })
} }
// 下载员工列表 export function getEmployeeByEmployeeId(employee_id) {
export function downloadAllEmployee(params) { return axios({
return axios({ url: `/common-setting/v1/employee/${employee_id}`,
url: `/common-setting/v1/employee/export_all`, method: 'get',
method: 'get', })
params, }
responseType: 'blob'
}) // 下载员工列表
} export function downloadAllEmployee(params) {
return axios({
export function getEmployeeListByFilter(data) { url: `/common-setting/v1/employee/export_all`,
return axios({ method: 'get',
url: '/common-setting/v1/employee/filter', params,
method: 'post', responseType: 'blob'
data })
}) }
}
export function getEmployeeListByFilter(data) {
export function getNoticeByEmployeeIds(data) { return axios({
return axios({ url: '/common-setting/v1/employee/filter',
url: '/common-setting/v1/employee/get_notice_by_ids', method: 'post',
method: 'post', data
data })
}) }
}
export function getNoticeByEmployeeIds(data) {
return axios({
url: '/common-setting/v1/employee/get_notice_by_ids',
method: 'post',
data
})
}

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

@ -1,285 +1,293 @@
<template> <template>
<div> <div>
<a-space :style="{ display: 'flex', marginBottom: '10px' }" v-for="(item, index) in ruleList" :key="item.id"> <a-space :style="{ display: 'flex', marginBottom: '10px' }" v-for="(item, index) in ruleList" :key="item.id">
<div :style="{ width: '50px', height: '24px', position: 'relative' }"> <div :style="{ width: '50px', height: '24px', position: 'relative' }">
<treeselect <treeselect
v-if="index" v-if="index"
class="custom-treeselect" class="custom-treeselect"
:style="{ width: '50px', '--custom-height': '24px', position: 'absolute', top: '-17px', left: 0 }" :style="{ width: '50px', '--custom-height': '24px', position: 'absolute', top: '-17px', left: 0 }"
v-model="item.type" v-model="item.type"
:multiple="false" :multiple="false"
:clearable="false" :clearable="false"
searchable searchable
:options="ruleTypeList" :options="ruleTypeList"
:normalizer=" :normalizer="
(node) => { (node) => {
return { return {
id: node.value, id: node.value,
label: node.label, label: node.label,
children: node.children, children: node.children,
} }
} }
" "
> >
</treeselect> </treeselect>
</div> </div>
<treeselect <treeselect
class="custom-treeselect" class="custom-treeselect"
:style="{ width: '130px', '--custom-height': '24px' }" :style="{ width: '130px', '--custom-height': '24px' }"
v-model="item.property" v-model="item.property"
:multiple="false" :multiple="false"
:clearable="false" :clearable="false"
searchable searchable
:options="canSearchPreferenceAttrList" :options="canSearchPreferenceAttrList"
:normalizer=" :normalizer="
(node) => { (node) => {
return { return {
id: node.name, id: node.name,
label: node.alias || node.name, label: node.alias || node.name,
children: node.children, children: node.children,
} }
} }
" "
> appendToBody
<div :zIndex="1050"
:title="node.label" >
slot="option-label" <div
slot-scope="{ node }" :title="node.label"
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }" slot="option-label"
> slot-scope="{ node }"
<ValueTypeMapIcon :attr="node.raw" /> :style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
{{ node.label }} >
</div> <ValueTypeMapIcon :attr="node.raw" />
<div {{ node.label }}
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }" </div>
slot="value-label" <div
slot-scope="{ node }" :style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
> slot="value-label"
<ValueTypeMapIcon :attr="node.raw" /> {{ node.label }} slot-scope="{ node }"
</div> >
</treeselect> <ValueTypeMapIcon :attr="node.raw" /> {{ node.label }}
<treeselect </div>
class="custom-treeselect" </treeselect>
:style="{ width: '100px', '--custom-height': '24px' }" <treeselect
v-model="item.exp" class="custom-treeselect"
:multiple="false" :style="{ width: '100px', '--custom-height': '24px' }"
:clearable="false" v-model="item.exp"
searchable :multiple="false"
:options="[...getExpListByProperty(item.property), ...advancedExpList]" :clearable="false"
:normalizer=" searchable
(node) => { :options="[...getExpListByProperty(item.property), ...advancedExpList]"
return { :normalizer="
id: node.value, (node) => {
label: node.label, return {
children: node.children, id: node.value,
} label: node.label,
} children: node.children,
" }
@select="(value) => handleChangeExp(value, item, index)" }
> "
</treeselect> @select="(value) => handleChangeExp(value, item, index)"
<treeselect appendToBody
class="custom-treeselect" :zIndex="1050"
:style="{ width: '175px', '--custom-height': '24px' }" >
v-model="item.value" </treeselect>
:multiple="false" <treeselect
:clearable="false" class="custom-treeselect"
searchable :style="{ width: '175px', '--custom-height': '24px' }"
v-if="isChoiceByProperty(item.property) && (item.exp === 'is' || item.exp === '~is')" v-model="item.value"
:options="getChoiceValueByProperty(item.property)" :multiple="false"
placeholder="请选择" :clearable="false"
:normalizer=" searchable
(node) => { v-if="isChoiceByProperty(item.property) && (item.exp === 'is' || item.exp === '~is')"
return { :options="getChoiceValueByProperty(item.property)"
id: node[0], placeholder="请选择"
label: node[0], :normalizer="
children: node.children, (node) => {
} return {
} id: node[0],
" label: node[0],
> children: node.children,
<div }
:title="node.label" }
slot="option-label" "
slot-scope="{ node }" appendToBody
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }" :zIndex="1050"
> >
{{ node.label }} <div
</div> :title="node.label"
</treeselect> slot="option-label"
<a-input-group slot-scope="{ node }"
size="small" :style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
compact >
v-else-if="item.exp === 'range' || item.exp === '~range'" {{ node.label }}
:style="{ width: '175px' }" </div>
> </treeselect>
<a-input class="ops-input" size="small" v-model="item.min" :style="{ width: '78px' }" placeholder="最小值" /> <a-input-group
~ size="small"
<a-input class="ops-input" size="small" v-model="item.max" :style="{ width: '78px' }" placeholder="最大值" /> compact
</a-input-group> v-else-if="item.exp === 'range' || item.exp === '~range'"
<a-input-group size="small" compact v-else-if="item.exp === 'compare'" :style="{ width: '175px' }"> :style="{ width: '175px' }"
<treeselect >
class="custom-treeselect" <a-input class="ops-input" size="small" v-model="item.min" :style="{ width: '78px' }" placeholder="最小值" />
:style="{ width: '60px', '--custom-height': '24px' }" ~
v-model="item.compareType" <a-input class="ops-input" size="small" v-model="item.max" :style="{ width: '78px' }" placeholder="最大值" />
:multiple="false" </a-input-group>
:clearable="false" <a-input-group size="small" compact v-else-if="item.exp === 'compare'" :style="{ width: '175px' }">
searchable <treeselect
:options="compareTypeList" class="custom-treeselect"
:normalizer=" :style="{ width: '60px', '--custom-height': '24px' }"
(node) => { v-model="item.compareType"
return { :multiple="false"
id: node.value, :clearable="false"
label: node.label, searchable
children: node.children, :options="compareTypeList"
} :normalizer="
} (node) => {
" return {
> id: node.value,
</treeselect> label: node.label,
<a-input class="ops-input" v-model="item.value" size="small" style="width: 113px" /> children: node.children,
</a-input-group> }
<a-input }
v-else-if="item.exp !== 'value' && item.exp !== '~value'" "
size="small" appendToBody
v-model="item.value" :zIndex="1050"
:placeholder="item.exp === 'in' || item.exp === '~in' ? '以 ; 分隔' : ''" >
class="ops-input" </treeselect>
></a-input> <a-input class="ops-input" v-model="item.value" size="small" style="width: 113px" />
<div v-else :style="{ width: '175px' }"></div> </a-input-group>
<a-tooltip title="复制"> <a-input
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="icon-xianxing-copy"/></a> v-else-if="item.exp !== 'value' && item.exp !== '~value'"
</a-tooltip> size="small"
<a-tooltip title="删除"> v-model="item.value"
<a class="operation" @click="handleDeleteRule(item)"><ops-icon type="icon-xianxing-delete"/></a> :placeholder="item.exp === 'in' || item.exp === '~in' ? '以 ; 分隔' : ''"
</a-tooltip> class="ops-input"
</a-space> ></a-input>
<div class="table-filter-add"> <div v-else :style="{ width: '175px' }"></div>
<a @click="handleAddRule">+ 新增</a> <a-tooltip title="复制">
</div> <a class="operation" @click="handleCopyRule(item)"><ops-icon type="icon-xianxing-copy"/></a>
</div> </a-tooltip>
</template> <a-tooltip title="删除">
<a class="operation" @click="handleDeleteRule(item)"><ops-icon type="icon-xianxing-delete"/></a>
<script> </a-tooltip>
import _ from 'lodash' </a-space>
import { v4 as uuidv4 } from 'uuid' <div class="table-filter-add">
import { ruleTypeList, expList, advancedExpList, compareTypeList } from './constants' <a @click="handleAddRule">+ 新增</a>
import ValueTypeMapIcon from '../CMDBValueTypeMapIcon' </div>
</div>
export default { </template>
name: 'Expression',
components: { ValueTypeMapIcon }, <script>
model: { import _ from 'lodash'
prop: 'value', import { v4 as uuidv4 } from 'uuid'
event: 'change', import { ruleTypeList, expList, advancedExpList, compareTypeList } from './constants'
}, import ValueTypeMapIcon from '../CMDBValueTypeMapIcon'
props: {
value: { export default {
type: Array, name: 'Expression',
default: () => [], components: { ValueTypeMapIcon },
}, model: {
canSearchPreferenceAttrList: { prop: 'value',
type: Array, event: 'change',
required: true, },
default: () => [], props: {
}, value: {
}, type: Array,
data() { default: () => [],
return { },
ruleTypeList, canSearchPreferenceAttrList: {
expList, type: Array,
advancedExpList, required: true,
compareTypeList, default: () => [],
} },
}, },
computed: { data() {
ruleList: { return {
get() { ruleTypeList,
return this.value expList,
}, advancedExpList,
set(val) { compareTypeList,
this.$emit('change', val) }
return val },
}, computed: {
}, ruleList: {
}, get() {
methods: { return this.value
getExpListByProperty(property) { },
if (property) { set(val) {
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property) this.$emit('change', val)
if (_find && ['0', '1', '3', '4', '5'].includes(_find.value_type)) { return val
return [ },
{ value: 'is', label: '等于' }, },
{ value: '~is', label: '不等于' }, },
{ value: '~value', label: '为空' }, // 为空的定义有点绕 methods: {
{ value: 'value', label: '不为空' }, getExpListByProperty(property) {
] if (property) {
} const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
return this.expList if (_find && ['0', '1', '3', '4', '5'].includes(_find.value_type)) {
} return [
return this.expList { value: 'is', label: '等于' },
}, { value: '~is', label: '不等于' },
isChoiceByProperty(property) { { value: '~value', label: '为空' }, // 为空的定义有点绕
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property) { value: 'value', label: '不为空' },
if (_find) { ]
return _find.is_choice }
} return this.expList
return false }
}, return this.expList
handleAddRule() { },
this.ruleList.push({ isChoiceByProperty(property) {
id: uuidv4(), const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
type: 'and', if (_find) {
property: this.canSearchPreferenceAttrList[0]?.name, return _find.is_choice
exp: 'is', }
value: null, return false
}) },
this.$emit('change', this.ruleList) handleAddRule() {
}, this.ruleList.push({
handleCopyRule(item) { id: uuidv4(),
this.ruleList.push({ ...item, id: uuidv4() }) type: 'and',
this.$emit('change', this.ruleList) property: this.canSearchPreferenceAttrList[0]?.name,
}, exp: 'is',
handleDeleteRule(item) { value: null,
const idx = this.ruleList.findIndex((r) => r.id === item.id) })
if (idx > -1) { this.$emit('change', this.ruleList)
this.ruleList.splice(idx, 1) },
} handleCopyRule(item) {
this.$emit('change', this.ruleList) this.ruleList.push({ ...item, id: uuidv4() })
}, this.$emit('change', this.ruleList)
getChoiceValueByProperty(property) { },
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property) handleDeleteRule(item) {
if (_find) { const idx = this.ruleList.findIndex((r) => r.id === item.id)
return _find.choice_value if (idx > -1) {
} this.ruleList.splice(idx, 1)
return [] }
}, this.$emit('change', this.ruleList)
handleChangeExp({ value }, item, index) { },
const _ruleList = _.cloneDeep(this.ruleList) getChoiceValueByProperty(property) {
if (value === 'range') { const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
_ruleList[index] = { if (_find) {
..._ruleList[index], return _find.choice_value
min: '', }
max: '', return []
exp: value, },
} handleChangeExp({ value }, item, index) {
} else if (value === 'compare') { const _ruleList = _.cloneDeep(this.ruleList)
_ruleList[index] = { if (value === 'range') {
..._ruleList[index], _ruleList[index] = {
compareType: '1', ..._ruleList[index],
exp: value, min: '',
} max: '',
} else { exp: value,
_ruleList[index] = { }
..._ruleList[index], } else if (value === 'compare') {
exp: value, _ruleList[index] = {
} ..._ruleList[index],
} compareType: '1',
this.ruleList = _ruleList exp: value,
this.$emit('change', this.ruleList) }
}, } else {
}, _ruleList[index] = {
} ..._ruleList[index],
</script> exp: value,
}
<style></style> }
this.ruleList = _ruleList
this.$emit('change', this.ruleList)
},
},
}
</script>
<style></style>

View File

@ -83,10 +83,10 @@ export default {
getContent() { getContent() {
const html = _.cloneDeep(this.editor.getHtml()) const html = _.cloneDeep(this.editor.getHtml())
const _html = html.replace( 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) => { (value) => {
const _match = value.match(/(?<=data-attachmentValue=").*?(?=")/) const _match = value.match(/(?<=data-attachment(V|v)alue=").*?(?=")/)
return `{{${_match}}}` return `{{${_match[0]}}}`
} }
) )
return { body_html: html, body: _html } return { body_html: html, body: _html }

View File

@ -59,7 +59,7 @@ export default {
] ]
return { return {
segmentedContentTypes, segmentedContentTypes,
// contentType: 'none', // contentType: 'none',
jsonData: {}, jsonData: {},
} }
}, },
@ -74,6 +74,9 @@ export default {
} }
div.jsoneditor { div.jsoneditor {
border-color: #f3f4f6; border-color: #f3f4f6;
.jsoneditor-outer {
border-color: #f3f4f6;
}
} }
} }
</style> </style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,195 +1,362 @@
<template> <template>
<a-tabs id="preValueArea" v-model="activeKey" size="small" :tabBarStyle="{ borderBottom: 'none' }"> <a-tabs id="preValueArea" v-model="activeKey" size="small" :tabBarStyle="{ borderBottom: 'none' }">
<a-tab-pane key="define" :disabled="disabled"> <a-tab-pane key="define" :disabled="disabled">
<span style="font-size:12px;" slot="tab">定义</span> <span style="font-size:12px;" slot="tab">定义</span>
<PreValueTag type="add" :item="[]" @add="addNewValue" :disabled="disabled"> <PreValueTag type="add" :item="[]" @add="addNewValue" :disabled="disabled">
<template #default> <template #default>
<a-button <a-button
:style="{ marginBottom: '10px', fontSize: '12px', padding: '1px 7px' }" :style="{ marginBottom: '10px', fontSize: '12px', padding: '1px 7px' }"
type="primary" type="primary"
ghost ghost
:disabled="disabled" :disabled="disabled"
size="small" size="small"
> >
<a-icon type="plus" />添加</a-button <a-icon type="plus" />添加</a-button
> >
</template> </template>
</PreValueTag> </PreValueTag>
<draggable :list="valueList" handle=".handle" :disabled="disabled"> <draggable :list="valueList" handle=".handle" :disabled="disabled">
<PreValueTag <PreValueTag
:disabled="disabled" :disabled="disabled"
v-for="(item, index) in valueList" v-for="(item, index) in valueList"
:key="`${item[0]}_${index}`" :key="`${item[0]}_${index}`"
:item="item" :item="item"
@deleteValue="deleteValue" @deleteValue="deleteValue"
@editValue="editValue" @editValue="editValue"
/> />
</draggable> </draggable>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="webhook" :disabled="disabled"> <a-tab-pane key="webhook" :disabled="disabled">
<span style="font-size:12px;" slot="tab">Webhook</span> <span style="font-size:12px;" slot="tab">Webhook</span>
<a-form-model :model="form"> <Webhook ref="webhook" style="margin-top:10px" />
<a-row :gutter="24"> <a-form-model :model="form">
<a-col :span="24"> <a-col :span="24">
<a-form-model-item label="地址" prop="url" :labelCol="{ span: 3 }" :wrapperCol="{ span: 16 }"> <a-form-model-item prop="ret_key" :labelCol="{ span: 3 }" :wrapperCol="{ span: 18 }">
<a-input v-model="form.url" :disabled="disabled"> <template slot="label">
<a-select <span
:showArrow="false" style="position:relative;white-space:pre;"
slot="addonBefore" >{{ `过滤` }}
style="width:60px;" <a-tooltip
v-model="form.method" title="返回的结果按字段来过滤,层级嵌套用##分隔比如k1##k2web请求返回{k1: [{k2: 1}, {k2: 2}]}, 解析结果为[1, 2]"
:disabled="disabled" >
> <a-icon
<a-select-option value="get"> style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
GET type="question-circle"
</a-select-option> theme="filled"
<a-select-option value="post"> />
POST </a-tooltip>
</a-select-option> </span>
<a-select-option value="put"> </template>
PUT <a-input style="width:150px;" v-model="form.ret_key" placeholder="k1##k2" :disabled="disabled" />
</a-select-option> </a-form-model-item>
</a-select> </a-col>
</a-input> </a-form-model>
</a-form-model-item> </a-tab-pane>
</a-col> <a-tab-pane key="choice_other" :disabled="disabled">
</a-row> <span style="font-size:12px;" slot="tab">其他模型属性</span>
<a-col :span="24"> <a-row :gutter="[24, 24]">
<a-form-model-item prop="ret_key" :labelCol="{ span: 3 }" :wrapperCol="{ span: 18 }"> <a-col :span="12">
<template slot="label"> <a-form-item
<span :style="{ lineHeight: '24px', marginBottom: '5px' }"
style="position:relative;white-space:pre;" label="模型"
>{{ `过滤` }} :label-col="{ span: 4 }"
<a-tooltip :wrapper-col="{ span: 20 }"
title="返回的结果按字段来过滤,层级嵌套用##分隔比如k1##k2web请求返回{k1: [{k2: 1}, {k2: 2}]}, 解析结果为[1, 2]" >
> <treeselect
<a-icon :disable-branch-nodes="true"
style="position:absolute;top:3px;left:-17px;color:#2f54eb;" :class="{
type="question-circle" 'custom-treeselect': true,
theme="filled" 'custom-treeselect-bgcAndBorder': true,
/> }"
</a-tooltip> :style="{
</span> '--custom-height': '32px',
</template> lineHeight: '32px',
<a-input style="width:150px;" v-model="form.ret_key" placeholder="k1##k2" :disabled="disabled" /> '--custom-bg-color': '#fff',
</a-form-model-item> '--custom-border': '1px solid #d9d9d9',
</a-col> '--custom-multiple-lineHeight': '14px',
</a-form-model> }"
</a-tab-pane> v-model="choice_other.type_ids"
</a-tabs> :multiple="true"
</template> :clearable="true"
searchable
<script> :options="ciTypeGroup"
import _ from 'lodash' value-consists-of="LEAF_PRIORITY"
import draggable from 'vuedraggable' placeholder="请选择CMDB模型"
import PreValueTag from './preValueTag.vue' :normalizer="
import { defautValueColor } from '../../utils/const' (node) => {
import ColorPicker from '../../components/colorPicker/index.vue' return {
id: node.id || -1,
export default { label: node.alias || node.name || '其他',
name: 'PreValueArea', title: node.alias || node.name || '其他',
components: { draggable, PreValueTag, ColorPicker }, children: node.ci_types,
props: { }
disabled: { }
type: Boolean, "
default: true, appendToBody
}, :zIndex="1050"
}, @select="
data() { () => {
return { choice_other.attr_id = undefined
defautValueColor, }
activeKey: 'define', // define webhook "
valueList: [], >
form: { <div
url: '', :title="node.label"
method: 'get', slot="option-label"
ret_key: '', slot-scope="{ node }"
}, :style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
} >
}, {{ node.label }}
watch: { </div>
disabled: { </treeselect>
immediate: false, </a-form-item>
handler(newValue) { </a-col>
const dom = document.querySelector('#preValueArea .ant-tabs-ink-bar') <a-col :span="12" v-if="choice_other.type_ids && choice_other.type_ids.length">
if (newValue) { <a-form-item
// 如果是disabled 把tab 的ink-bar也置灰 :style="{ marginBottom: '5px' }"
dom.style.backgroundColor = '#00000040' label="属性"
} else { :label-col="{ span: 4 }"
dom.style.backgroundColor = '#2f54eb' :wrapper-col="{ span: 20 }"
} >
}, <treeselect
}, :disable-branch-nodes="true"
}, class="ops-setting-treeselect"
methods: { v-model="choice_other.attr_id"
addNewValue(newValue, newStyle, newIcon) { :multiple="false"
if (newValue) { :clearable="true"
const idx = this.valueList.findIndex((v) => v[0] === newValue) searchable
if (idx > -1) { :options="typeAttrs"
this.$message.warning('当前值已存在!') value-consists-of="LEAF_PRIORITY"
} else { placeholder="请选择模型属性"
this.valueList.push([newValue, { style: newStyle, icon: { ...newIcon } }]) :normalizer="
} (node) => {
} return {
}, id: node.id || -1,
deleteValue(item) { label: node.alias || node.name || '其他',
const _valueList = _.cloneDeep(this.valueList) title: node.alias || node.name || '其他',
const idx = _valueList.findIndex((v) => v[0] === item[0]) }
if (idx > -1) { }
_valueList.splice(idx, 1) "
this.valueList = _valueList appendToBody
} :zIndex="1050"
}, >
editValue(item, newValue, newStyle, newIcon) { <div
const _valueList = _.cloneDeep(this.valueList) :title="node.label"
const idx = _valueList.findIndex((v) => v[0] === item[0]) slot="option-label"
if (idx > -1) { slot-scope="{ node }"
_valueList[idx] = [newValue, { style: newStyle, icon: { ...newIcon } }] :style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
this.valueList = _valueList >
} {{ node.label }}
}, </div>
getData() { </treeselect>
if (this.activeKey === 'define') { </a-form-item>
return { </a-col>
choice_value: this.valueList, <a-col :span="24" v-if="choice_other.type_ids && choice_other.type_ids.length">
choice_web_hook: null, <a-form-item
} :style="{ marginBottom: '5px' }"
} else { class="pre-value-filter"
return { choice_value: [], choice_web_hook: this.form } label="筛选"
} :label-col="{ span: 2 }"
}, :wrapper-col="{ span: 22 }"
setData({ choice_value, choice_web_hook }) { >
if (choice_web_hook) { <FilterComp
this.form = choice_web_hook ref="filterComp"
this.activeKey = 'webhook' :isDropdown="false"
} else { :canSearchPreferenceAttrList="typeAttrs"
this.valueList = choice_value @setExpFromFilter="setExpFromFilter"
this.activeKey = 'define' :expression="filterExp ? `q=${filterExp}` : ''"
} />
const dom = document.querySelector('#preValueArea .ant-tabs-ink-bar') </a-form-item>
if (this.disabled) { </a-col>
// 如果是disabled 把tab 的ink-bar也置灰 </a-row>
dom.style.backgroundColor = '#00000040' </a-tab-pane>
} else { </a-tabs>
dom.style.backgroundColor = '#2f54eb' </template>
}
}, <script>
}, import _ from 'lodash'
} import draggable from 'vuedraggable'
</script> import PreValueTag from './preValueTag.vue'
import { defautValueColor } from '../../utils/const'
<style lang="less" scoped> import ColorPicker from '../../components/colorPicker/index.vue'
.pre-value-edit-color { import Webhook from '../../components/webhook'
display: flex; import { getCITypeGroups } from '../../api/ciTypeGroup'
flex-direction: row; import { getCITypeCommonAttributesByTypeIds } from '../../api/CITypeAttr'
justify-content: space-between; import FilterComp from '@/components/CMDBFilterComp'
flex-wrap: wrap;
.pre-value-edit-color-item { export default {
cursor: pointer; name: 'PreValueArea',
display: inline-block; components: { draggable, PreValueTag, ColorPicker, Webhook, FilterComp },
width: 25px; props: {
height: 20px; disabled: {
margin: 5px; type: Boolean,
} default: true,
} },
</style> },
data() {
return {
defautValueColor,
activeKey: 'define', // define webhook
valueList: [],
form: {
ret_key: '',
},
choice_other: {
type_ids: undefined,
attr_id: undefined,
},
ciTypeGroup: [],
typeAttrs: [],
filterExp: '',
}
},
watch: {
disabled: {
immediate: false,
handler(newValue) {
const dom = document.querySelector('#preValueArea .ant-tabs-ink-bar')
if (newValue) {
// 如果是disabled 把tab 的ink-bar也置灰
dom.style.backgroundColor = '#00000040'
} else {
dom.style.backgroundColor = '#2f54eb'
}
},
},
'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) {
if (newValue) {
const idx = this.valueList.findIndex((v) => v[0] === newValue)
if (idx > -1) {
this.$message.warning('当前值已存在!')
} else {
this.valueList.push([newValue, { style: newStyle, icon: { ...newIcon } }])
}
}
},
deleteValue(item) {
const _valueList = _.cloneDeep(this.valueList)
const idx = _valueList.findIndex((v) => v[0] === item[0])
if (idx > -1) {
_valueList.splice(idx, 1)
this.valueList = _valueList
}
},
editValue(item, newValue, newStyle, newIcon) {
const _valueList = _.cloneDeep(this.valueList)
const idx = _valueList.findIndex((v) => v[0] === item[0])
if (idx > -1) {
_valueList[idx] = [newValue, { style: newStyle, icon: { ...newIcon } }]
this.valueList = _valueList
}
},
getData() {
if (this.activeKey === 'define') {
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 {
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, choice_other }) {
if (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'
}
const dom = document.querySelector('#preValueArea .ant-tabs-ink-bar')
if (this.disabled) {
// 如果是disabled 把tab 的ink-bar也置灰
dom.style.backgroundColor = '#00000040'
} else {
dom.style.backgroundColor = '#2f54eb'
}
},
setExpFromFilter(filterExp) {
if (filterExp) {
this.filterExp = `${filterExp}`
} else {
this.filterExp = ''
}
},
},
}
</script>
<style lang="less" scoped>
.pre-value-edit-color {
display: flex;
flex-direction: row;
justify-content: space-between;
flex-wrap: wrap;
.pre-value-edit-color-item {
cursor: pointer;
display: inline-block;
width: 25px;
height: 20px;
margin: 5px;
}
}
</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>
<a-form-model-item label="通知方式" prop="method"> <a-form-model-item label="通知方式" prop="method">
<a-checkbox-group v-model="notifies.method"> <a-checkbox-group v-model="notifies.method">
<a-checkbox value="wechatApp"> <a-row :style="{ marginTop: '4px' }" :gutter="[0, 12]">
微信 <a-col :span="6">
</a-checkbox> <a-checkbox value="email"> <ops-icon type="email" style="margin-right:5px" />邮件 </a-checkbox>
<a-checkbox value="email"> </a-col>
邮件 <a-col :span="6">
</a-checkbox> <a-checkbox value="wechatApp">
<ops-icon type="wechatApp" style="margin-right:5px" />企业微信
</a-checkbox>
</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-checkbox-group>
</a-form-model-item> </a-form-model-item>
</a-form-model> </a-form-model>
@ -200,6 +262,7 @@ import EmployeeTreeSelect from '@/views/setting/components/employeeTreeSelect.vu
import Webhook from '../../components/webhook' import Webhook from '../../components/webhook'
import NoticeContent from '../../components/noticeContent' import NoticeContent from '../../components/noticeContent'
import { getNoticeByEmployeeIds } from '@/api/employee' import { getNoticeByEmployeeIds } from '@/api/employee'
import { getNoticeConfigAppBot } from '@/api/noticeSetting'
export default { export default {
name: 'TriggerForm', name: 'TriggerForm',
@ -260,6 +323,8 @@ export default {
isShow: false, isShow: false,
dag_id: null, dag_id: null,
showCustomEmail: false, showCustomEmail: false,
appBot: [],
selectedBot: undefined,
} }
}, },
computed: { computed: {
@ -286,9 +351,15 @@ export default {
this.dags = res.map((dag) => ({ id: dag[1], label: dag[0] })) this.dags = res.map((dag) => ({ id: dag[1], label: dag[0] }))
}) })
}, },
async getNoticeConfigAppBot() {
await getNoticeConfigAppBot().then((res) => {
this.appBot = res
})
},
createFromTriggerTable(attrList) { createFromTriggerTable(attrList) {
this.visible = true this.visible = true
// this.getDags() this.getDags()
this.getNoticeConfigAppBot()
this.attrList = attrList this.attrList = attrList
this.triggerId = null this.triggerId = null
this.title = '新增触发器' this.title = '新增触发器'
@ -307,7 +378,8 @@ export default {
}, },
async open(property, attrList) { async open(property, attrList) {
this.visible = true this.visible = true
// await this.getDags() await this.getDags()
this.getNoticeConfigAppBot()
this.attrList = attrList this.attrList = attrList
if (property.has_trigger) { if (property.has_trigger) {
this.triggerId = property.trigger.id this.triggerId = property.trigger.id
@ -348,7 +420,7 @@ export default {
const employee_ids = property?.trigger?.option?.employee_ids ?? undefined const employee_ids = property?.trigger?.option?.employee_ids ?? undefined
const custom_email = const custom_email =
tos tos
.filter((t) => !t.employee_id) .filter((t) => !t.employee_id && t.email)
.map((t) => t.email) .map((t) => t.email)
.join(';') ?? '' .join(';') ?? ''
@ -360,7 +432,16 @@ export default {
this.$refs.noticeContent.setContent(body_html) this.$refs.noticeContent.setContent(body_html)
}, 100) }, 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 { } else {
this.title = `新增触发器 ${property.alias || property.name}` this.title = `新增触发器 ${property.alias || property.name}`
@ -378,6 +459,7 @@ export default {
this.category = 1 this.category = 1
this.triggerAction = '1' this.triggerAction = '1'
this.filterExp = '' this.filterExp = ''
this.selectedBot = undefined
this.visible = false this.visible = false
}, },
filterChange(value) { filterChange(value) {
@ -415,11 +497,30 @@ export default {
tos.push({ email }) tos.push({ email })
}) })
} }
if (this.selectedBot && this.selectedBot.length) {
this.selectedBot.forEach((bot) => {
tos.push({ [`${bot}`]: bot })
})
}
if (this.category === 2) { if (this.category === 2) {
const { before_days, notify_at } = this.dateForm 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 { } else {
params.option.notifies = { tos, subject, body, body_html, method } params.option.notifies = {
tos,
subject,
body,
body_html,
method: [...method, ...(this.selectedBot ?? [])],
}
} }
break break
case '2': case '2':

View File

@ -29,6 +29,12 @@
<span v-else-if="row.notify">通知</span> <span v-else-if="row.notify">通知</span>
</template> </template>
</vxe-column> </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="触发时间"> <vxe-column title="触发时间">
<template #default="{row}"> <template #default="{row}">
{{ row.updated_at || row.created_at }} {{ 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'] }, 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') 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 return routes

View File

@ -1,372 +1,379 @@
<template> <template>
<div class="ops-setting-companyinfo" :style="{ height: `${windowHeight - 64}px` }"> <div class="ops-setting-companyinfo" :style="{ height: `${windowHeight - 64}px` }">
<a-form-model ref="infoData" :model="infoData" :label-col="labelCol" :wrapper-col="wrapperCol" :rules="rule"> <a-form-model ref="infoData" :model="infoData" :label-col="labelCol" :wrapper-col="wrapperCol" :rules="rule">
<SpanTitle>公司描述</SpanTitle> <SpanTitle>公司描述</SpanTitle>
<a-form-model-item label="名称" prop="name"> <a-form-model-item label="名称" prop="name">
<a-input v-model="infoData.name" :disabled="!isEditable" /> <a-input v-model="infoData.name" :disabled="!isEditable" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="描述"> <a-form-model-item label="描述">
<a-input v-model="infoData.description" type="textarea" :disabled="!isEditable" /> <a-input v-model="infoData.description" type="textarea" :disabled="!isEditable" />
</a-form-model-item> </a-form-model-item>
<SpanTitle>公司地址</SpanTitle> <SpanTitle>公司地址</SpanTitle>
<a-form-model-item label="国家/地区"> <a-form-model-item label="国家/地区">
<a-input v-model="infoData.country" :disabled="!isEditable" /> <a-input v-model="infoData.country" :disabled="!isEditable" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="城市"> <a-form-model-item label="城市">
<a-input v-model="infoData.city" :disabled="!isEditable" /> <a-input v-model="infoData.city" :disabled="!isEditable" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="地址"> <a-form-model-item label="地址">
<a-input v-model="infoData.address" :disabled="!isEditable" /> <a-input v-model="infoData.address" :disabled="!isEditable" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="邮编"> <a-form-model-item label="邮编">
<a-input v-model="infoData.postCode" :disabled="!isEditable" /> <a-input v-model="infoData.postCode" :disabled="!isEditable" />
</a-form-model-item> </a-form-model-item>
<SpanTitle>联系方式</SpanTitle> <SpanTitle>联系方式</SpanTitle>
<a-form-model-item label="网站"> <a-form-model-item label="网站">
<a-input v-model="infoData.website" :disabled="!isEditable" /> <a-input v-model="infoData.website" :disabled="!isEditable" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="电话号码" prop="phone"> <a-form-model-item label="电话号码" prop="phone">
<a-input v-model="infoData.phone" :disabled="!isEditable" /> <a-input v-model="infoData.phone" :disabled="!isEditable" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="传真号码" prop="faxCode"> <a-form-model-item label="传真号码" prop="faxCode">
<a-input v-model="infoData.faxCode" :disabled="!isEditable" /> <a-input v-model="infoData.faxCode" :disabled="!isEditable" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="电子邮箱" prop="email"> <a-form-model-item label="电子邮箱" prop="email">
<a-input v-model="infoData.email" :disabled="!isEditable" /> <a-input v-model="infoData.email" :disabled="!isEditable" />
</a-form-model-item> </a-form-model-item>
<SpanTitle>公司标识</SpanTitle> <SpanTitle>公司标识</SpanTitle>
<a-form-model-item label="部署域名" prop="domainName"> <a-form-model-item label="Messenger地址" prop="messenger">
<a-input v-model="infoData.domainName" :disabled="!isEditable" /> <a-input v-model="infoData.messenger" :disabled="!isEditable" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="公司logo"> <a-form-model-item label="部署域名" prop="domainName">
<a-space> <a-input v-model="infoData.domainName" :disabled="!isEditable" />
<a-upload </a-form-model-item>
:disabled="!isEditable" <a-form-model-item label="公司logo">
name="avatar" <a-space>
list-type="picture-card" <a-upload
class="avatar-uploader" :disabled="!isEditable"
:show-upload-list="false" name="avatar"
:customRequest="customRequest" list-type="picture-card"
:before-upload="beforeUpload" class="avatar-uploader"
:style="{ width: '400px', height: '80px' }" :show-upload-list="false"
accept=".png,.jpg,.jpeg" :customRequest="customRequest"
> :before-upload="beforeUpload"
<div :style="{ width: '400px', height: '80px' }"
class="ops-setting-companyinfo-upload-show" accept=".png,.jpg,.jpeg"
v-if="infoData.logoName" >
:style="{ width: '400px', height: '80px' }" <div
@click="eidtImageOption.type = 'Logo'" class="ops-setting-companyinfo-upload-show"
> v-if="infoData.logoName"
<img :src="`/api/common-setting/v1/file/${infoData.logoName}`" alt="avatar" /> :style="{ width: '400px', height: '80px' }"
<a-icon @click="eidtImageOption.type = 'Logo'"
v-if="isEditable" >
type="minus-circle" <img :src="`/api/common-setting/v1/file/${infoData.logoName}`" alt="avatar" />
theme="filled" <a-icon
class="delete-icon" v-if="isEditable"
@click.stop="deleteLogo" type="minus-circle"
/> theme="filled"
</div> class="delete-icon"
<div v-else @click="eidtImageOption.type = 'Logo'"> @click.stop="deleteLogo"
<a-icon type="plus" /> />
<div class="ant-upload-text">上传</div> </div>
</div> <div v-else @click="eidtImageOption.type = 'Logo'">
</a-upload> <a-icon type="plus" />
<div class="ant-upload-text">上传</div>
<a-upload </div>
:disabled="!isEditable" </a-upload>
name="avatar"
list-type="picture-card" <a-upload
class="avatar-uploader" :disabled="!isEditable"
:show-upload-list="false" name="avatar"
:customRequest="customRequest" list-type="picture-card"
:before-upload="beforeUpload" class="avatar-uploader"
:style="{ width: '82px', height: '82px' }" :show-upload-list="false"
accept=".png,.jpg,.jpeg" :customRequest="customRequest"
> :before-upload="beforeUpload"
<div :style="{ width: '82px', height: '82px' }"
class="ops-setting-companyinfo-upload-show" accept=".png,.jpg,.jpeg"
v-if="infoData.smallLogoName" >
:style="{ width: '82px', height: '82px' }" <div
@click="eidtImageOption.type = 'SmallLogo'" class="ops-setting-companyinfo-upload-show"
> v-if="infoData.smallLogoName"
<img :src="`/api/common-setting/v1/file/${infoData.smallLogoName}`" alt="avatar" /> :style="{ width: '82px', height: '82px' }"
<a-icon @click="eidtImageOption.type = 'SmallLogo'"
v-if="isEditable" >
type="minus-circle" <img :src="`/api/common-setting/v1/file/${infoData.smallLogoName}`" alt="avatar" />
theme="filled" <a-icon
class="delete-icon" v-if="isEditable"
@click.stop="deleteSmallLogo" type="minus-circle"
/> theme="filled"
</div> class="delete-icon"
<div v-else @click="eidtImageOption.type = 'SmallLogo'"> @click.stop="deleteSmallLogo"
<a-icon type="plus" /> />
<div class="ant-upload-text">上传</div> </div>
</div> <div v-else @click="eidtImageOption.type = 'SmallLogo'">
</a-upload> <a-icon type="plus" />
</a-space> <div class="ant-upload-text">上传</div>
</a-form-model-item> </div>
<a-form-model-item :wrapper-col="{ span: 14, offset: 3 }" v-if="isEditable"> </a-upload>
<a-button type="primary" @click="onSubmit"> 保存 </a-button> </a-space>
<a-button ghost type="primary" style="margin-left: 28px" @click="resetForm"> 重置 </a-button> </a-form-model-item>
</a-form-model-item> <a-form-model-item :wrapper-col="{ span: 14, offset: 3 }" v-if="isEditable">
</a-form-model> <a-button type="primary" @click="onSubmit"> 保存 </a-button>
<edit-image <a-button ghost type="primary" style="margin-left: 28px" @click="resetForm"> 重置 </a-button>
v-if="showEditImage" </a-form-model-item>
:show="showEditImage" </a-form-model>
:image="editImage" <edit-image
:title="eidtImageOption.title" v-if="showEditImage"
:eidtImageOption="eidtImageOption" :show="showEditImage"
@save="submitImage" :image="editImage"
@close="showEditImage = false" :title="eidtImageOption.title"
/> :eidtImageOption="eidtImageOption"
</div> @save="submitImage"
</template> @close="showEditImage = false"
/>
<script> </div>
import { getCompanyInfo, postCompanyInfo, putCompanyInfo } from '@/api/company' </template>
import { postImageFile } from '@/api/file'
import { mapMutations, mapState } from 'vuex' <script>
import SpanTitle from '../components/spanTitle.vue' import { getCompanyInfo, postCompanyInfo, putCompanyInfo } from '@/api/company'
import EditImage from '../components/EditImage.vue' import { postImageFile } from '@/api/file'
import { mixinPermissions } from '@/utils/mixin' import { mapMutations, mapState } from 'vuex'
export default { import SpanTitle from '../components/spanTitle.vue'
name: 'CompanyInfo', import EditImage from '../components/EditImage.vue'
mixins: [mixinPermissions], import { mixinPermissions } from '@/utils/mixin'
components: { SpanTitle, EditImage }, export default {
data() { name: 'CompanyInfo',
return { mixins: [mixinPermissions],
labelCol: { span: 3 }, components: { SpanTitle, EditImage },
wrapperCol: { span: 10 }, data() {
infoData: { return {
name: '', labelCol: { span: 3 },
description: '', wrapperCol: { span: 10 },
address: '', infoData: {
city: '', name: '',
postCode: '', description: '',
country: '', address: '',
website: '', city: '',
phone: '', postCode: '',
faxCode: '', country: '',
email: '', website: '',
logoName: '', phone: '',
smallLogoName: '', faxCode: '',
}, email: '',
rule: { logoName: '',
name: [{ required: true, whitespace: true, message: '请输入名称', trigger: 'blur' }], smallLogoName: '',
phone: [ messenger: '',
{ domainName: '',
required: false, },
whitespace: true, rule: {
pattern: new RegExp('^([0-9]|-)+$', 'g'), name: [{ required: true, whitespace: true, message: '请输入名称', trigger: 'blur' }],
message: '请输入正确的电话号码', phone: [
trigger: 'blur', {
}, required: false,
], whitespace: true,
faxCode: [ pattern: new RegExp('^([0-9]|-)+$', 'g'),
{ message: '请输入正确的电话号码',
required: false, trigger: 'blur',
whitespace: true, },
pattern: new RegExp('^([0-9]|-)+$', 'g'), ],
message: '请输入正确的传真号码', faxCode: [
trigger: 'blur', {
}, required: false,
], whitespace: true,
email: [ pattern: new RegExp('^([0-9]|-)+$', 'g'),
{ message: '请输入正确的传真号码',
required: false, trigger: 'blur',
whitespace: true, },
pattern: new RegExp('^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(.[a-zA-Z0-9-]+)*.[a-zA-Z0-9]{2,6}$', 'g'), ],
message: '请输入正确的邮箱地址', email: [
trigger: 'blur', {
}, required: false,
], whitespace: true,
}, pattern: new RegExp('^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(.[a-zA-Z0-9-]+)*.[a-zA-Z0-9]{2,6}$', 'g'),
getId: -1, message: '请输入正确的邮箱地址',
showEditImage: false, trigger: 'blur',
editImage: null, },
eidtImageOption: { ],
type: 'Logo', },
fixedNumber: [15, 4], getId: -1,
title: '编辑企业logo', showEditImage: false,
previewWidth: '200px', editImage: null,
previewHeight: '40px', eidtImageOption: {
autoCropWidth: 200, type: 'Logo',
autoCropHeight: 40, fixedNumber: [15, 4],
}, title: '编辑企业logo',
} previewWidth: '200px',
}, previewHeight: '40px',
async mounted() { autoCropWidth: 200,
const res = await getCompanyInfo() autoCropHeight: 40,
if (!res.id) { },
this.getId = -1 }
} else { },
this.infoData = res.info async mounted() {
this.getId = res.id const res = await getCompanyInfo()
} if (!res.id) {
}, this.getId = -1
computed: { } else {
...mapState({ this.infoData = res.info
windowHeight: (state) => state.windowHeight, this.getId = res.id
}), }
isEditable() { },
return this.hasDetailPermission('backend', '公司信息', ['update']) computed: {
}, ...mapState({
}, windowHeight: (state) => state.windowHeight,
methods: { }),
...mapMutations(['SET_FILENAME', 'SET_SMALL_FILENAME']), isEditable() {
deleteLogo() { return this.hasDetailPermission('backend', '公司信息', ['update'])
this.infoData.logoName = '' },
}, },
deleteSmallLogo() { methods: {
this.infoData.smallLogoName = '' ...mapMutations(['SET_FILENAME', 'SET_SMALL_FILENAME']),
}, deleteLogo() {
async onSubmit() { this.infoData.logoName = ''
this.$refs.infoData.validate(async (valid) => { },
if (valid) { deleteSmallLogo() {
if (this.getId === -1) { this.infoData.smallLogoName = ''
await postCompanyInfo(this.infoData) },
} else { async onSubmit() {
await putCompanyInfo(this.getId, this.infoData) this.$refs.infoData.validate(async (valid) => {
} if (valid) {
this.SET_FILENAME(this.infoData.logoName) if (this.getId === -1) {
this.SET_SMALL_FILENAME(this.infoData.smallFileName) await postCompanyInfo(this.infoData)
this.$message.success('保存成功') } else {
} else { await putCompanyInfo(this.getId, this.infoData)
this.$message.warning('检查您的输入是否正确!') }
return false this.SET_FILENAME(this.infoData.logoName)
} this.SET_SMALL_FILENAME(this.infoData.smallFileName)
}) this.$message.success('保存成功')
}, } else {
resetForm() { this.$message.warning('检查您的输入是否正确!')
this.infoData = { return false
name: '', }
description: '', })
address: '', },
city: '', resetForm() {
postCode: '', this.infoData = {
country: '', name: '',
website: '', description: '',
phone: '', address: '',
faxCode: '', city: '',
email: '', postCode: '',
logoName: '', country: '',
smallLogoName: '', website: '',
} phone: '',
}, faxCode: '',
customRequest(file) { email: '',
const reader = new FileReader() logoName: '',
var self = this smallLogoName: '',
if (this.eidtImageOption.type === 'Logo') { messenger: '',
this.eidtImageOption = { domainName: '',
type: 'Logo', }
fixedNumber: [20, 4], },
title: '编辑企业logo', customRequest(file) {
previewWidth: '200px', const reader = new FileReader()
previewHeight: '40px', var self = this
autoCropWidth: 200, if (this.eidtImageOption.type === 'Logo') {
autoCropHeight: 40, this.eidtImageOption = {
} type: 'Logo',
} else if (this.eidtImageOption.type === 'SmallLogo') { fixedNumber: [20, 4],
this.eidtImageOption = { title: '编辑企业logo',
type: 'SmallLogo', previewWidth: '200px',
fixedNumber: [4, 4], previewHeight: '40px',
title: '编辑企业logo缩略图', autoCropWidth: 200,
previewWidth: '80px', autoCropHeight: 40,
previewHeight: '80px', }
autoCropWidth: 250, } else if (this.eidtImageOption.type === 'SmallLogo') {
autoCropHeight: 250, this.eidtImageOption = {
} type: 'SmallLogo',
} fixedNumber: [4, 4],
reader.onload = function(e) { title: '编辑企业logo缩略图',
let result previewWidth: '80px',
if (typeof e.target.result === 'object') { previewHeight: '80px',
// 把Array Buffer转化为blob 如果是base64不需要 autoCropWidth: 250,
result = window.URL.createObjectURL(new Blob([e.target.result])) autoCropHeight: 250,
} else { }
result = e.target.result }
} reader.onload = function(e) {
let result
self.editImage = result if (typeof e.target.result === 'object') {
self.showEditImage = true // 把Array Buffer转化为blob 如果是base64不需要
} result = window.URL.createObjectURL(new Blob([e.target.result]))
reader.readAsDataURL(file.file) } else {
}, result = e.target.result
submitImage(file) { }
postImageFile(file).then((res) => {
if (res.file_name) { self.editImage = result
if (this.eidtImageOption.type === 'Logo') { self.showEditImage = true
this.infoData.logoName = res.file_name }
} else if (this.eidtImageOption.type === 'SmallLogo') { reader.readAsDataURL(file.file)
this.infoData.smallLogoName = res.file_name },
} submitImage(file) {
} else { postImageFile(file).then((res) => {
} if (res.file_name) {
}) if (this.eidtImageOption.type === 'Logo') {
}, this.infoData.logoName = res.file_name
} else if (this.eidtImageOption.type === 'SmallLogo') {
beforeUpload(file) { this.infoData.smallLogoName = res.file_name
const isLt2M = file.size / 1024 / 1024 < 2 }
if (!isLt2M) { } else {
this.$message.error('图片大小不可超过2MB!') }
} })
return isLt2M },
},
}, beforeUpload(file) {
} const isLt2M = file.size / 1024 / 1024 < 2
</script> if (!isLt2M) {
this.$message.error('图片大小不可超过2MB!')
<style lang="less"> }
.ops-setting-companyinfo { return isLt2M
padding-top: 15px; },
background-color: #fff; },
border-radius: 15px; }
overflow: auto; </script>
margin-bottom: -24px;
.ops-setting-companyinfo-upload-show { <style lang="less">
position: relative; .ops-setting-companyinfo {
width: 290px; padding-top: 15px;
height: 100px; background-color: #fff;
max-height: 100px; border-radius: 15px;
img { overflow: auto;
width: 100%; margin-bottom: -24px;
height: 100%; .ops-setting-companyinfo-upload-show {
} position: relative;
width: 290px;
.delete-icon { height: 100px;
display: none; max-height: 100px;
} img {
} width: 100%;
.ant-upload:hover .delete-icon { height: 100%;
display: block; }
position: absolute;
top: 5px; .delete-icon {
right: 5px; display: none;
color: rgb(247, 85, 85); }
} }
.ant-form-item { .ant-upload:hover .delete-icon {
margin-bottom: 10px; display: block;
} position: absolute;
.ant-form-item label { top: 5px;
padding-right: 10px; right: 5px;
} color: rgb(247, 85, 85);
.avatar-uploader > .ant-upload { }
// max-width: 100px; .ant-form-item {
max-height: 100px; margin-bottom: 10px;
} }
// .ant-upload.ant-upload-select-picture-card { .ant-form-item label {
// width: 100%; padding-right: 10px;
// > .ant-upload { }
// padding: 0px; .avatar-uploader > .ant-upload {
.ant-upload-picture-card-wrapper { // max-width: 100px;
height: 100px; max-height: 100px;
.ant-upload.ant-upload-select-picture-card { }
width: 100%; // .ant-upload.ant-upload-select-picture-card {
height: 100%; // width: 100%;
margin: 0; // > .ant-upload {
> .ant-upload { // padding: 0px;
padding: 0px; .ant-upload-picture-card-wrapper {
} height: 100px;
} .ant-upload.ant-upload-select-picture-card {
} width: 100%;
} height: 100%;
</style> margin: 0;
> .ant-upload {
padding: 0px;
}
}
}
}
</style>

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

@ -1,368 +1,405 @@
<template> <template>
<div class="setting-person"> <div class="setting-person">
<div class="setting-person-left"> <div class="setting-person-left">
<div <div
@click=" @click="
() => { () => {
$refs.personForm.clearValidate() $refs.personForm.clearValidate()
$nextTick(() => { $nextTick(() => {
current = '1' current = '1'
}) })
} }
" "
:class="{ 'setting-person-left-item': true, 'setting-person-left-item-selected': current === '1' }" :class="{ 'setting-person-left-item': true, 'setting-person-left-item-selected': current === '1' }"
> >
<ops-icon type="icon-shidi-yonghu" />个人信息 <ops-icon type="icon-shidi-yonghu" />个人信息
</div> </div>
<div <div
@click=" @click="
() => { () => {
$refs.personForm.clearValidate() $refs.personForm.clearValidate()
$nextTick(() => { $nextTick(() => {
current = '2' current = '2'
}) })
} }
" "
:class="{ 'setting-person-left-item': true, 'setting-person-left-item-selected': current === '2' }" :class="{ 'setting-person-left-item': true, 'setting-person-left-item-selected': current === '2' }"
> >
<a-icon type="unlock" theme="filled" />账号密码 <a-icon type="unlock" theme="filled" />账号密码
</div> </div>
</div> </div>
<div class="setting-person-right"> <div class="setting-person-right">
<a-form-model <a-form-model
ref="personForm" ref="personForm"
:model="form" :model="form"
:rules="current === '1' ? rules1 : rules2" :rules="current === '1' ? rules1 : rules2"
:colon="false" :colon="false"
labelAlign="left" labelAlign="left"
:labelCol="{ span: 4 }" :labelCol="{ span: 4 }"
:wrapperCol="{ span: 10 }" :wrapperCol="{ span: 10 }"
> >
<div v-show="current === '1'"> <div v-show="current === '1'">
<a-form-model-item label="头像" :style="{ display: 'flex', alignItems: 'center' }"> <a-form-model-item label="头像" :style="{ display: 'flex', alignItems: 'center' }">
<a-space> <a-space>
<a-avatar v-if="form.avatar" :src="`/api/common-setting/v1/file/${form.avatar}`" :size="64"> </a-avatar> <a-avatar v-if="form.avatar" :src="`/api/common-setting/v1/file/${form.avatar}`" :size="64"> </a-avatar>
<a-avatar v-else style="backgroundColor:#F0F5FF" :size="64"> <a-avatar v-else style="backgroundColor:#F0F5FF" :size="64">
<ops-icon type="icon-shidi-yonghu" :style="{ color: '#2F54EB' }" /> <ops-icon type="icon-shidi-yonghu" :style="{ color: '#2F54EB' }" />
</a-avatar> </a-avatar>
<a-upload <a-upload
name="avatar" name="avatar"
:show-upload-list="false" :show-upload-list="false"
:customRequest="customRequest" :customRequest="customRequest"
:before-upload="beforeUpload" :before-upload="beforeUpload"
:style="{ width: '310px', height: '100px' }" :style="{ width: '310px', height: '100px' }"
accept=".svg,.png,.jpg,.jpeg" accept=".svg,.png,.jpg,.jpeg"
> >
<a-button type="primary" ghost size="small">更换头像</a-button> <a-button type="primary" ghost size="small">更换头像</a-button>
</a-upload> </a-upload>
</a-space> </a-space>
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="姓名" prop="nickname"> <a-form-model-item label="姓名" prop="nickname">
<a-input v-model="form.nickname" /> <a-input v-model="form.nickname" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="用户名"> <a-form-model-item label="用户名">
<div class="setting-person-right-disabled">{{ form.username }}</div> <div class="setting-person-right-disabled">{{ form.username }}</div>
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="邮箱"> <a-form-model-item label="邮箱">
<div class="setting-person-right-disabled">{{ form.email }}</div> <div class="setting-person-right-disabled">{{ form.email }}</div>
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="直属上级"> <a-form-model-item label="直属上级">
<div class="setting-person-right-disabled"> <div class="setting-person-right-disabled">
{{ getDirectorName(allFlatEmployees, form.direct_supervisor_id) }} {{ getDirectorName(allFlatEmployees, form.direct_supervisor_id) }}
</div> </div>
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="性别"> <a-form-model-item label="性别">
<a-select v-model="form.sex"> <a-select v-model="form.sex">
<a-select-option value=""></a-select-option> <a-select-option value=""></a-select-option>
<a-select-option value=""></a-select-option> <a-select-option value=""></a-select-option>
</a-select> </a-select>
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="手机号" prop="mobile"> <a-form-model-item label="手机号" prop="mobile">
<a-input v-model="form.mobile" /> <a-input v-model="form.mobile" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="部门"> <a-form-model-item label="部门">
<div class="setting-person-right-disabled"> <div class="setting-person-right-disabled">
{{ getDepartmentName(allFlatDepartments, form.department_id) }} {{ getDepartmentName(allFlatDepartments, form.department_id) }}
</div> </div>
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="岗位"> <a-form-model-item label="岗位">
<div class="setting-person-right-disabled">{{ form.position_name }}</div> <div class="setting-person-right-disabled">{{ form.position_name }}</div>
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="绑定信息"> <a-form-model-item label="绑定信息">
<a-space> <a-space>
<div :class="{ 'setting-person-bind': true, 'setting-person-bind-existed': form.wx_id }"> <a-tooltip title="企业微信">
<ops-icon type="ops-setting-notice-wx" /> <div
</div> @click="handleBind('wechatApp', form.notice_info && form.notice_info.wechatApp)"
<div @click="handleBindWx" class="setting-person-bind-button"> :class="{
{{ form.notice_info && form.notice_info.wechatApp ? '重新绑定' : '绑定' }} 'setting-person-bind': true,
</div> 'setting-person-bind-existed': form.notice_info && form.notice_info.wechatApp,
</a-space> }"
</a-form-model-item> >
</div> <ops-icon type="ops-setting-notice-wx" />
<div v-show="current === '2'"> </div>
<a-form-model-item label="新密码" prop="password1"> </a-tooltip>
<a-input v-model="form.password1" /> <a-tooltip title="飞书">
</a-form-model-item> <div
<a-form-model-item label="确认密码" prop="password2"> @click="handleBind('feishuApp', form.notice_info && form.notice_info.feishuApp)"
<a-input v-model="form.password2" /> :class="{
</a-form-model-item> 'setting-person-bind': true,
</div> 'setting-person-bind-existed': form.notice_info && form.notice_info.feishuApp,
<div style="margin-right: 120px"> }"
<a-form-model-item label=" "> >
<a-button type="primary" @click="handleSave" :style="{ width: '100%' }">保存</a-button> <ops-icon type="ops-setting-notice-feishu" />
</a-form-model-item> </div>
</div> </a-tooltip>
</a-form-model> <a-tooltip title="钉钉">
</div> <div
<EditImage @click="handleBind('dingdingApp', form.notice_info && form.notice_info.dingdingApp)"
v-if="showEditImage" :class="{
:fixed-number="eidtImageOption.fixedNumber" 'setting-person-bind': true,
:show="showEditImage" 'setting-person-bind-existed': form.notice_info && form.notice_info.dingdingApp,
:image="editImage" }"
:title="eidtImageOption.title" >
:preview-width="eidtImageOption.previewWidth" <ops-icon type="ops-setting-notice-dingding" />
:preview-height="eidtImageOption.previewHeight" </div>
preview-radius="0" </a-tooltip>
width="550px" </a-space>
save-button-title="确定" </a-form-model-item>
@save="submitImage" </div>
@close="showEditImage = false" <div v-show="current === '2'">
/> <a-form-model-item label="新密码" prop="password1">
</div> <a-input v-model="form.password1" />
</template> </a-form-model-item>
<a-form-model-item label="确认密码" prop="password2">
<script> <a-input v-model="form.password2" />
import { mapActions, mapGetters } from 'vuex' </a-form-model-item>
import { getAllDepartmentList } from '@/api/company' </div>
import { postImageFile } from '@/api/file' <div style="margin-right: 120px">
import { <a-form-model-item label=" ">
getEmployeeList, <a-button type="primary" @click="handleSave" :style="{ width: '100%' }">保存</a-button>
getEmployeeByUid, </a-form-model-item>
updateEmployeeByUid, </div>
updatePasswordByUid, </a-form-model>
bindWxByUid, </div>
} from '@/api/employee' <EditImage
import { getDepartmentName, getDirectorName } from '@/utils/util' v-if="showEditImage"
import EditImage from '../components/EditImage.vue' :show="showEditImage"
export default { :image="editImage"
name: 'Person', :title="eidtImageOption.title"
components: { EditImage }, :preview-width="eidtImageOption.previewWidth"
data() { :preview-height="eidtImageOption.previewHeight"
const validatePassword = (rule, value, callback) => { preview-radius="0"
if (!value) { width="550px"
callback(new Error('请二次确认新密码')) save-button-title="确定"
} @save="submitImage"
if (value !== this.form.password1) { @close="showEditImage = false"
callback(new Error('两次输入密码不一致')) />
} </div>
callback() </template>
}
return { <script>
current: '1', import { mapActions, mapGetters } from 'vuex'
form: {}, import { getAllDepartmentList } from '@/api/company'
rules1: { import { postImageFile } from '@/api/file'
nickname: [ import {
{ required: true, whitespace: true, message: '请输入姓名', trigger: 'blur' }, getEmployeeList,
{ max: 20, message: '字符数须小于20' }, getEmployeeByUid,
], updateEmployeeByUid,
mobile: [ updatePasswordByUid,
{ bindPlatformByUid,
pattern: /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/, unbindPlatformByUid,
message: '请输入正确的手机号', } from '@/api/employee'
trigger: 'blur', import { getDepartmentName, getDirectorName } from '@/utils/util'
}, import EditImage from '../components/EditImage.vue'
], export default {
}, name: 'Person',
rules2: { components: { EditImage },
password1: [{ required: true, message: '请输入新密码', trigger: 'blur' }], data() {
password2: [{ required: true, message: '两次输入密码不一致', trigger: 'blur', validator: validatePassword }], const validatePassword = (rule, value, callback) => {
}, if (!value) {
allFlatEmployees: [], callback(new Error('请二次确认新密码'))
allFlatDepartments: [], }
showEditImage: false, if (value !== this.form.password1) {
eidtImageOption: { callback(new Error('两次输入密码不一致'))
type: 'avatar', }
fixedNumber: [4, 4], callback()
title: '编辑头像', }
previewWidth: '60px', return {
previewHeight: '60px', current: '1',
}, form: {},
editImage: null, rules1: {
} nickname: [
}, { required: true, whitespace: true, message: '请输入姓名', trigger: 'blur' },
computed: { { max: 20, message: '字符数须小于20' },
...mapGetters(['uid']), ],
}, mobile: [
mounted() { {
this.getAllFlatEmployees() pattern: /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/,
this.getAllFlatDepartment() message: '请输入正确的手机号',
this.getEmployeeByUid() trigger: 'blur',
}, },
methods: { ],
...mapActions(['GetInfo']), },
getDepartmentName, rules2: {
getDirectorName, password1: [{ required: true, message: '请输入新密码', trigger: 'blur' }],
getEmployeeByUid() { password2: [{ required: true, message: '两次输入密码不一致', trigger: 'blur', validator: validatePassword }],
getEmployeeByUid(this.uid).then((res) => { },
this.form = { ...res } allFlatEmployees: [],
}) allFlatDepartments: [],
}, showEditImage: false,
getAllFlatEmployees() { eidtImageOption: {
getEmployeeList({ block_status: 0, page_size: 99999 }).then((res) => { type: 'avatar',
this.allFlatEmployees = res.data_list fixedNumber: [4, 4],
}) title: '编辑头像',
}, previewWidth: '60px',
getAllFlatDepartment() { previewHeight: '60px',
getAllDepartmentList({ is_tree: 0 }).then((res) => { },
this.allFlatDepartments = res editImage: null,
}) }
}, },
async handleSave() { computed: {
await this.$refs.personForm.validate(async (valid) => { ...mapGetters(['uid']),
if (valid) { },
const { nickname, mobile, sex, avatar, password1 } = this.form mounted() {
const params = { nickname, mobile, sex, avatar } this.getAllFlatEmployees()
if (this.current === '1') { this.getAllFlatDepartment()
await updateEmployeeByUid(this.uid, params).then((res) => { this.getEmployeeByUid()
this.$message.success('保存成功!') },
this.getEmployeeByUid() methods: {
this.GetInfo() ...mapActions(['GetInfo']),
}) getDepartmentName,
} else { getDirectorName,
await updatePasswordByUid(this.uid, { password: password1 }).then((res) => { getEmployeeByUid() {
this.$message.success('保存成功!') getEmployeeByUid(this.uid).then((res) => {
}) this.form = { ...res }
} })
} },
}) getAllFlatEmployees() {
}, getEmployeeList({ block_status: 0, page_size: 99999 }).then((res) => {
customRequest(file) { this.allFlatEmployees = res.data_list
const reader = new FileReader() })
var self = this },
reader.onload = function(e) { getAllFlatDepartment() {
let result getAllDepartmentList({ is_tree: 0 }).then((res) => {
if (typeof e.target.result === 'object') { this.allFlatDepartments = res
// 把Array Buffer转化为blob 如果是base64不需要 })
result = window.URL.createObjectURL(new Blob([e.target.result])) },
} else { async handleSave() {
result = e.target.result await this.$refs.personForm.validate(async (valid) => {
} if (valid) {
const { nickname, mobile, sex, avatar, password1 } = this.form
self.editImage = result const params = { nickname, mobile, sex, avatar }
self.showEditImage = true if (this.current === '1') {
} await updateEmployeeByUid(this.uid, params).then((res) => {
reader.readAsDataURL(file.file) this.$message.success('保存成功!')
}, this.getEmployeeByUid()
beforeUpload(file) { this.GetInfo()
const isLt2M = file.size / 1024 / 1024 < 2 })
if (!isLt2M) { } else {
this.$message.error('图片大小不可超过2MB!') await updatePasswordByUid(this.uid, { password: password1 }).then((res) => {
} this.$message.success('保存成功!')
return isLt2M })
}, }
submitImage(file) { }
postImageFile(file).then((res) => { })
if (res.file_name) { },
this.form.avatar = res.file_name customRequest(file) {
} const reader = new FileReader()
}) var self = this
}, reader.onload = function(e) {
async handleBindWx() { let result
await this.$refs.personForm.validate(async (valid) => { if (typeof e.target.result === 'object') {
if (valid) { // 把Array Buffer转化为blob 如果是base64不需要
const { nickname, mobile, sex, avatar } = this.form result = window.URL.createObjectURL(new Blob([e.target.result]))
const params = { nickname, mobile, sex, avatar } } else {
await updateEmployeeByUid(this.uid, params) result = e.target.result
bindWxByUid(this.uid) }
.then(() => {
this.$message.success('绑定成功!') self.editImage = result
}) self.showEditImage = true
.finally(() => { }
this.getEmployeeByUid() reader.readAsDataURL(file.file)
this.GetInfo() },
}) beforeUpload(file) {
} const isLt2M = file.size / 1024 / 1024 < 2
}) if (!isLt2M) {
}, this.$message.error('图片大小不可超过2MB!')
}, }
} return isLt2M
</script> },
submitImage(file) {
<style lang="less" scoped> postImageFile(file).then((res) => {
@import '~@/style/static.less'; if (res.file_name) {
.setting-person { this.form.avatar = res.file_name
display: flex; }
flex-direction: row; })
.setting-person-left { },
width: 200px; async handleBind(platform, isBind) {
height: 400px; if (isBind) {
margin-right: 24px; const that = this
background-color: #fff; this.$confirm({
border-radius: 15px; title: '警告',
padding-top: 15px; content: `确认解绑`,
.setting-person-left-item { onOk() {
cursor: pointer; unbindPlatformByUid(platform, that.uid)
padding: 10px 20px; .then(() => {
color: #a5a9bc; that.$message.success('解绑成功!')
border-left: 4px solid #fff; })
margin-bottom: 5px; .finally(() => {
&:hover { that.getEmployeeByUid()
.ops_popover_item_selected(); that.GetInfo()
border-color: #custom_colors[color_1]; })
} },
> i { })
margin-right: 10px; } else {
} await this.$refs.personForm.validate(async (valid) => {
} if (valid) {
.setting-person-left-item-selected { const { nickname, mobile, sex, avatar } = this.form
.ops_popover_item_selected(); const params = { nickname, mobile, sex, avatar }
border-color: #custom_colors[color_1]; await updateEmployeeByUid(this.uid, params)
} bindPlatformByUid(platform, this.uid)
} .then(() => {
.setting-person-right { this.$message.success('绑定成功!')
width: 800px; })
height: 700px; .finally(() => {
background-color: #fff; this.getEmployeeByUid()
border-radius: 15px; this.GetInfo()
padding: 24px 48px; })
.setting-person-right-disabled { }
background-color: #custom_colors[color_2]; })
border-radius: 4px; }
height: 30px; },
line-height: 30px; },
margin-top: 4px; }
padding: 0 10px; </script>
color: #a5a9bc;
} <style lang="less" scoped>
.setting-person-bind { @import '~@/style/static.less';
width: 40px; .setting-person {
height: 40px; display: flex;
background: #a5a9bc; flex-direction: row;
border-radius: 4px; .setting-person-left {
color: #fff; width: 200px;
font-size: 30px; height: 400px;
text-align: center; margin-right: 24px;
} background-color: #fff;
.setting-person-bind-existed { border-radius: 15px;
background: #008cee; padding-top: 15px;
} .setting-person-left-item {
.setting-person-bind-button { cursor: pointer;
height: 40px; padding: 10px 20px;
width: 72px; color: #a5a9bc;
background: #f0f5ff; border-left: 4px solid #fff;
border-radius: 4px; margin-bottom: 5px;
padding: 0 8px; &:hover {
text-align: center; .ops_popover_item_selected();
cursor: pointer; border-color: #custom_colors[color_1];
} }
} > i {
} margin-right: 10px;
</style> }
<style lang="less"> }
.setting-person-right .ant-form-item { .setting-person-left-item-selected {
margin-bottom: 12px; .ops_popover_item_selected();
display: flex; border-color: #custom_colors[color_1];
justify-content: center; }
align-items: center; }
} .setting-person-right {
</style> width: 800px;
height: 700px;
background-color: #fff;
border-radius: 15px;
padding: 24px 48px;
.setting-person-right-disabled {
background-color: #custom_colors[color_2];
border-radius: 4px;
height: 30px;
line-height: 30px;
margin-top: 4px;
padding: 0 10px;
color: #a5a9bc;
}
.setting-person-bind {
width: 40px;
height: 40px;
background: #a5a9bc;
border-radius: 4px;
color: #fff;
font-size: 30px;
text-align: center;
cursor: pointer;
}
.setting-person-bind-existed {
background: #008cee;
}
}
}
</style>
<style lang="less">
.setting-person-right .ant-form-item {
margin-bottom: 12px;
display: flex;
justify-content: center;
align-items: center;
}
</style>