Merge branch 'dev' of github.com:jetlinks/jetlinks-ui-vue into dev
This commit is contained in:
commit
693da4ec6d
|
@ -19,6 +19,7 @@
|
||||||
"ant-design-vue": "^3.2.15",
|
"ant-design-vue": "^3.2.15",
|
||||||
"axios": "^1.2.1",
|
"axios": "^1.2.1",
|
||||||
"echarts": "^5.4.1",
|
"echarts": "^5.4.1",
|
||||||
|
"event-source-polyfill": "^1.0.31",
|
||||||
"jetlinks-store": "^0.0.3",
|
"jetlinks-store": "^0.0.3",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.1",
|
||||||
"less": "^4.1.3",
|
"less": "^4.1.3",
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { BASE_API_PATH } from "@/utils/variable";
|
||||||
|
import server from '@/utils/request'
|
||||||
|
import { SearchHistoryList } from 'components/Search/types'
|
||||||
|
|
||||||
|
export const FILE_UPLOAD = `${BASE_API_PATH}/file/static`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存查询记录
|
||||||
|
* @param data
|
||||||
|
* @param target
|
||||||
|
*/
|
||||||
|
export const saveSearchHistory = (data: any, target:string) => server.post(`/user/settings/${target}`, data)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取查询记录
|
||||||
|
* @param target
|
||||||
|
*/
|
||||||
|
export const getSearchHistory = (target:string) => server.get<SearchHistoryList[]>(`/user/settings/${target}`)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除指定查询记录
|
||||||
|
* @param id
|
||||||
|
* @param target
|
||||||
|
*/
|
||||||
|
export const deleteSearchHistory = (target:string, id:string) => server.remove<SearchHistoryList[]>(`/user/settings/${target}/${id}`)
|
|
@ -51,4 +51,25 @@ export const _deploy = (id: string) => server.post(`/device-instance/${id}/deplo
|
||||||
* @param data
|
* @param data
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const _undeploy = (id: string) => server.post(`/device-instance/${id}/undeploy`)
|
export const _undeploy = (id: string) => server.post(`/device-instance/${id}/undeploy`)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量激活设备
|
||||||
|
* @param data 设备id数组
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const batchDeployDevice = (data: string[]) => server.put(`/device-instance/batch/_deploy`, data)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量注销设备
|
||||||
|
* @param data 设备id数组
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const batchUndeployDevice = (data: string[]) => server.put(`/device-instance/batch/_unDeploy`, data)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除
|
||||||
|
* @param data 设备id数组
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const batchDeleteDevice = (data: string[]) => server.put(`/device-instance/batch/_delete`, data)
|
||||||
|
|
|
@ -36,4 +36,10 @@ export const getCodecs = () => server.get<{id: string, name: string}>('/device/p
|
||||||
* @param id 产品ID
|
* @param id 产品ID
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const detail = (id: string) => server.get<ProductItem>(`/device-product/${id}`)
|
export const detail = (id: string) => server.get<ProductItem>(`/device-product/${id}`)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品分类
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
export const category = (data: any) => server.post('/device/category/_tree', data)
|
|
@ -3,4 +3,4 @@ import server from '@/utils/request';
|
||||||
// 保存
|
// 保存
|
||||||
export const save_api = (data: any) => server.post(`/system/config/scope/_save`, data)
|
export const save_api = (data: any) => server.post(`/system/config/scope/_save`, data)
|
||||||
// 获取详情
|
// 获取详情
|
||||||
export const getDetails_api = (data: any) => server.post(`/system/config/scopes`, data)
|
export const getDetails_api = (data: any) => server.post(`/system/config/scopes`, data)
|
||||||
|
|
|
@ -21,7 +21,11 @@ const iconKeys = [
|
||||||
'StopOutlined',
|
'StopOutlined',
|
||||||
'CheckOutlined',
|
'CheckOutlined',
|
||||||
'CloseOutlined',
|
'CloseOutlined',
|
||||||
'DownOutlined'
|
'DownOutlined',
|
||||||
|
'ImportOutlined',
|
||||||
|
'ExportOutlined',
|
||||||
|
'SyncOutlined',
|
||||||
|
'ExclamationCircleOutlined'
|
||||||
]
|
]
|
||||||
|
|
||||||
const Icon = (props: {type: string}) => {
|
const Icon = (props: {type: string}) => {
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
v-model:value='formData.data[item.name]'
|
v-model:value='formData.data[item.name]'
|
||||||
:options='item.options'
|
:options='item.options'
|
||||||
/>
|
/>
|
||||||
<a-inputnumber
|
<a-input-number
|
||||||
v-else-if='item.component === componentType.inputNumber'
|
v-else-if='item.component === componentType.inputNumber'
|
||||||
v-bind='item.componentProps'
|
v-bind='item.componentProps'
|
||||||
v-model:value='formData.data[item.name]'
|
v-model:value='formData.data[item.name]'
|
||||||
|
|
|
@ -32,6 +32,7 @@ self.MonacoEnvironment = {
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: [String, Number],
|
modelValue: [String, Number],
|
||||||
|
theme: { type: String, default: 'vs-dark' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
@ -48,7 +49,7 @@ onMounted(() => {
|
||||||
tabSize: 2,
|
tabSize: 2,
|
||||||
automaticLayout: true,
|
automaticLayout: true,
|
||||||
scrollBeyondLastLine: false,
|
scrollBeyondLastLine: false,
|
||||||
theme: 'vs-dark', // 主题色: vs(默认高亮), vs-dark(黑色), hc-black(高亮黑色)
|
theme: props.theme, // 主题色: vs(默认高亮), vs-dark(黑色), hc-black(高亮黑色)
|
||||||
});
|
});
|
||||||
|
|
||||||
instance.onDidChangeModelContent(() => {
|
instance.onDidChangeModelContent(() => {
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
<template>
|
||||||
|
<a-dropdown-button
|
||||||
|
type='primary'
|
||||||
|
@click='click'
|
||||||
|
placement='bottomLeft'
|
||||||
|
:visible='historyVisible'
|
||||||
|
@visibleChange='visibleChange'
|
||||||
|
>
|
||||||
|
搜索
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu>
|
||||||
|
<template v-if='!showEmpty'>
|
||||||
|
<a-menu-item v-for='item in historyList' :key='item.id'>
|
||||||
|
<div class='history-item'>
|
||||||
|
<span @click.stop='itemClick(item.content)'>{{ item.name }}</span>
|
||||||
|
<a-popconfirm
|
||||||
|
title='确认删除吗?'
|
||||||
|
placement='top'
|
||||||
|
@confirm.stop='deleteHistory(item.id)'
|
||||||
|
:okButtonProps='{
|
||||||
|
loading: deleteLoading
|
||||||
|
}'
|
||||||
|
>
|
||||||
|
<span class='delete'>
|
||||||
|
<DeleteOutlined />
|
||||||
|
</span>
|
||||||
|
</a-popconfirm>
|
||||||
|
</div>
|
||||||
|
</a-menu-item>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class='history-empty'>
|
||||||
|
<a-empty />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
<template #icon>
|
||||||
|
<SearchOutlined />
|
||||||
|
</template>
|
||||||
|
</a-dropdown-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang='ts' name='SearchHistory'>
|
||||||
|
import { SearchOutlined, DeleteOutlined } from '@ant-design/icons-vue'
|
||||||
|
import { deleteSearchHistory, getSearchHistory } from '@/api/comm'
|
||||||
|
import type { SearchHistoryList } from 'components/Search/types'
|
||||||
|
|
||||||
|
type Emit = {
|
||||||
|
(event: 'click'): void
|
||||||
|
(event: 'itemClick', data: string): void
|
||||||
|
}
|
||||||
|
const emit = defineEmits<Emit>()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
target: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const historyList = ref<SearchHistoryList[]>([])
|
||||||
|
const historyVisible = ref(false)
|
||||||
|
const deleteLoading = ref(false)
|
||||||
|
const showEmpty = ref(false)
|
||||||
|
|
||||||
|
const visibleChange = async (visible: boolean) => {
|
||||||
|
historyVisible.value = visible
|
||||||
|
if (visible) {
|
||||||
|
const resp = await getSearchHistory(props.target)
|
||||||
|
if (resp.success && resp.result.length) {
|
||||||
|
historyList.value = resp.result.filter(item => item.content)
|
||||||
|
showEmpty.value = false
|
||||||
|
} else {
|
||||||
|
showEmpty.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const click = () => {
|
||||||
|
emit('click')
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemClick = (content: string) => {
|
||||||
|
historyVisible.value = false
|
||||||
|
emit('itemClick', content)
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteHistory = async (id: string) => {
|
||||||
|
deleteLoading.value = true
|
||||||
|
const resp = await deleteSearchHistory(props.target, id)
|
||||||
|
deleteLoading.value = false
|
||||||
|
if (resp.success) {
|
||||||
|
historyVisible.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang='less'>
|
||||||
|
.history-empty {
|
||||||
|
width: 200px;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: @box-shadow-base;
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
max-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-item {
|
||||||
|
width: 200px;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete {
|
||||||
|
padding: 0 6px;
|
||||||
|
flex: 0 0 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -6,6 +6,7 @@
|
||||||
:options='typeOptions'
|
:options='typeOptions'
|
||||||
v-model:value='termsModel.type'
|
v-model:value='termsModel.type'
|
||||||
style='width: 100%;'
|
style='width: 100%;'
|
||||||
|
@change='valueChange'
|
||||||
/>
|
/>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
{{
|
{{
|
||||||
|
@ -17,65 +18,86 @@
|
||||||
class='JSearch-item--column'
|
class='JSearch-item--column'
|
||||||
:options='columnOptions'
|
:options='columnOptions'
|
||||||
v-model:value='termsModel.column'
|
v-model:value='termsModel.column'
|
||||||
|
@change='columnChange'
|
||||||
/>
|
/>
|
||||||
<a-select
|
<a-select
|
||||||
class='JSearch-item--termType'
|
class='JSearch-item--termType'
|
||||||
:options='termTypeOptions'
|
:options='termTypeOptions.option'
|
||||||
v-model:value='termsModel.termType'
|
v-model:value='termsModel.termType'
|
||||||
|
@change='termTypeChange'
|
||||||
/>
|
/>
|
||||||
<div class='JSearch-item--value'>
|
<div class='JSearch-item--value'>
|
||||||
<a-input
|
<a-input
|
||||||
v-if='component === componentType.input'
|
v-if='component === componentType.input'
|
||||||
v-model:value='termsModel.value'
|
v-model:value='termsModel.value'
|
||||||
@change='(v) => valueChange(v)'
|
style='width: 100%'
|
||||||
|
@change='valueChange'
|
||||||
/>
|
/>
|
||||||
<a-select
|
<a-select
|
||||||
v-else-if='component === componentType.select'
|
v-else-if='component === componentType.select'
|
||||||
|
showSearch
|
||||||
|
:loading='optionLoading'
|
||||||
v-model:value='termsModel.value'
|
v-model:value='termsModel.value'
|
||||||
:options='options'
|
:options='options'
|
||||||
@change='(v) => valueChange(v)'
|
style='width: 100%'
|
||||||
|
:filterOption='(v, option) => filterTreeSelectNode(v, option, "label")'
|
||||||
|
@change='valueChange'
|
||||||
/>
|
/>
|
||||||
<a-inputnumber
|
<a-input-number
|
||||||
v-else-if='component === componentType.inputNumber'
|
v-else-if='component === componentType.inputNumber'
|
||||||
v-model:value='termsModel.value'
|
v-model:value='termsModel.value'
|
||||||
@change='(v) => valueChange(v)'
|
style='width: 100%'
|
||||||
|
@change='valueChange'
|
||||||
/>
|
/>
|
||||||
<a-input-password
|
<a-input-password
|
||||||
v-else-if='component === componentType.password'
|
v-else-if='component === componentType.password'
|
||||||
v-model:value='termsModel.value'
|
v-model:value='termsModel.value'
|
||||||
@change='(v) => valueChange(v)'
|
style='width: 100%'
|
||||||
|
@change='valueChange'
|
||||||
/>
|
/>
|
||||||
<a-switch
|
<a-switch
|
||||||
v-else-if='component === componentType.switch'
|
v-else-if='component === componentType.switch'
|
||||||
v-model:checked='termsModel.value'
|
v-model:checked='termsModel.value'
|
||||||
@change='(v) => valueChange(v)'
|
style='width: 100%'
|
||||||
|
@change='valueChange'
|
||||||
/>
|
/>
|
||||||
<a-radio-group
|
<a-radio-group
|
||||||
v-else-if='component === componentType.radio'
|
v-else-if='component === componentType.radio'
|
||||||
v-model:value='termsModel.value'
|
v-model:value='termsModel.value'
|
||||||
@change='(v) => valueChange(v)'
|
style='width: 100%'
|
||||||
|
@change='valueChange'
|
||||||
/>
|
/>
|
||||||
<a-checkbox-group
|
<a-checkbox-group
|
||||||
v-else-if='component === componentType.checkbox'
|
v-else-if='component === componentType.checkbox'
|
||||||
v-model:value='termsModel.value'
|
v-model:value='termsModel.value'
|
||||||
:options='options'
|
:options='options'
|
||||||
@change='(v) => valueChange(v)'
|
style='width: 100%'
|
||||||
|
@change='valueChange'
|
||||||
/>
|
/>
|
||||||
<a-time-picker
|
<a-time-picker
|
||||||
v-else-if='component === componentType.time'
|
v-else-if='component === componentType.time'
|
||||||
|
valueFormat='HH:mm:ss'
|
||||||
v-model:value='termsModel.value'
|
v-model:value='termsModel.value'
|
||||||
@change='(v) => valueChange(v)'
|
style='width: 100%'
|
||||||
|
@change='valueChange'
|
||||||
/>
|
/>
|
||||||
<a-date-picker
|
<a-date-picker
|
||||||
v-else-if='component === componentType.date'
|
v-else-if='component === componentType.date'
|
||||||
|
showTime
|
||||||
v-model:value='termsModel.value'
|
v-model:value='termsModel.value'
|
||||||
@change='(v) => valueChange(v)'
|
valueFormat='YYYY-MM-DD HH:mm:ss'
|
||||||
|
style='width: 100%'
|
||||||
|
@change='valueChange'
|
||||||
/>
|
/>
|
||||||
<a-tree-select
|
<a-tree-select
|
||||||
v-else-if='component === componentType.tree'
|
v-else-if='component === componentType.treeSelect'
|
||||||
|
showSearch
|
||||||
v-model:value='termsModel.value'
|
v-model:value='termsModel.value'
|
||||||
:tree-data='options'
|
:tree-data='options'
|
||||||
@change='(v) => valueChange(v)'
|
style='width: 100%'
|
||||||
|
:fieldNames='{ label: "name", value: "id" }'
|
||||||
|
@change='valueChange'
|
||||||
|
:filterTreeNode='(v, option) => filterSelectNode(v, option)'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -85,18 +107,19 @@
|
||||||
import { componentType } from 'components/Form'
|
import { componentType } from 'components/Form'
|
||||||
import { typeOptions, termType } from './util'
|
import { typeOptions, termType } from './util'
|
||||||
import { PropType } from 'vue'
|
import { PropType } from 'vue'
|
||||||
import type { SearchItemProps, SearchItemData } from './types'
|
import type { SearchItemData, SearchProps, Terms } from './types'
|
||||||
import { cloneDeep } from 'lodash-es'
|
import { cloneDeep, get, isArray, isFunction } from 'lodash-es'
|
||||||
|
import { filterTreeSelectNode, filterSelectNode } from '@/utils/comm'
|
||||||
|
|
||||||
type ItemDataProps = Omit<SearchItemData, 'title'>
|
type ItemType = SearchProps['type']
|
||||||
|
|
||||||
interface Emit {
|
interface Emit {
|
||||||
(e: 'change', data: ItemDataProps): void
|
(e: 'change', data: SearchItemData): void
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
columns: {
|
columns: {
|
||||||
type: Array as PropType<SearchItemProps[]>,
|
type: Array as PropType<SearchProps[]>,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
@ -107,66 +130,146 @@ const props = defineProps({
|
||||||
expand: {
|
expand: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
termsItem: {
|
||||||
|
type: Object as PropType<Terms>,
|
||||||
|
default: {}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<Emit>()
|
const emit = defineEmits<Emit>()
|
||||||
|
|
||||||
const termsModel = reactive<ItemDataProps>({
|
const termsModel = reactive<SearchItemData>({
|
||||||
type: 'or',
|
type: 'or',
|
||||||
value: '',
|
value: '',
|
||||||
termType: 'eq',
|
termType: 'like',
|
||||||
column: ''
|
column: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const component = ref(componentType.input)
|
const component = ref(componentType.input)
|
||||||
|
|
||||||
const options = ref([])
|
const options = ref<any[]>([])
|
||||||
|
|
||||||
const columnOptions = ref<({ label: string, value: string})[]>([])
|
const columnOptions = ref<({ label: string, value: string})[]>([])
|
||||||
const columnOptionMap = new Map()
|
const columnOptionMap = new Map()
|
||||||
|
|
||||||
const termTypeOptions = reactive(termType)
|
const termTypeOptions = reactive({option: termType})
|
||||||
|
|
||||||
const getTermType = (type: string) => {
|
const optionLoading = ref(false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据类型切换默termType值
|
||||||
|
* @param type
|
||||||
|
*/
|
||||||
|
const getTermType = (type?: ItemType) => {
|
||||||
|
termTypeOptions.option = termType
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'select':
|
case 'select':
|
||||||
case 'treeSelect':
|
case 'treeSelect':
|
||||||
|
case 'number':
|
||||||
return 'eq'
|
return 'eq'
|
||||||
case 'date':
|
case 'date':
|
||||||
case 'time':
|
case 'time':
|
||||||
|
// 时间只有大于或小于两个值
|
||||||
|
termTypeOptions.option = termType.filter(item => ['gt','lt'].includes(item.value))
|
||||||
return 'gt'
|
return 'gt'
|
||||||
default:
|
default:
|
||||||
return 'like'
|
return 'like'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据类型返回组件
|
||||||
|
* @param type
|
||||||
|
*/
|
||||||
|
const getComponent = (type?: ItemType) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'select':
|
||||||
|
component.value = componentType.select
|
||||||
|
break;
|
||||||
|
case 'treeSelect':
|
||||||
|
component.value = componentType.treeSelect
|
||||||
|
break;
|
||||||
|
case 'date':
|
||||||
|
component.value = componentType.date
|
||||||
|
break;
|
||||||
|
case 'time':
|
||||||
|
component.value = componentType.time
|
||||||
|
break;
|
||||||
|
case 'number':
|
||||||
|
component.value = componentType.inputNumber
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
component.value = componentType.input
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleItemOptions = (option?: any[] | Function) => {
|
||||||
|
options.value = []
|
||||||
|
if (isArray(option)) {
|
||||||
|
options.value = option
|
||||||
|
} else if (isFunction(option)) {
|
||||||
|
optionLoading.value = true
|
||||||
|
option().then((res: any[]) => {
|
||||||
|
optionLoading.value = false
|
||||||
|
options.value = res
|
||||||
|
}).catch((_: any) => {
|
||||||
|
optionLoading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const columnChange = (value: string, isChange: boolean) => {
|
||||||
|
const item = columnOptionMap.get(value)
|
||||||
|
optionLoading.value = false
|
||||||
|
// 设置value为undefined
|
||||||
|
termsModel.column = value
|
||||||
|
termsModel.termType = item.defaultTermType || getTermType(item.type)
|
||||||
|
|
||||||
|
getComponent(item.type) // 处理Item的组件类型
|
||||||
|
|
||||||
|
// 处理options 以及 request
|
||||||
|
if ('options' in item) {
|
||||||
|
handleItemOptions(item.options)
|
||||||
|
}
|
||||||
|
|
||||||
|
termsModel.value = undefined
|
||||||
|
|
||||||
|
if (isChange) {
|
||||||
|
valueChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleItem = () => {
|
const handleItem = () => {
|
||||||
columnOptionMap.clear()
|
columnOptionMap.clear()
|
||||||
columnOptions.value = []
|
columnOptions.value = []
|
||||||
if (!props.columns.length) return
|
if (!props.columns.length) return
|
||||||
|
|
||||||
// 获取第一个值
|
columnOptions.value = props.columns.map(item => { // 对columns进行Map处理以及值处理
|
||||||
|
|
||||||
const sortColumn = cloneDeep(props.columns)
|
|
||||||
sortColumn?.sort((a, b) => a.sortIndex! - b.sortIndex!)
|
|
||||||
|
|
||||||
const _index = props.index > sortColumn.length ? sortColumn.length - 1 : props.index
|
|
||||||
const _itemColumn = sortColumn[_index - 1]
|
|
||||||
|
|
||||||
termsModel.column = _itemColumn.column
|
|
||||||
termsModel.termType = _itemColumn.defaultTermType || getTermType(_itemColumn.type as string)
|
|
||||||
|
|
||||||
columnOptions.value = props.columns.map(item => {
|
|
||||||
columnOptionMap.set(item.column, item)
|
columnOptionMap.set(item.column, item)
|
||||||
return {
|
return {
|
||||||
label: item.title,
|
label: item.title,
|
||||||
value: item.column
|
value: item.column
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 获取第一个值
|
||||||
|
const sortColumn = cloneDeep(props.columns)
|
||||||
|
sortColumn?.sort((a, b) => a.sortIndex! - b.sortIndex!)
|
||||||
|
|
||||||
|
const _index = props.index > sortColumn.length ? sortColumn.length - 1 : props.index
|
||||||
|
const _itemColumn = sortColumn[_index - 1]
|
||||||
|
|
||||||
|
columnChange(_itemColumn.column, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const valueChange = (value: any) => {
|
const termTypeChange = () => {
|
||||||
|
valueChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
const valueChange = () => {
|
||||||
|
|
||||||
emit('change', {
|
emit('change', {
|
||||||
type: termsModel.type,
|
type: termsModel.type,
|
||||||
value: termsModel.value,
|
value: termsModel.value,
|
||||||
|
@ -177,6 +280,27 @@ const valueChange = (value: any) => {
|
||||||
|
|
||||||
handleItem()
|
handleItem()
|
||||||
|
|
||||||
|
watch( props.termsItem, (newValue) => {
|
||||||
|
|
||||||
|
const path = props.index < 4 ? [0, 'terms', props.index - 1] : [1, 'terms', props.index - 4]
|
||||||
|
const itemData: SearchItemData = get(newValue.terms, path)
|
||||||
|
if (itemData) {
|
||||||
|
termsModel.type = itemData.type
|
||||||
|
termsModel.column = itemData.column
|
||||||
|
termsModel.termType = itemData.termType
|
||||||
|
termsModel.value = itemData.value
|
||||||
|
const item = columnOptionMap.get(itemData.column)
|
||||||
|
getComponent(item.type) // 处理Item的组件类型
|
||||||
|
|
||||||
|
// 处理options 以及 request
|
||||||
|
if ('options' in item) {
|
||||||
|
handleItemOptions(item.options)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handleItem()
|
||||||
|
}
|
||||||
|
}, { immediate: true, deep: true })
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang='less'>
|
<style scoped lang='less'>
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
<template>
|
||||||
|
<a-popover
|
||||||
|
title='搜索名称'
|
||||||
|
trigger='click'
|
||||||
|
v-model:visible='visible'
|
||||||
|
@visibleChange='visibleChange'
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div style='width: 240px'>
|
||||||
|
<a-form ref='formRef' :model='modelRef'>
|
||||||
|
<a-form-item
|
||||||
|
name='name'
|
||||||
|
:rules='[
|
||||||
|
{ required: true, message: "请输入名称"}
|
||||||
|
]'
|
||||||
|
>
|
||||||
|
<a-textarea
|
||||||
|
v-model:value='modelRef.name'
|
||||||
|
:rows='3'
|
||||||
|
:maxlength='200'
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<a-button
|
||||||
|
:loading='saveHistoryLoading'
|
||||||
|
type='primary'
|
||||||
|
class='save-btn'
|
||||||
|
@click='saveHistory'
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<a-button>
|
||||||
|
<template #icon>
|
||||||
|
<SaveOutlined />
|
||||||
|
</template>
|
||||||
|
保存
|
||||||
|
</a-button>
|
||||||
|
</a-popover>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang='ts' name='SaveHistory'>
|
||||||
|
import type { Terms } from './types'
|
||||||
|
import { PropType } from 'vue'
|
||||||
|
import { saveSearchHistory } from '@/api/comm'
|
||||||
|
import { SaveOutlined } from '@ant-design/icons-vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
terms: {
|
||||||
|
type: Object as PropType<Terms>,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const searchName = ref('')
|
||||||
|
|
||||||
|
const saveHistoryLoading = ref(false)
|
||||||
|
|
||||||
|
const visible = ref(false)
|
||||||
|
|
||||||
|
const formRef = ref()
|
||||||
|
|
||||||
|
const modelRef = reactive({
|
||||||
|
name: undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存当前查询条件
|
||||||
|
*/
|
||||||
|
const saveHistory = async () => {
|
||||||
|
// 获取当前查询条件并转化为字符串
|
||||||
|
const formData = await formRef.value.validate()
|
||||||
|
if (formData) {
|
||||||
|
formData.content = JSON.stringify(props.terms)
|
||||||
|
saveHistoryLoading.value = true
|
||||||
|
const resp = await saveSearchHistory(formData, props.target)
|
||||||
|
saveHistoryLoading.value = false
|
||||||
|
if (resp.success) {
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const visibleChange = (e: boolean) => {
|
||||||
|
visible.value = e
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang='less'>
|
||||||
|
.save-btn {
|
||||||
|
width: 100%
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -4,9 +4,9 @@
|
||||||
<div v-if='props.type === "advanced"' :class='["JSearch-content senior", expand ? "senior-expand" : "", screenSize ? "big" : "small"]'>
|
<div v-if='props.type === "advanced"' :class='["JSearch-content senior", expand ? "senior-expand" : "", screenSize ? "big" : "small"]'>
|
||||||
<div :class='["JSearch-items", expand ? "items-expand" : "", layout]'>
|
<div :class='["JSearch-items", expand ? "items-expand" : "", layout]'>
|
||||||
<div class='left'>
|
<div class='left'>
|
||||||
<SearchItem :expand='expand' :index='1' :columns='searchItems' />
|
<SearchItem :expand='expand' :index='1' :columns='searchItems' @change='(v) => itemValueChange(v, 1)' :termsItem='terms'/>
|
||||||
<SearchItem v-if='expand' :expand='expand' :index='2' :columns='searchItems' />
|
<SearchItem v-if='expand' :expand='expand' :index='2' :columns='searchItems' @change='(v) => itemValueChange(v, 2)' :termsItem='terms'/>
|
||||||
<SearchItem v-if='expand' :expand='expand' :index='3' :columns='searchItems' />
|
<SearchItem v-if='expand' :expand='expand' :index='3' :columns='searchItems' @change='(v) => itemValueChange(v, 3)' :termsItem='terms'/>
|
||||||
</div>
|
</div>
|
||||||
<div class='center' v-if='expand'>
|
<div class='center' v-if='expand'>
|
||||||
<a-select
|
<a-select
|
||||||
|
@ -16,31 +16,17 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class='right' v-if='expand'>
|
<div class='right' v-if='expand'>
|
||||||
<SearchItem :expand='expand' :index='4' :columns='searchItems' />
|
<SearchItem :expand='expand' :index='4' :columns='searchItems' @change='(v) => itemValueChange(v, 4)' :termsItem='terms'/>
|
||||||
<SearchItem :expand='expand' :index='5' :columns='searchItems' />
|
<SearchItem :expand='expand' :index='5' :columns='searchItems' @change='(v) => itemValueChange(v, 5)' :termsItem='terms'/>
|
||||||
<SearchItem :expand='expand' :index='6' :columns='searchItems' />
|
<SearchItem :expand='expand' :index='6' :columns='searchItems' @change='(v) => itemValueChange(v, 6)' :termsItem='terms'/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div :class='["JSearch-footer", expand ? "expand" : ""]'>
|
<div :class='["JSearch-footer", expand ? "expand" : ""]'>
|
||||||
<div class='JSearch-footer--btns'>
|
<div class='JSearch-footer--btns'>
|
||||||
<a-dropdown-button type="primary">
|
<History :target='target' @click='searchSubmit' @itemClick='historyItemClick' />
|
||||||
搜索
|
<SaveHistory :terms='terms' :target='target'/>
|
||||||
<template #overlay>
|
<a-button @click='reset'>
|
||||||
<a-menu v-if='!!historyList.length'>
|
<template #icon><RedoOutlined /></template>
|
||||||
<a-menu-item>
|
|
||||||
|
|
||||||
</a-menu-item>
|
|
||||||
</a-menu>
|
|
||||||
<a-empty v-else />
|
|
||||||
</template>
|
|
||||||
<template #icon><SearchOutlined /></template>
|
|
||||||
</a-dropdown-button>
|
|
||||||
<a-button>
|
|
||||||
<template #icon><PoweroffOutlined /></template>
|
|
||||||
保存
|
|
||||||
</a-button>
|
|
||||||
<a-button>
|
|
||||||
<template #icon><PoweroffOutlined /></template>
|
|
||||||
重置
|
重置
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -54,17 +40,17 @@
|
||||||
<div v-else class='JSearch-content simple big'>
|
<div v-else class='JSearch-content simple big'>
|
||||||
<div class='JSearch-items'>
|
<div class='JSearch-items'>
|
||||||
<div class='left'>
|
<div class='left'>
|
||||||
<SearchItem :expand='false' :index='1' />
|
<SearchItem :expand='false' :index='1' :columns='searchItems' @change='(v) => itemValueChange(v, 1)' :termsItem='terms'/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class='JSearch-footer'>
|
<div class='JSearch-footer'>
|
||||||
<div class='JSearch-footer--btns'>
|
<div class='JSearch-footer--btns'>
|
||||||
<a-button type="primary">
|
<a-button type="primary" @click='searchSubmit'>
|
||||||
<template #icon><SearchOutlined /></template>
|
<template #icon><SearchOutlined /></template>
|
||||||
搜索
|
搜索
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button>
|
<a-button @click='reset'>
|
||||||
<template #icon><PoweroffOutlined /></template>
|
<template #icon><RedoOutlined /></template>
|
||||||
重置
|
重置
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -76,18 +62,25 @@
|
||||||
<script setup lang='ts' name='Search'>
|
<script setup lang='ts' name='Search'>
|
||||||
import SearchItem from './Item.vue'
|
import SearchItem from './Item.vue'
|
||||||
import { typeOptions } from './util'
|
import { typeOptions } from './util'
|
||||||
import { useElementSize } from '@vueuse/core'
|
import { useElementSize, useUrlSearchParams } from '@vueuse/core'
|
||||||
import { omit } from 'lodash-es'
|
import { cloneDeep, isFunction, isString, set } from 'lodash-es'
|
||||||
import { SearchOutlined, DownOutlined } from '@ant-design/icons-vue';
|
import { SearchOutlined, DownOutlined, RedoOutlined } from '@ant-design/icons-vue';
|
||||||
import type { SearchItemProps } from './types'
|
|
||||||
import { PropType } from 'vue'
|
import { PropType } from 'vue'
|
||||||
import { JColumnsProps } from 'components/Table/types'
|
import { JColumnsProps } from 'components/Table/types'
|
||||||
|
import SaveHistory from './SaveHistory.vue'
|
||||||
|
import History from './History.vue'
|
||||||
|
import type { SearchItemData, SearchProps, Terms } from './types'
|
||||||
|
|
||||||
|
type UrlParam = {
|
||||||
|
q: string | null
|
||||||
|
target: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emit {
|
||||||
|
(e: 'search', data: Terms): void
|
||||||
|
}
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
defaultParams: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
columns: {
|
columns: {
|
||||||
type: Array as PropType<JColumnsProps[]>,
|
type: Array as PropType<JColumnsProps[]>,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
|
@ -97,7 +90,7 @@ const props = defineProps({
|
||||||
type: String,
|
type: String,
|
||||||
default: 'advanced'
|
default: 'advanced'
|
||||||
},
|
},
|
||||||
key: {
|
target: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
required: true
|
required: true
|
||||||
|
@ -107,11 +100,13 @@ const props = defineProps({
|
||||||
const searchRef = ref(null)
|
const searchRef = ref(null)
|
||||||
const { width } = useElementSize(searchRef)
|
const { width } = useElementSize(searchRef)
|
||||||
|
|
||||||
|
const urlParams = useUrlSearchParams<UrlParam>('hash')
|
||||||
|
|
||||||
// 是否展开更多筛选
|
// 是否展开更多筛选
|
||||||
const expand = ref(false)
|
const expand = ref(false)
|
||||||
|
|
||||||
// 第一组,第二组的关系
|
// 第一组,第二组的关系
|
||||||
const termType = ref('or')
|
const termType = ref('and')
|
||||||
// 搜索历史记录
|
// 搜索历史记录
|
||||||
const historyList = ref([])
|
const historyList = ref([])
|
||||||
|
|
||||||
|
@ -120,7 +115,13 @@ const layout = ref('horizontal')
|
||||||
// 当前组件宽度 true 大于1000
|
// 当前组件宽度 true 大于1000
|
||||||
const screenSize = ref(true)
|
const screenSize = ref(true)
|
||||||
|
|
||||||
const searchItems = ref<SearchItemProps[]>([])
|
const searchItems = ref<SearchProps[]>([])
|
||||||
|
// 当前查询条件
|
||||||
|
const terms = reactive<Terms>({ terms: [] })
|
||||||
|
|
||||||
|
const columnOptionMap = new Map()
|
||||||
|
|
||||||
|
const emit = defineEmits<Emit>()
|
||||||
|
|
||||||
const expandChange = () => {
|
const expandChange = () => {
|
||||||
expand.value = !expand.value
|
expand.value = !expand.value
|
||||||
|
@ -132,10 +133,12 @@ const searchParams = reactive({
|
||||||
|
|
||||||
const handleItems = () => {
|
const handleItems = () => {
|
||||||
searchItems.value = []
|
searchItems.value = []
|
||||||
|
columnOptionMap.clear()
|
||||||
props.columns!.forEach((item, index) => {
|
props.columns!.forEach((item, index) => {
|
||||||
if (item.search && Object.keys(item.search).length) {
|
if (item.search && Object.keys(item.search).length) {
|
||||||
|
columnOptionMap.set(item.dataIndex, item.search)
|
||||||
searchItems.value.push({
|
searchItems.value.push({
|
||||||
...omit(item.search, ['rename']),
|
...item.search,
|
||||||
sortIndex: item.search.first ? 0 : index + 1,
|
sortIndex: item.search.first ? 0 : index + 1,
|
||||||
title: item.title,
|
title: item.title,
|
||||||
column: item.dataIndex,
|
column: item.dataIndex,
|
||||||
|
@ -144,6 +147,92 @@ const handleItems = () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const itemValueChange = (value: SearchItemData, index: number) => {
|
||||||
|
if (index < 4) { // 第一组数据
|
||||||
|
set(terms.terms, [0, 'terms', index - 1], value)
|
||||||
|
} else { // 第二组数据
|
||||||
|
set(terms.terms, [1, 'terms', index - 4], value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const addUrlParams = () => {
|
||||||
|
urlParams.q = JSON.stringify(terms)
|
||||||
|
urlParams.target = props.target
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理termType为like,nlike的值
|
||||||
|
* @param v
|
||||||
|
*/
|
||||||
|
const handleLikeValue = (v: string) => {
|
||||||
|
if (isString(v)) {
|
||||||
|
return v.split('').reduce((pre: string, next: string) => {
|
||||||
|
let _next = next
|
||||||
|
if (next === '\\') {
|
||||||
|
_next = '\\\\'
|
||||||
|
} else if (next === '%') {
|
||||||
|
_next = '\\%'
|
||||||
|
}
|
||||||
|
return pre + _next
|
||||||
|
}, '')
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理为外部使用
|
||||||
|
*/
|
||||||
|
const handleParamsFormat = () => {
|
||||||
|
// 过滤掉terms中value无效的item
|
||||||
|
const cloneParams = cloneDeep(terms)
|
||||||
|
return {
|
||||||
|
terms: cloneParams.terms.map(item => {
|
||||||
|
if (item.terms) {
|
||||||
|
item.terms = item.terms.filter(iItem => iItem && iItem.value)
|
||||||
|
.map(iItem => {
|
||||||
|
// 处理handleValue和rename
|
||||||
|
const _item = columnOptionMap.get(iItem.column)
|
||||||
|
if (_item.rename) {
|
||||||
|
iItem.column = _item.rename
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_item.handleValue && isFunction(_item.handleValue)) {
|
||||||
|
iItem.value = _item.handleValue(iItem.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['like','nlike'].includes(iItem.termType) && !!iItem.value) {
|
||||||
|
iItem.value = `%${handleLikeValue(iItem.value)}%`
|
||||||
|
}
|
||||||
|
return iItem
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交
|
||||||
|
*/
|
||||||
|
const searchSubmit = () => {
|
||||||
|
emit('search', handleParamsFormat())
|
||||||
|
if (props.type === 'advanced') {
|
||||||
|
addUrlParams()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置查询
|
||||||
|
*/
|
||||||
|
const reset = () => {
|
||||||
|
terms.terms = []
|
||||||
|
expand.value = false
|
||||||
|
if (props.type === 'advanced') {
|
||||||
|
urlParams.q = null
|
||||||
|
urlParams.target = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
watch(width, (value) => {
|
watch(width, (value) => {
|
||||||
if (value < 1000) {
|
if (value < 1000) {
|
||||||
layout.value = 'vertical'
|
layout.value = 'vertical'
|
||||||
|
@ -154,6 +243,41 @@ watch(width, (value) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const historyItemClick = (content: string) => {
|
||||||
|
try {
|
||||||
|
terms.terms = JSON.parse(content)?.terms || []
|
||||||
|
if (terms.terms.length === 2) {
|
||||||
|
expand.value = true
|
||||||
|
}
|
||||||
|
addUrlParams()
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`Search组件中handleUrlParams处理JSON时异常:【${e}】`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理URL中的查询参数
|
||||||
|
* @param _params
|
||||||
|
*/
|
||||||
|
const handleUrlParams = (_params: UrlParam) => {
|
||||||
|
// URL中的target和props的一致,则还原查询参数
|
||||||
|
if (_params.target === props.target && _params.q) {
|
||||||
|
try {
|
||||||
|
terms.terms = JSON.parse(_params.q)?.terms || []
|
||||||
|
if (terms.terms.length === 2) {
|
||||||
|
expand.value = true
|
||||||
|
}
|
||||||
|
emit('search', handleParamsFormat())
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`Search组件中handleUrlParams处理JSON时异常:【${e}】`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
handleUrlParams(urlParams)
|
||||||
|
})
|
||||||
|
|
||||||
handleItems()
|
handleItems()
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -253,4 +377,5 @@ handleItems()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
|
@ -0,0 +1,107 @@
|
||||||
|
# Search组件
|
||||||
|
|
||||||
|
- 需要结合Table使用
|
||||||
|
|
||||||
|
## 属性
|
||||||
|
|
||||||
|
| 名称 | 说明 | 类型 | 默认值 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| columns | 查询下拉列表 | JColumnsProps[] | [] |
|
||||||
|
| type | 查询模式 | 'advanced', 'simple' | 'advanced' |
|
||||||
|
| target | 查询组件唯一key | String | |
|
||||||
|
| search | 查询回调事件 | Function | |
|
||||||
|
|
||||||
|
> JColumnsProps[*].search
|
||||||
|
|
||||||
|
| 名称 | 说明 | 类型 | 默认值 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| rename | 用来重命名查询字段值 | String | |
|
||||||
|
| type | 查询值组件类型 | 'select', 'number', 'string', 'treeSelect', 'date', 'time' | |
|
||||||
|
| options | Select和TreeSelect组件下拉值 | Array, Promise | |
|
||||||
|
| first | 控制查询字段下拉默认值,默认为name即名称 | Boolean | |
|
||||||
|
| defaultTermType | 查询条件 | String | |
|
||||||
|
| handleValue | 处理单个查询value值 | Function | |
|
||||||
|
|
||||||
|
## 基础用法
|
||||||
|
|
||||||
|
> columns中包含search属性才会出现在查询下拉中
|
||||||
|
|
||||||
|
```vue
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
search: {
|
||||||
|
type: 'string',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const search = (params) => {
|
||||||
|
}
|
||||||
|
<Search
|
||||||
|
:columns='columns'
|
||||||
|
target='device'
|
||||||
|
@search='search'
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
> rename的作用在于search抛出params会根据rename修改数据中column的值
|
||||||
|
|
||||||
|
```vue
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
search: {
|
||||||
|
type: 'string',
|
||||||
|
rename: 'TestName'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const search = (params) => {
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
column: 'TestName',
|
||||||
|
value: '',
|
||||||
|
termType: 'like'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
<Search
|
||||||
|
:columns='columns'
|
||||||
|
target='device'
|
||||||
|
@search='search'
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
> defaultTermType的作用在于设置查询条件,相关条件参考util中的termType
|
||||||
|
|
||||||
|
```vue
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
search: {
|
||||||
|
type: 'string',
|
||||||
|
defaultTermType: 'gt'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const search = (params) => {
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
column: 'TestName',
|
||||||
|
value: '',
|
||||||
|
termType: 'gt'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
<Search
|
||||||
|
:columns='columns'
|
||||||
|
target='device'
|
||||||
|
@search='search'
|
||||||
|
/>
|
||||||
|
```
|
|
@ -1,4 +1,4 @@
|
||||||
export interface SearchProps {
|
export interface SearchBaseProps {
|
||||||
rename?: string
|
rename?: string
|
||||||
type?: 'select' | 'number' | 'string' | 'treeSelect' | 'date' | 'time'
|
type?: 'select' | 'number' | 'string' | 'treeSelect' | 'date' | 'time'
|
||||||
format?: string
|
format?: string
|
||||||
|
@ -7,15 +7,43 @@ export interface SearchProps {
|
||||||
defaultTermType?: string // 默认 eq
|
defaultTermType?: string // 默认 eq
|
||||||
title?: ColumnType.title
|
title?: ColumnType.title
|
||||||
sortIndex?: number
|
sortIndex?: number
|
||||||
|
handleValue?: (value: SearchItemData) => any
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchItemProps {
|
||||||
|
rename?: SearchBaseProps['rename']
|
||||||
|
title: string
|
||||||
|
column: ColumnType.dataIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchItemData {
|
export interface SearchItemData {
|
||||||
column: ColumnType.dataIndex
|
column: ColumnType.dataIndex
|
||||||
rename?: string
|
|
||||||
value: any
|
value: any
|
||||||
termType: string
|
termType: string
|
||||||
type?: string
|
type?: string
|
||||||
title: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchItemProps extends SearchProps, SearchItemData {}
|
export interface TermsItem {
|
||||||
|
terms: SearchItemData[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Terms {
|
||||||
|
terms: TermsItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SortItem {
|
||||||
|
name: string
|
||||||
|
order?: 'desc' | 'asc'
|
||||||
|
value?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchHistoryList {
|
||||||
|
content?: string
|
||||||
|
name: string
|
||||||
|
id: string
|
||||||
|
key: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchProps extends SearchBaseProps, SearchItemProps {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -204,9 +204,13 @@ const JTable = defineComponent<JTableProps>({
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
watchEffect(() => {
|
watch(
|
||||||
handleSearch(props.params)
|
() => props.params,
|
||||||
})
|
(newValue) => {
|
||||||
|
handleSearch(newValue)
|
||||||
|
},
|
||||||
|
{deep: true, immediate: true}
|
||||||
|
)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.onresize = () => {
|
window.onresize = () => {
|
||||||
|
@ -266,7 +270,7 @@ const JTable = defineComponent<JTableProps>({
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
emit('cancelSelect')
|
emit('cancelSelect')
|
||||||
}}
|
}}
|
||||||
closeText={<a>取消选择</a>}
|
closeText={<a-button type="link">取消选择</a-button>}
|
||||||
/>
|
/>
|
||||||
</div> : null
|
</div> : null
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { SearchItemProps } from 'components/Search/types'
|
import { SearchProps } from 'components/Search/types'
|
||||||
import { ColumnType } from 'ant-design-vue/es/table'
|
import { ColumnType } from 'ant-design-vue/es/table'
|
||||||
|
|
||||||
export interface JColumnsProps extends ColumnType{
|
export interface JColumnsProps extends ColumnType{
|
||||||
scopedSlots?: boolean;
|
scopedSlots?: boolean;
|
||||||
search: SearchItemProps
|
search: SearchProps
|
||||||
}
|
}
|
|
@ -45,7 +45,7 @@
|
||||||
<template #addonAfter>
|
<template #addonAfter>
|
||||||
<a-upload
|
<a-upload
|
||||||
name="file"
|
name="file"
|
||||||
:action="action"
|
:action="FILE_UPLOAD"
|
||||||
:headers="headers"
|
:headers="headers"
|
||||||
:showUploadList="false"
|
:showUploadList="false"
|
||||||
@change="handleFileChange"
|
@change="handleFileChange"
|
||||||
|
@ -89,6 +89,7 @@ import GeoComponent from '@/components/GeoComponent/index.vue';
|
||||||
import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
|
import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
|
||||||
import { LocalStore } from '@/utils/comm';
|
import { LocalStore } from '@/utils/comm';
|
||||||
import { ItemData, ITypes } from './types';
|
import { ItemData, ITypes } from './types';
|
||||||
|
import { FILE_UPLOAD } from '@/api/comm';
|
||||||
|
|
||||||
type Emits = {
|
type Emits = {
|
||||||
(e: 'update:modelValue', data: string | number | boolean): void;
|
(e: 'update:modelValue', data: string | number | boolean): void;
|
||||||
|
@ -161,7 +162,6 @@ const handleItemModalSubmit = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// 文件上传
|
// 文件上传
|
||||||
const action = ref<string>(`${BASE_API_PATH}/file/static`);
|
|
||||||
const headers = ref({ [TOKEN_KEY]: LocalStore.get(TOKEN_KEY) });
|
const headers = ref({ [TOKEN_KEY]: LocalStore.get(TOKEN_KEY) });
|
||||||
const handleFileChange = (info: UploadChangeParam<UploadFile<any>>) => {
|
const handleFileChange = (info: UploadChangeParam<UploadFile<any>>) => {
|
||||||
if (info.file.status === 'done') {
|
if (info.file.status === 'done') {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { TOKEN_KEY } from '@/utils/variable'
|
import { TOKEN_KEY } from '@/utils/variable'
|
||||||
|
import { Terms } from 'components/Search/types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 静态图片资源处理
|
* 静态图片资源处理
|
||||||
|
@ -35,4 +36,24 @@ export const LocalStore = {
|
||||||
|
|
||||||
export const getToken = () => {
|
export const getToken = () => {
|
||||||
return LocalStore.get(TOKEN_KEY)
|
return LocalStore.get(TOKEN_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TreeSelect过滤
|
||||||
|
* @param value 过滤值
|
||||||
|
* @param treeNode
|
||||||
|
* @param key
|
||||||
|
*/
|
||||||
|
export const filterTreeSelectNode = (value: string, treeNode: any, key: string = 'name'): boolean => {
|
||||||
|
return treeNode[key]?.includes(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select过滤
|
||||||
|
* @param value 过滤值
|
||||||
|
* @param option
|
||||||
|
* @param key
|
||||||
|
*/
|
||||||
|
export const filterSelectNode = (value: string, option: any, key: string = 'label'): boolean => {
|
||||||
|
return option[key]?.includes(value)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
export default function encodeQuery(params: any) {
|
||||||
|
if (!params) return {};
|
||||||
|
const queryParam = {
|
||||||
|
// pageIndex: 0,
|
||||||
|
current: params.current,
|
||||||
|
};
|
||||||
|
const { terms, sorts } = params;
|
||||||
|
Object.keys(params).forEach((key: string) => {
|
||||||
|
if (key === 'terms') {
|
||||||
|
let index = 0;
|
||||||
|
if (!terms) return;
|
||||||
|
Object.keys(terms).forEach((k: string) => {
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
terms[k] === '' ||
|
||||||
|
terms[k] === undefined ||
|
||||||
|
terms[k].length === 0 ||
|
||||||
|
terms[k] === {} ||
|
||||||
|
terms[k] === null
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (k.indexOf('$LIKE') > -1 && terms[k].toString().indexOf('%') === -1) {
|
||||||
|
terms[k] = `%${terms[k]}%`;
|
||||||
|
}
|
||||||
|
if (k.indexOf('$IN') > -1) {
|
||||||
|
terms[k] = terms[k].toString();
|
||||||
|
} else if (k.indexOf('$START') > -1) {
|
||||||
|
terms[k] = `%${terms[k]}`;
|
||||||
|
} else if (k.indexOf('$END') > -1) {
|
||||||
|
terms[k] = `${terms[k]}%`;
|
||||||
|
}
|
||||||
|
if (k.indexOf('@') > -1) {
|
||||||
|
const temp = k.split('@');
|
||||||
|
// eslint-disable-next-line prefer-destructuring
|
||||||
|
queryParam[`terms[${index}].column`] = temp[0];
|
||||||
|
// eslint-disable-next-line prefer-destructuring
|
||||||
|
queryParam[`terms[${index}].type`] = temp[1];
|
||||||
|
} else {
|
||||||
|
queryParam[`terms[${index}].column`] = k;
|
||||||
|
}
|
||||||
|
queryParam[`terms[${index}].value`] = terms[k];
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (key === 'sorts') {
|
||||||
|
// 当前Ant Design排序只支持单字段排序
|
||||||
|
if (!sorts) return;
|
||||||
|
Object.keys(sorts).forEach((s, index) => {
|
||||||
|
queryParam[`sorts[${index}].name`] = s;
|
||||||
|
queryParam[`sorts[${index}].order`] = sorts[s].replace('end', '');
|
||||||
|
});
|
||||||
|
// if (Object.keys(sorts).length > 0) {
|
||||||
|
// queryParam[`sorts[0].name`] = sorts.field;
|
||||||
|
// queryParam[`sorts[0].order`] = (sorts.order || '').replace('end', '');
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
queryParam[key] = params[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// queryParam.pageIndex = current - 1;
|
||||||
|
|
||||||
|
return queryParam;
|
||||||
|
}
|
|
@ -1,3 +1,7 @@
|
||||||
|
import moment from "moment";
|
||||||
|
import { LocalStore } from "./comm";
|
||||||
|
import { TOKEN_KEY } from "./variable";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 把数据下载成JSON
|
* 把数据下载成JSON
|
||||||
* @param record
|
* @param record
|
||||||
|
@ -18,4 +22,34 @@ export const downloadObject = (record: Record<string, any>, fileName: string, fo
|
||||||
ghostLink.click();
|
ghostLink.click();
|
||||||
//移除
|
//移除
|
||||||
document.body.removeChild(ghostLink);
|
document.body.removeChild(ghostLink);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载文件
|
||||||
|
* @param url 下载链接
|
||||||
|
* @param params 参数
|
||||||
|
*/
|
||||||
|
export const downloadFile = (url: string, params?: Record<string, any>) => {
|
||||||
|
const formElement = document.createElement('form');
|
||||||
|
formElement.style.display = 'display:none;';
|
||||||
|
formElement.method = 'GET';
|
||||||
|
formElement.action = url;
|
||||||
|
// 添加参数
|
||||||
|
if (params) {
|
||||||
|
Object.keys(params).forEach((key: string) => {
|
||||||
|
const inputElement = document.createElement('input');
|
||||||
|
inputElement.type = 'hidden';
|
||||||
|
inputElement.name = key;
|
||||||
|
inputElement.value = params[key];
|
||||||
|
formElement.appendChild(inputElement);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const inputElement = document.createElement('input');
|
||||||
|
inputElement.type = 'hidden';
|
||||||
|
inputElement.name = ':X_Access_Token';
|
||||||
|
inputElement.value = LocalStore.get(TOKEN_KEY);
|
||||||
|
formElement.appendChild(inputElement);
|
||||||
|
document.body.appendChild(formElement);
|
||||||
|
formElement.submit();
|
||||||
|
document.body.removeChild(formElement);
|
||||||
};
|
};
|
|
@ -2,13 +2,22 @@
|
||||||
<div class='search'>
|
<div class='search'>
|
||||||
<Search
|
<Search
|
||||||
:columns='columns'
|
:columns='columns'
|
||||||
|
target='device-instance-search'
|
||||||
|
@search='search'
|
||||||
|
/>
|
||||||
|
<Search
|
||||||
|
type='simple'
|
||||||
|
:columns='columns'
|
||||||
|
target='product'
|
||||||
|
@search='search'
|
||||||
/>
|
/>
|
||||||
<Search type='simple' />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup name='demoSearch'>
|
<script setup name='demoSearch'>
|
||||||
|
|
||||||
|
import { category } from '../../api/device/product'
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: '名称',
|
title: '名称',
|
||||||
|
@ -17,20 +26,59 @@ const columns = [
|
||||||
search: {
|
search: {
|
||||||
rename: 'deviceId',
|
rename: 'deviceId',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
handValue: (v) => {
|
options: [
|
||||||
|
{
|
||||||
|
label: '测试1',
|
||||||
|
value: 'test1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '测试2',
|
||||||
|
value: 'test2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '测试3',
|
||||||
|
value: 'test3'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
handleValue: (v) => {
|
||||||
return '123'
|
return '123'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '序号',
|
||||||
|
dataIndex: 'sortIndex',
|
||||||
|
key: 'sortIndex',
|
||||||
|
scopedSlots: true,
|
||||||
|
search: {
|
||||||
|
type: 'number',
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
dataIndex: 'id',
|
dataIndex: 'id',
|
||||||
key: 'id',
|
key: 'id',
|
||||||
scopedSlots: true,
|
|
||||||
search: {
|
search: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '时间',
|
||||||
|
dataIndex: 'date',
|
||||||
|
key: 'date',
|
||||||
|
search: {
|
||||||
|
type: 'date',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '时间2',
|
||||||
|
dataIndex: 'date2',
|
||||||
|
key: 'date2',
|
||||||
|
search: {
|
||||||
|
type: 'time',
|
||||||
|
defaultTermType: 'lt'
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '分类',
|
title: '分类',
|
||||||
dataIndex: 'classifiedName',
|
dataIndex: 'classifiedName',
|
||||||
|
@ -38,9 +86,13 @@ const columns = [
|
||||||
search: {
|
search: {
|
||||||
first: true,
|
first: true,
|
||||||
type: 'treeSelect',
|
type: 'treeSelect',
|
||||||
// options: async () => {
|
options: async () => {
|
||||||
// return await
|
return new Promise((res) => {
|
||||||
// }
|
category().then(resp => {
|
||||||
|
res(resp.result)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -51,6 +103,9 @@ const columns = [
|
||||||
scopedSlots: true,
|
scopedSlots: true,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
const search = (params) => {
|
||||||
|
console.log(params)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
<template>
|
||||||
|
<a-modal :maskClosable="false" width="800px" :visible="true" title="导出" @ok="handleOk" @cancel="handleCancel">
|
||||||
|
<div style="background-color: rgb(236, 237, 238)">
|
||||||
|
<p style="padding: 10px">
|
||||||
|
<AIcon type="ExclamationCircleOutlined" />
|
||||||
|
选择单个产品时可导出其下属设备的详细数据,不选择产品时导出所有设备的基础数据。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 20px">
|
||||||
|
<a-form :layout="'vertical'">
|
||||||
|
<a-form-item label="产品">
|
||||||
|
<a-select showSearch v-model:value="modelRef.product" placeholder="请选择产品">
|
||||||
|
<a-select-option :value="item.id" v-for="item in productList" :key="item.id" :title="item.name"></a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="文件格式">
|
||||||
|
<a-radio-group button-style="solid" v-model:value="modelRef.fileType" placeholder="请选择文件格式">
|
||||||
|
<a-radio-button value="xlsx">xlsx</a-radio-button>
|
||||||
|
<a-radio-button value="csv">csv</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { queryNoPagingPost } from '@/api/device/product'
|
||||||
|
import { downloadFile } from '@/utils/utils'
|
||||||
|
import encodeQuery from '@/utils/encodeQuery'
|
||||||
|
import { BASE_API_PATH } from '@/utils/variable'
|
||||||
|
|
||||||
|
const emit = defineEmits(['close'])
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default: undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const modelRef = reactive({
|
||||||
|
product: undefined,
|
||||||
|
fileType: 'xlsx'
|
||||||
|
});
|
||||||
|
|
||||||
|
const productList = ref<Record<string, any>[]>([])
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.data,
|
||||||
|
() => {
|
||||||
|
queryNoPagingPost({paging: false}).then(resp => {
|
||||||
|
if(resp.status === 200){
|
||||||
|
productList.value = resp.result as Record<string, any>[]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{immediate: true, deep: true}
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
const params = encodeQuery(props.data);
|
||||||
|
if(modelRef.product){
|
||||||
|
downloadFile(
|
||||||
|
`${BASE_API_PATH}/device/instance/${modelRef.product}/export.${modelRef.fileType}`,
|
||||||
|
params
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
downloadFile(`${BASE_API_PATH}/device/instance/export.${modelRef.fileType}`, params);
|
||||||
|
}
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,14 @@
|
||||||
|
<template>
|
||||||
|
<a-modal :maskClosable="false" width="800px" :visible="true" title="导入" @ok="handleOk" @cancel="handleCancel">
|
||||||
|
123
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
const handleOk = () => {
|
||||||
|
|
||||||
|
}
|
||||||
|
const handleCancel = () => {
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,51 @@
|
||||||
|
<template>
|
||||||
|
<a-modal :maskClosable="false" width="800px" :visible="true" title="当前进度" @ok="handleOk" @cancel="handleCancel">
|
||||||
|
<div>
|
||||||
|
<a-badge v-if="flag" status="processing" text="进行中" />
|
||||||
|
<a-badge v-else status="success" text="已完成" />
|
||||||
|
</div>
|
||||||
|
<p>总数量:{{count}}</p>
|
||||||
|
<a></a>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { downloadFile } from '@/utils/utils'
|
||||||
|
|
||||||
|
const emit = defineEmits(['close'])
|
||||||
|
const props = defineProps({
|
||||||
|
api: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const eventSource = ref<Record<string, any>>({})
|
||||||
|
const count = ref<number>(0)
|
||||||
|
const flag = ref<boolean>(false)
|
||||||
|
const errMessage = ref<string>('')
|
||||||
|
const isSource = ref<boolean>(false)
|
||||||
|
const id = ref<string>('')
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
|
||||||
|
const getData = () => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.api,
|
||||||
|
() => {
|
||||||
|
getData()
|
||||||
|
},
|
||||||
|
{deep: true, immediate: true}
|
||||||
|
)
|
||||||
|
</script>
|
|
@ -1,5 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<JTable ref="instanceRef" :columns="columns" :request="query" :defaultParams="{sorts: [{name: 'createTime', order: 'desc'}]}" :params="{pageIndex: 0, pageSize: 12}">
|
<JTable
|
||||||
|
ref="instanceRef"
|
||||||
|
:columns="columns"
|
||||||
|
:request="query"
|
||||||
|
:defaultParams="{sorts: [{name: 'createTime', order: 'desc'}]}"
|
||||||
|
:rowSelection="{
|
||||||
|
selectedRowKeys: _selectedRowKeys,
|
||||||
|
onChange: onSelectChange
|
||||||
|
}"
|
||||||
|
@cancelSelect="cancelSelect"
|
||||||
|
:params="params"
|
||||||
|
>
|
||||||
<template #headerTitle>
|
<template #headerTitle>
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-button type="primary" @click="handleAdd">新增</a-button>
|
<a-button type="primary" @click="handleAdd">新增</a-button>
|
||||||
|
@ -8,13 +19,33 @@
|
||||||
<template #overlay>
|
<template #overlay>
|
||||||
<a-menu>
|
<a-menu>
|
||||||
<a-menu-item>
|
<a-menu-item>
|
||||||
<a href="javascript:;">1st menu item</a>
|
<a-button @click="exportVisible = true"><AIcon type="ExportOutlined" />批量导出设备</a-button>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item>
|
<a-menu-item>
|
||||||
<a href="javascript:;">2nd menu item</a>
|
<a-button @click="importVisible = true"><AIcon type="ImportOutlined" />批量导入设备</a-button>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item>
|
<a-menu-item>
|
||||||
<a href="javascript:;">3rd menu item</a>
|
<a-popconfirm @confirm="activeAllDevice" title="确认激活全部设备?">
|
||||||
|
<a-button type="primary" ghost><AIcon type="CheckCircleOutlined" />激活全部设备</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item>
|
||||||
|
<a-button @click="syncDeviceStatus" type="primary"><AIcon type="SyncOutlined" />同步设备状态</a-button>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item v-if="_selectedRowKeys.length">
|
||||||
|
<a-popconfirm @confirm="delSelectedDevice" title="已启用的设备无法删除,确认删除选中的禁用状态设备?">
|
||||||
|
<a-button type="primary" danger><AIcon type="DeleteOutlined" />删除选中设备</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item v-if="_selectedRowKeys.length" title="确认激活选中设备?">
|
||||||
|
<a-popconfirm @confirm="activeSelectedDevice" >
|
||||||
|
<a-button type="primary"><AIcon type="CheckOutlined" />激活选中设备</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item v-if="_selectedRowKeys.length">
|
||||||
|
<a-popconfirm @confirm="disabledSelectedDevice" title="确认禁用选中设备?">
|
||||||
|
<a-button type="primary" danger><AIcon type="StopOutlined" />禁用选中设备</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
</template>
|
</template>
|
||||||
|
@ -24,9 +55,10 @@
|
||||||
<template #card="slotProps">
|
<template #card="slotProps">
|
||||||
<CardBox
|
<CardBox
|
||||||
:value="slotProps"
|
:value="slotProps"
|
||||||
@click="handleView"
|
@click="handleClick"
|
||||||
:actions="getActions(slotProps, 'card')"
|
:actions="getActions(slotProps, 'card')"
|
||||||
v-bind="slotProps"
|
v-bind="slotProps"
|
||||||
|
:active="_selectedRowKeys.includes(slotProps.id)"
|
||||||
:status="slotProps.state.value"
|
:status="slotProps.state.value"
|
||||||
:statusText="slotProps.state.text"
|
:statusText="slotProps.state.text"
|
||||||
:statusNames="{
|
:statusNames="{
|
||||||
|
@ -41,7 +73,7 @@
|
||||||
</slot>
|
</slot>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<h3>{{ slotProps.name }}</h3>
|
<h3 @click="handleView(slotProps.id)">{{ slotProps.name }}</h3>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<div class="card-item-content-text">设备类型</div>
|
<div class="card-item-content-text">设备类型</div>
|
||||||
|
@ -116,15 +148,30 @@
|
||||||
</a-space>
|
</a-space>
|
||||||
</template>
|
</template>
|
||||||
</JTable>
|
</JTable>
|
||||||
|
<Import v-if="importVisible" @close="importVisible = false" />
|
||||||
|
<Export v-if="exportVisible" @close="exportVisible = false" :data="params" />
|
||||||
|
<Process v-if="operationVisible" @close="operationVisible = false" :api="api" :type="type" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { query, _delete, _deploy, _undeploy } from '@/api/device/instance'
|
import { query, _delete, _deploy, _undeploy, batchUndeployDevice, batchDeployDevice, batchDeleteDevice } from '@/api/device/instance'
|
||||||
import type { ActionsType } from '@/components/Table/index.vue'
|
import type { ActionsType } from '@/components/Table/index.vue'
|
||||||
import { getImage } from '@/utils/comm';
|
import { getImage, LocalStore } from '@/utils/comm';
|
||||||
import { message } from "ant-design-vue";
|
import { message } from "ant-design-vue";
|
||||||
|
import Import from './Import/index.vue'
|
||||||
|
import Export from './Export/index.vue'
|
||||||
|
import Process from './Process/index.vue'
|
||||||
|
import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
|
||||||
|
|
||||||
const instanceRef = ref<Record<string, any>>({});
|
const instanceRef = ref<Record<string, any>>({});
|
||||||
|
const params = ref<Record<string, any>>({pageIndex: 0, pageSize: 12})
|
||||||
|
const _selectedRowKeys = ref<string[]>([])
|
||||||
|
const importVisible = ref<boolean>(false)
|
||||||
|
const exportVisible = ref<boolean>(false)
|
||||||
|
const current = ref<Record<string, any>>({})
|
||||||
|
const operationVisible = ref<boolean>(false)
|
||||||
|
const api = ref<string>('')
|
||||||
|
const type = ref<string>('')
|
||||||
|
|
||||||
const statusMap = new Map();
|
const statusMap = new Map();
|
||||||
statusMap.set('online', 'processing');
|
statusMap.set('online', 'processing');
|
||||||
|
@ -173,12 +220,45 @@ const columns = [
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const paramsFormat = (config: any, _terms: any, name?: string) => {
|
||||||
|
if (config?.terms && Array.isArray(config.terms) && config?.terms.length > 0) {
|
||||||
|
(config?.terms || []).map((item: any, index: number) => {
|
||||||
|
if (item?.type) {
|
||||||
|
_terms[`${name ? `${name}.` : ''}terms[${index}].type`] = item.type;
|
||||||
|
}
|
||||||
|
paramsFormat(item, _terms, `${name ? `${name}.` : ''}terms[${index}]`);
|
||||||
|
});
|
||||||
|
} else if (!config?.terms && Object.keys(config).length > 0) {
|
||||||
|
Object.keys(config).forEach((key) => {
|
||||||
|
if (config[key]) {
|
||||||
|
_terms[`${name ? `${name}.` : ''}${key}`] = config[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleParams = (config: any) => {
|
||||||
|
const _terms: any = {};
|
||||||
|
paramsFormat(config, _terms);
|
||||||
|
const url = new URLSearchParams();
|
||||||
|
Object.keys(_terms).forEach((key) => {
|
||||||
|
url.append(key, _terms[key]);
|
||||||
|
});
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增
|
||||||
|
*/
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
message.warn('123')
|
message.warn('新增')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查看
|
||||||
|
*/
|
||||||
const handleView = (dt: any) => {
|
const handleView = (dt: any) => {
|
||||||
|
// message.warn('查看')
|
||||||
}
|
}
|
||||||
|
|
||||||
const getActions = (data: Partial<Record<string, any>>, type: 'card' | 'table'): ActionsType[] => {
|
const getActions = (data: Partial<Record<string, any>>, type: 'card' | 'table'): ActionsType[] => {
|
||||||
|
@ -257,4 +337,61 @@ const getActions = (data: Partial<Record<string, any>>, type: 'card' | 'table'):
|
||||||
return actions
|
return actions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onSelectChange = (keys: string[]) => {
|
||||||
|
_selectedRowKeys.value = [...keys]
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelSelect = () => {
|
||||||
|
_selectedRowKeys.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClick = (dt: any) => {
|
||||||
|
if(_selectedRowKeys.value.includes(dt.id)) {
|
||||||
|
const _index = _selectedRowKeys.value.findIndex(i => i === dt.id)
|
||||||
|
_selectedRowKeys.value.splice(_index, 1)
|
||||||
|
} else {
|
||||||
|
_selectedRowKeys.value = [..._selectedRowKeys.value, dt.id]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeAllDevice = () => {
|
||||||
|
type.value = 'active'
|
||||||
|
const activeAPI = `/${BASE_API_PATH}/device-instance/deploy?:X_Access_Token=${LocalStore.get(TOKEN_KEY)}&${handleParams(params)}`;
|
||||||
|
api.value = activeAPI
|
||||||
|
operationVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncDeviceStatus = () => {
|
||||||
|
type.value = 'sync'
|
||||||
|
const syncAPI = `/${BASE_API_PATH}/device-instance/state/_sync?:X_Access_Token=${LocalStore.get(TOKEN_KEY)}&${handleParams(params)}`;
|
||||||
|
api.value = syncAPI
|
||||||
|
operationVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const delSelectedDevice = async () => {
|
||||||
|
const resp = await batchDeleteDevice(_selectedRowKeys.value)
|
||||||
|
if(resp.status === 200){
|
||||||
|
message.success('操作成功!')
|
||||||
|
_selectedRowKeys.value = []
|
||||||
|
instanceRef.value?.reload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeSelectedDevice = async () => {
|
||||||
|
const resp = await batchDeployDevice(_selectedRowKeys.value)
|
||||||
|
if(resp.status === 200){
|
||||||
|
message.success('操作成功!')
|
||||||
|
_selectedRowKeys.value = []
|
||||||
|
instanceRef.value?.reload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const disabledSelectedDevice = async () => {
|
||||||
|
const resp = await batchUndeployDevice(_selectedRowKeys.value)
|
||||||
|
if(resp.status === 200){
|
||||||
|
message.success('操作成功!')
|
||||||
|
_selectedRowKeys.value = []
|
||||||
|
instanceRef.value?.reload()
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
|
@ -313,7 +313,6 @@ const formData = ref<ConfigFormData>({
|
||||||
configuration: {
|
configuration: {
|
||||||
appKey: '',
|
appKey: '',
|
||||||
appSecret: '',
|
appSecret: '',
|
||||||
url: '',
|
|
||||||
},
|
},
|
||||||
description: '',
|
description: '',
|
||||||
name: '',
|
name: '',
|
||||||
|
@ -325,13 +324,20 @@ const formData = ref<ConfigFormData>({
|
||||||
watch(
|
watch(
|
||||||
() => formData.value.type,
|
() => formData.value.type,
|
||||||
(val) => {
|
(val) => {
|
||||||
formData.value.configuration = CONFIG_FIELD_MAP[val];
|
// formData.value.configuration = Object.values<any>(CONFIG_FIELD_MAP[val])[0];
|
||||||
msgType.value = MSG_TYPE[val];
|
msgType.value = MSG_TYPE[val];
|
||||||
|
|
||||||
formData.value.provider = msgType.value[0].value;
|
formData.value.provider = msgType.value[0].value;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
computed(() =>
|
||||||
|
Object.assign(
|
||||||
|
formData.value.configuration,
|
||||||
|
CONFIG_FIELD_MAP[formData.value.type][formData.value.provider],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// 验证规则
|
// 验证规则
|
||||||
const formRules = ref({
|
const formRules = ref({
|
||||||
type: [{ required: true, message: '请选择通知方式' }],
|
type: [{ required: true, message: '请选择通知方式' }],
|
||||||
|
|
|
@ -3,34 +3,62 @@ export interface IHeaders {
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
export interface IConfiguration {
|
||||||
|
// 钉钉
|
||||||
|
appKey?: string;
|
||||||
|
appSecret?: string;
|
||||||
|
url?: string;
|
||||||
|
// 微信
|
||||||
|
corpId?: string;
|
||||||
|
corpSecret?: string;
|
||||||
|
// 邮件
|
||||||
|
host?: string;
|
||||||
|
port?: number;
|
||||||
|
ssl?: boolean;
|
||||||
|
sender?: string;
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
// 语音
|
||||||
|
regionId?: string;
|
||||||
|
accessKeyId?: string;
|
||||||
|
secret?: string;
|
||||||
|
// 短信
|
||||||
|
regionId?: string;
|
||||||
|
accessKeyId?: string;
|
||||||
|
secret?: string;
|
||||||
|
// webhook
|
||||||
|
// url?: string;
|
||||||
|
headers?: IHeaders[];
|
||||||
|
}
|
||||||
export type ConfigFormData = {
|
export type ConfigFormData = {
|
||||||
configuration: {
|
configuration: IConfiguration;
|
||||||
// 钉钉
|
// configuration: {
|
||||||
appKey?: string;
|
// // 钉钉
|
||||||
appSecret?: string;
|
// appKey?: string;
|
||||||
url?: string;
|
// appSecret?: string;
|
||||||
// 微信
|
// url?: string;
|
||||||
corpId?: string;
|
// // 微信
|
||||||
corpSecret?: string;
|
// corpId?: string;
|
||||||
// 邮件
|
// corpSecret?: string;
|
||||||
host?: string;
|
// // 邮件
|
||||||
port?: number;
|
// host?: string;
|
||||||
ssl?: boolean;
|
// port?: number;
|
||||||
sender?: string;
|
// ssl?: boolean;
|
||||||
username?: string;
|
// sender?: string;
|
||||||
password?: string;
|
// username?: string;
|
||||||
// 语音
|
// password?: string;
|
||||||
regionId?: string;
|
// // 语音
|
||||||
accessKeyId?: string;
|
// regionId?: string;
|
||||||
secret?: string;
|
// accessKeyId?: string;
|
||||||
// 短信
|
// secret?: string;
|
||||||
regionId?: string;
|
// // 短信
|
||||||
accessKeyId?: string;
|
// regionId?: string;
|
||||||
secret?: string;
|
// accessKeyId?: string;
|
||||||
// webhook
|
// secret?: string;
|
||||||
// url?: string;
|
// // webhook
|
||||||
headers?: IHeaders[];
|
// // url?: string;
|
||||||
};
|
// headers?: IHeaders[];
|
||||||
|
// };
|
||||||
description: string;
|
description: string;
|
||||||
name: string;
|
name: string;
|
||||||
provider: string;
|
provider: string;
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
<!-- webhook请求头可编辑表格 -->
|
||||||
|
<template>
|
||||||
|
<div class="attachment-wrapper">
|
||||||
|
<div
|
||||||
|
class="attachment-item"
|
||||||
|
v-for="(item, index) in fileList"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="item.name">
|
||||||
|
<template #addonAfter>
|
||||||
|
<a-upload
|
||||||
|
name="file"
|
||||||
|
:action="FILE_UPLOAD"
|
||||||
|
:headers="{
|
||||||
|
[TOKEN_KEY]: LocalStore.get(TOKEN_KEY),
|
||||||
|
}"
|
||||||
|
:showUploadList="false"
|
||||||
|
@change="handleChange"
|
||||||
|
>
|
||||||
|
<upload-outlined />
|
||||||
|
</a-upload>
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
<delete-outlined @click="handleDelete" style="cursor: pointer" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-button
|
||||||
|
type="dashed"
|
||||||
|
@click="handleAdd"
|
||||||
|
style="width: 100%; margin-top: 5px"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<plus-outlined />
|
||||||
|
</template>
|
||||||
|
添加
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="Attachments">
|
||||||
|
import {
|
||||||
|
PlusOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
UploadOutlined,
|
||||||
|
} from '@ant-design/icons-vue';
|
||||||
|
import { PropType } from 'vue';
|
||||||
|
import { IAttachments } from '../../types';
|
||||||
|
import { FILE_UPLOAD } from '@/api/comm';
|
||||||
|
import { LocalStore } from '@/utils/comm';
|
||||||
|
import { TOKEN_KEY } from '@/utils/variable';
|
||||||
|
import { UploadChangeParam } from 'ant-design-vue';
|
||||||
|
|
||||||
|
type Emits = {
|
||||||
|
(e: 'update:attachments', data: IAttachments[]): void;
|
||||||
|
};
|
||||||
|
const emit = defineEmits<Emits>();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
attachments: {
|
||||||
|
type: Array as PropType<IAttachments[]>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChange = (info: UploadChangeParam) => {
|
||||||
|
if (info.file.status === 'done') {
|
||||||
|
const result = info.file.response?.result;
|
||||||
|
console.log('result: ', result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fileList = ref<IAttachments[]>([]);
|
||||||
|
watch(
|
||||||
|
() => props.attachments,
|
||||||
|
(val) => {
|
||||||
|
fileList.value = val;
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDelete = (id: number) => {
|
||||||
|
const idx = fileList.value.findIndex((f) => f.id === id);
|
||||||
|
fileList.value.splice(idx, 1);
|
||||||
|
emit('update:attachments', fileList.value);
|
||||||
|
};
|
||||||
|
const handleAdd = () => {
|
||||||
|
fileList.value.push({
|
||||||
|
id: fileList.value.length,
|
||||||
|
name: '',
|
||||||
|
location: '',
|
||||||
|
});
|
||||||
|
emit('update:attachments', fileList.value);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.attachment-wrapper {
|
||||||
|
.attachment-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -31,40 +31,46 @@
|
||||||
<a-form-item
|
<a-form-item
|
||||||
label="类型"
|
label="类型"
|
||||||
v-bind="validateInfos.provider"
|
v-bind="validateInfos.provider"
|
||||||
v-if="formData.type !== 'email'"
|
v-if="
|
||||||
|
formData.type !== 'email' &&
|
||||||
|
formData.type !== 'webhook'
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<RadioCard
|
<RadioCard
|
||||||
:options="msgType"
|
:options="msgType"
|
||||||
v-model="formData.provider"
|
v-model="formData.provider"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
label="绑定配置"
|
||||||
|
v-bind="validateInfos.configId"
|
||||||
|
v-if="formData.type !== 'email'"
|
||||||
|
>
|
||||||
|
<a-select
|
||||||
|
v-model:value="formData.configId"
|
||||||
|
placeholder="请选择绑定配置"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="(item, index) in ROBOT_MSG_TYPE"
|
||||||
|
:key="index"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
<!-- 钉钉 -->
|
<!-- 钉钉 -->
|
||||||
<template v-if="formData.type === 'dingTalk'">
|
<template v-if="formData.type === 'dingTalk'">
|
||||||
<template
|
<template
|
||||||
v-if="formData.provider === 'dingTalkMessage'"
|
v-if="formData.provider === 'dingTalkMessage'"
|
||||||
>
|
>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
label="AppKey"
|
label="AgentId"
|
||||||
v-bind="
|
v-bind="validateInfos['template.agentId']"
|
||||||
validateInfos['configuration.appKey']
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="
|
v-model:value="
|
||||||
formData.configuration.appKey
|
formData.template.agentId
|
||||||
"
|
|
||||||
placeholder="请输入AppKey"
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item
|
|
||||||
label="AppSecret"
|
|
||||||
v-bind="
|
|
||||||
validateInfos['configuration.appSecret']
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<a-input
|
|
||||||
v-model:value="
|
|
||||||
formData.configuration.appSecret
|
|
||||||
"
|
"
|
||||||
placeholder="请输入AppSecret"
|
placeholder="请输入AppSecret"
|
||||||
/>
|
/>
|
||||||
|
@ -76,155 +82,340 @@
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
label="webHook"
|
label="消息类型"
|
||||||
v-bind="validateInfos['configuration.url']"
|
v-bind="
|
||||||
|
validateInfos['template.messageType']
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<a-input
|
<a-select
|
||||||
v-model:value="
|
v-model:value="
|
||||||
formData.configuration.url
|
formData.template.messageType
|
||||||
"
|
"
|
||||||
placeholder="请输入webHook"
|
placeholder="请选择消息类型"
|
||||||
/>
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="(
|
||||||
|
item, index
|
||||||
|
) in ROBOT_MSG_TYPE"
|
||||||
|
:key="index"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<template
|
||||||
|
v-if="
|
||||||
|
formData.template.messageType ===
|
||||||
|
'markdown'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
label="标题"
|
||||||
|
v-bind="
|
||||||
|
validateInfos[
|
||||||
|
'template.markdown.title'
|
||||||
|
]
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!-- <a-input
|
||||||
|
v-model:value="
|
||||||
|
formData.template.markdown
|
||||||
|
?.title
|
||||||
|
"
|
||||||
|
placeholder="请输入标题"
|
||||||
|
/> -->
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<!-- <template
|
||||||
|
v-if="
|
||||||
|
formData.template.messageType === 'link'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
label="标题"
|
||||||
|
v-bind="
|
||||||
|
validateInfos['template.link.title']
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="
|
||||||
|
formData.template.link?.title
|
||||||
|
"
|
||||||
|
placeholder="请输入标题"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="图片链接">
|
||||||
|
<a-input
|
||||||
|
v-model:value="
|
||||||
|
formData.template.link?.picUrl
|
||||||
|
"
|
||||||
|
placeholder="请输入图片链接"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="内容链接">
|
||||||
|
<a-input
|
||||||
|
v-model:value="
|
||||||
|
formData.template.link
|
||||||
|
?.messageUrl
|
||||||
|
"
|
||||||
|
placeholder="请输入内容链接"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</template> -->
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<!-- 微信 -->
|
<!-- 微信 -->
|
||||||
<template v-if="formData.type === 'weixin'">
|
<template v-if="formData.type === 'weixin'">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
label="corpId"
|
label="AgentId"
|
||||||
v-bind="validateInfos['configuration.corpId']"
|
v-bind="validateInfos['template.agentId']"
|
||||||
>
|
>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="
|
v-model:value="formData.template.agentId"
|
||||||
formData.configuration.corpId
|
placeholder="请输入agentId"
|
||||||
"
|
|
||||||
placeholder="请输入corpId"
|
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-row :gutter="10">
|
||||||
label="corpSecret"
|
<a-col :span="12">
|
||||||
v-bind="
|
<a-form-item label="收信人">
|
||||||
validateInfos['configuration.corpSecret']
|
<a-select
|
||||||
"
|
v-model:value="
|
||||||
>
|
formData.template.toUser
|
||||||
<a-input
|
"
|
||||||
v-model:value="
|
placeholder="请选择收信人"
|
||||||
formData.configuration.corpSecret
|
>
|
||||||
"
|
<a-select-option
|
||||||
placeholder="请输入corpSecret"
|
v-for="(
|
||||||
/>
|
item, index
|
||||||
|
) in ROBOT_MSG_TYPE"
|
||||||
|
:key="index"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="收信部门">
|
||||||
|
<a-select
|
||||||
|
v-model:value="
|
||||||
|
formData.template.toParty
|
||||||
|
"
|
||||||
|
placeholder="请选择收信部门"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="(
|
||||||
|
item, index
|
||||||
|
) in ROBOT_MSG_TYPE"
|
||||||
|
:key="index"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-form-item label="标签推送">
|
||||||
|
<a-select
|
||||||
|
v-model:value="formData.template.toTag"
|
||||||
|
placeholder="请选择标签推送"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="(item, index) in ROBOT_MSG_TYPE"
|
||||||
|
:key="index"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
<!-- 邮件 -->
|
<!-- 邮件 -->
|
||||||
<template v-if="formData.type === 'email'">
|
<template v-if="formData.type === 'email'">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
label="服务器地址"
|
label="标题"
|
||||||
v-bind="validateInfos['configuration.host']"
|
v-bind="validateInfos['template.subject']"
|
||||||
>
|
>
|
||||||
<a-space>
|
<a-input
|
||||||
<a-input
|
v-model:value="formData.template.subject"
|
||||||
v-model:value="
|
placeholder="请输入标题"
|
||||||
formData.configuration.host
|
/>
|
||||||
"
|
</a-form-item>
|
||||||
placeholder="请输入服务器地址"
|
<a-form-item label="收件人">
|
||||||
/>
|
<a-select
|
||||||
<a-input-number
|
v-model:value="formData.template.sendTo"
|
||||||
v-model:value="
|
placeholder="请选择收件人"
|
||||||
formData.configuration.port
|
>
|
||||||
"
|
<a-select-option
|
||||||
/>
|
v-for="(item, index) in ROBOT_MSG_TYPE"
|
||||||
<a-checkbox
|
:key="index"
|
||||||
v-model:value="
|
:value="item.value"
|
||||||
formData.configuration.ssl
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
开启SSL
|
{{ item.label }}
|
||||||
</a-checkbox>
|
</a-select-option>
|
||||||
</a-space>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item label="附件信息">
|
||||||
label="发件人"
|
<Attachments
|
||||||
v-bind="validateInfos['configuration.sender']"
|
v-model:attachments="
|
||||||
>
|
formData.template.attachments
|
||||||
<a-input
|
|
||||||
v-model:value="
|
|
||||||
formData.configuration.sender
|
|
||||||
"
|
"
|
||||||
placeholder="请输入发件人"
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item
|
|
||||||
label="用户名"
|
|
||||||
v-bind="validateInfos['configuration.username']"
|
|
||||||
>
|
|
||||||
<a-input
|
|
||||||
v-model:value="
|
|
||||||
formData.configuration.username
|
|
||||||
"
|
|
||||||
placeholder="请输入用户名"
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item
|
|
||||||
label="密码"
|
|
||||||
v-bind="validateInfos['configuration.password']"
|
|
||||||
>
|
|
||||||
<a-input
|
|
||||||
v-model:value="
|
|
||||||
formData.configuration.password
|
|
||||||
"
|
|
||||||
placeholder="请输入密码"
|
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
<!-- 语音/短信 -->
|
<!-- 语音 -->
|
||||||
<template
|
<template v-if="formData.type === 'voice'">
|
||||||
v-if="
|
|
||||||
formData.type === 'voice' ||
|
|
||||||
formData.type === 'sms'
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<a-form-item
|
<a-form-item
|
||||||
label="AccessKeyId"
|
label="类型"
|
||||||
v-bind="
|
v-bind="validateInfos['template.templateType']"
|
||||||
validateInfos['configuration.accessKeyId']
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
|
<a-select
|
||||||
|
v-model:value="
|
||||||
|
formData.template.templateType
|
||||||
|
"
|
||||||
|
placeholder="请选择类型"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="(item, index) in VOICE_TYPE"
|
||||||
|
:key="index"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-row :gutter="10">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item
|
||||||
|
label="模板ID"
|
||||||
|
v-bind="
|
||||||
|
validateInfos[
|
||||||
|
'template.templateCode'
|
||||||
|
]
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="
|
||||||
|
formData.template.templateCode
|
||||||
|
"
|
||||||
|
placeholder="请输入模板ID"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="被叫号码">
|
||||||
|
<a-input
|
||||||
|
v-model:value="
|
||||||
|
formData.template.calledNumber
|
||||||
|
"
|
||||||
|
placeholder="请输入被叫号码"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-form-item label="被叫显号">
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="
|
v-model:value="
|
||||||
formData.configuration.accessKeyId
|
formData.template.calledShowNumbers
|
||||||
"
|
"
|
||||||
placeholder="请输入AccessKeyId"
|
placeholder="请输入被叫显号"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="播放次数">
|
||||||
|
<a-input
|
||||||
|
v-model:value="formData.template.playTimes"
|
||||||
|
placeholder="请输入播放次数"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
label="Secret"
|
label="模板内容"
|
||||||
v-bind="validateInfos['configuration.secret']"
|
v-if="formData.template.templateType === 'tts'"
|
||||||
|
>
|
||||||
|
<a-textarea
|
||||||
|
v-model:value="formData.template.ttsCode"
|
||||||
|
show-count
|
||||||
|
:rows="5"
|
||||||
|
placeholder="内容中的变量将用于阿里云语音验证码"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<!-- 短信 -->
|
||||||
|
<template v-if="formData.type === 'sms'">
|
||||||
|
<a-row :gutter="10">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item
|
||||||
|
label="模板"
|
||||||
|
v-bind="validateInfos['template.code']"
|
||||||
|
>
|
||||||
|
<a-select
|
||||||
|
v-model:value="
|
||||||
|
formData.template.code
|
||||||
|
"
|
||||||
|
placeholder="请选择模板"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="(
|
||||||
|
item, index
|
||||||
|
) in ROBOT_MSG_TYPE"
|
||||||
|
:key="index"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="收信人">
|
||||||
|
<a-input
|
||||||
|
v-model:value="
|
||||||
|
formData.template.phoneNumber
|
||||||
|
"
|
||||||
|
placeholder="请输入收信人"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-form-item
|
||||||
|
label="签名"
|
||||||
|
v-bind="validateInfos['template.signName']"
|
||||||
>
|
>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="
|
v-model:value="formData.template.signName"
|
||||||
formData.configuration.secret
|
placeholder="请输入签名"
|
||||||
"
|
|
||||||
placeholder="Secret"
|
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
<!-- webhook -->
|
<!-- webhook -->
|
||||||
<template v-if="formData.type === 'webhook'">
|
<template v-if="formData.type === 'webhook'">
|
||||||
<a-form-item
|
<a-form-item label="请求体">
|
||||||
label="Webhook"
|
<a-radio-group
|
||||||
v-bind="validateInfos['configuration.url']"
|
v-model:value="
|
||||||
>
|
formData.template.contextAsBody
|
||||||
<a-input
|
|
||||||
v-model:value="formData.configuration.url"
|
|
||||||
placeholder="请输入Webhook"
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="请求头">
|
|
||||||
<!-- <EditTable
|
|
||||||
v-model:headers="
|
|
||||||
formData.configuration.headers
|
|
||||||
"
|
"
|
||||||
/> -->
|
style="margin-bottom: 20px"
|
||||||
|
>
|
||||||
|
<a-radio :value="true">默认</a-radio>
|
||||||
|
<a-radio :value="false">自定义</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
<a-textarea
|
||||||
|
v-model:value="formData.template.body"
|
||||||
|
placeholder="请求体中的数据来自于发送通知时指定的所有变量"
|
||||||
|
v-if="formData.template.contextAsBody"
|
||||||
|
disabled
|
||||||
|
:rows="5"
|
||||||
|
/>
|
||||||
|
<div v-else style="height: 400px">
|
||||||
|
<MonacoEditor
|
||||||
|
theme="vs"
|
||||||
|
v-model:modelValue="
|
||||||
|
formData.template.body
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
<a-form-item label="说明">
|
<a-form-item label="说明">
|
||||||
|
@ -263,12 +454,15 @@ import { message } from 'ant-design-vue';
|
||||||
import { TemplateFormData } from '../types';
|
import { TemplateFormData } from '../types';
|
||||||
import {
|
import {
|
||||||
NOTICE_METHOD,
|
NOTICE_METHOD,
|
||||||
CONFIG_FIELD_MAP,
|
TEMPLATE_FIELD_MAP,
|
||||||
MSG_TYPE,
|
MSG_TYPE,
|
||||||
|
ROBOT_MSG_TYPE,
|
||||||
|
VOICE_TYPE,
|
||||||
} from '@/views/notice/const';
|
} from '@/views/notice/const';
|
||||||
// import EditTable from './components/EditTable.vue';
|
import templateApi from '@/api/notice/template';
|
||||||
import configApi from '@/api/notice/config';
|
|
||||||
import Doc from './doc/index';
|
import Doc from './doc/index';
|
||||||
|
import MonacoEditor from '@/components/MonacoEditor/index.vue';
|
||||||
|
import Attachments from './components/Attachments.vue'
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
@ -290,30 +484,34 @@ const msgType = ref([
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
const formData = ref<TemplateFormData>({
|
const formData = ref<TemplateFormData>({
|
||||||
description: '',
|
template: {},
|
||||||
name: '',
|
name: '',
|
||||||
provider: '',
|
type: 'email',
|
||||||
type: NOTICE_METHOD[2].value,
|
provider: 'embedded',
|
||||||
template: {
|
description: '',
|
||||||
subject: '',
|
variableDefinitions: [],
|
||||||
sendTo: [],
|
|
||||||
attachments: [],
|
|
||||||
message: '',
|
|
||||||
text: '',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 根据通知方式展示对应的字段
|
// 根据通知方式展示对应的字段
|
||||||
watch(
|
watch(
|
||||||
() => formData.value.type,
|
() => formData.value.type,
|
||||||
(val) => {
|
(val) => {
|
||||||
formData.value.configuration = CONFIG_FIELD_MAP[val];
|
// formData.value.template = TEMPLATE_FIELD_MAP[val];
|
||||||
msgType.value = MSG_TYPE[val];
|
msgType.value = MSG_TYPE[val];
|
||||||
|
|
||||||
formData.value.provider = msgType.value[0].value;
|
formData.value.provider = msgType.value[0].value;
|
||||||
|
console.log('formData.value.template: ', formData.value.template);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
computed(() => {
|
||||||
|
console.log('formData.value.type: ', formData.value.type);
|
||||||
|
Object.assign(
|
||||||
|
formData.value.template,
|
||||||
|
TEMPLATE_FIELD_MAP[formData.value.type][formData.value.provider],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// 验证规则
|
// 验证规则
|
||||||
const formRules = ref({
|
const formRules = ref({
|
||||||
type: [{ required: true, message: '请选择通知方式' }],
|
type: [{ required: true, message: '请选择通知方式' }],
|
||||||
|
@ -322,58 +520,23 @@ const formRules = ref({
|
||||||
{ max: 64, message: '最多可输入64个字符' },
|
{ max: 64, message: '最多可输入64个字符' },
|
||||||
],
|
],
|
||||||
provider: [{ required: true, message: '请选择类型' }],
|
provider: [{ required: true, message: '请选择类型' }],
|
||||||
|
configId: [{ required: true, message: '请选择绑定配置' }],
|
||||||
// 钉钉
|
// 钉钉
|
||||||
'configuration.appKey': [
|
'template.agentId': [{ required: true, message: '请输入agentId' }],
|
||||||
{ required: true, message: '请输入AppKey' },
|
'template.messageType': [{ required: true, message: '请选择消息类型' }],
|
||||||
{ max: 64, message: '最多可输入64个字符' },
|
'template.markdown.title': [{ required: true, message: '请输入标题' }],
|
||||||
],
|
// 'template.url': [{ required: true, message: '请输入WebHook' }],
|
||||||
'configuration.appSecret': [
|
|
||||||
{ required: true, message: '请输入AppSecret' },
|
|
||||||
{ max: 64, message: '最多可输入64个字符' },
|
|
||||||
],
|
|
||||||
// 'configuration.url': [{ required: true, message: '请输入WebHook' }],
|
|
||||||
// 微信
|
// 微信
|
||||||
'configuration.corpId': [
|
// 'template.agentId': [{ required: true, message: '请输入agentId' }],
|
||||||
{ required: true, message: '请输入corpId' },
|
|
||||||
{ max: 64, message: '最多可输入64个字符' },
|
|
||||||
],
|
|
||||||
'configuration.corpSecret': [
|
|
||||||
{ required: true, message: '请输入corpSecret' },
|
|
||||||
{ max: 64, message: '最多可输入64个字符' },
|
|
||||||
],
|
|
||||||
// 阿里云语音/短信
|
|
||||||
'configuration.regionId': [
|
|
||||||
{ required: true, message: '请输入RegionId' },
|
|
||||||
{ max: 64, message: '最多可输入64个字符' },
|
|
||||||
],
|
|
||||||
'configuration.accessKeyId': [
|
|
||||||
{ required: true, message: '请输入AccessKeyId' },
|
|
||||||
{ max: 64, message: '最多可输入64个字符' },
|
|
||||||
],
|
|
||||||
'configuration.secret': [
|
|
||||||
{ required: true, message: '请输入Secret' },
|
|
||||||
{ max: 64, message: '最多可输入64个字符' },
|
|
||||||
],
|
|
||||||
// 邮件
|
// 邮件
|
||||||
'configuration.host': [{ required: true, message: '请输入服务器地址' }],
|
'template.subject': [{ required: true, message: '请输入标题' }],
|
||||||
'configuration.sender': [{ required: true, message: '请输入发件人' }],
|
// 阿里云语音
|
||||||
'configuration.username': [
|
'template.templateType': [{ required: true, message: '请选择类型' }],
|
||||||
{ required: true, message: '请输入用户名' },
|
'template.templateCode': [{ required: true, message: '请输入模板ID' }],
|
||||||
{ max: 64, message: '最多可输入64个字符' },
|
// 短信
|
||||||
],
|
'template.code': [{ required: true, message: '请选择模板' }],
|
||||||
'configuration.password': [
|
'template.signName': [{ required: true, message: '请输入签名' }],
|
||||||
{ required: true, message: '请输入密码' },
|
|
||||||
{ max: 64, message: '最多可输入64个字符' },
|
|
||||||
],
|
|
||||||
// webhook
|
// webhook
|
||||||
'configuration.url': [
|
|
||||||
{ required: true, message: '请输入Webhook' },
|
|
||||||
{
|
|
||||||
pattern:
|
|
||||||
/^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/,
|
|
||||||
message: 'Webhook需要是一个合法的URL',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
description: [{ max: 200, message: '最多可输入200个字符' }],
|
description: [{ max: 200, message: '最多可输入200个字符' }],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -390,12 +553,12 @@ watch(
|
||||||
);
|
);
|
||||||
|
|
||||||
const getDetail = async () => {
|
const getDetail = async () => {
|
||||||
const res = await configApi.detail(route.params.id as string);
|
const res = await templateApi.detail(route.params.id as string);
|
||||||
// console.log('res: ', res);
|
// console.log('res: ', res);
|
||||||
formData.value = res.result;
|
formData.value = res.result;
|
||||||
// console.log('formData.value: ', formData.value);
|
// console.log('formData.value: ', formData.value);
|
||||||
};
|
};
|
||||||
getDetail();
|
// getDetail();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表单提交
|
* 表单提交
|
||||||
|
@ -404,19 +567,19 @@ const btnLoading = ref<boolean>(false);
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
validate()
|
validate()
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
// console.log('formData.value: ', formData.value);
|
console.log('formData.value: ', formData.value);
|
||||||
btnLoading.value = true;
|
btnLoading.value = true;
|
||||||
let res;
|
// let res;
|
||||||
if (!formData.value.id) {
|
// if (!formData.value.id) {
|
||||||
res = await configApi.save(formData.value);
|
// res = await templateApi.save(formData.value);
|
||||||
} else {
|
// } else {
|
||||||
res = await configApi.update(formData.value);
|
// res = await templateApi.update(formData.value);
|
||||||
}
|
// }
|
||||||
// console.log('res: ', res);
|
// // console.log('res: ', res);
|
||||||
if (res?.success) {
|
// if (res?.success) {
|
||||||
message.success('保存成功');
|
// message.success('保存成功');
|
||||||
router.back();
|
// router.back();
|
||||||
}
|
// }
|
||||||
btnLoading.value = false;
|
btnLoading.value = false;
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
|
|
@ -3,6 +3,20 @@
|
||||||
<div class="page-container">通知模板</div>
|
<div class="page-container">通知模板</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts"></script>
|
<script setup lang="ts">
|
||||||
|
import templateApi from '@/api/notice/template';
|
||||||
|
|
||||||
|
const getList = async () => {
|
||||||
|
const res = await templateApi.list({
|
||||||
|
current: 1,
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: 12,
|
||||||
|
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||||
|
terms: [],
|
||||||
|
});
|
||||||
|
console.log('res: ', res);
|
||||||
|
};
|
||||||
|
getList();
|
||||||
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
<style lang="less" scoped></style>
|
||||||
|
|
|
@ -7,6 +7,7 @@ export interface IHeaders {
|
||||||
interface IAttachments {
|
interface IAttachments {
|
||||||
location: string;
|
location: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
id?: number;
|
||||||
}
|
}
|
||||||
interface IVariableDefinitions {
|
interface IVariableDefinitions {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -16,14 +17,6 @@ interface IVariableDefinitions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TemplateFormData = {
|
export type TemplateFormData = {
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
provider: string;
|
|
||||||
description: string;
|
|
||||||
id?: string;
|
|
||||||
creatorId?: string;
|
|
||||||
createTime?: number;
|
|
||||||
configId?: string;
|
|
||||||
template: {
|
template: {
|
||||||
// 钉钉消息
|
// 钉钉消息
|
||||||
agentId?: string;
|
agentId?: string;
|
||||||
|
@ -41,7 +34,7 @@ export type TemplateFormData = {
|
||||||
text: string;
|
text: string;
|
||||||
};
|
};
|
||||||
// 微信
|
// 微信
|
||||||
agentId?: string;
|
// agentId?: string;
|
||||||
// message?: string;
|
// message?: string;
|
||||||
toParty?: string;
|
toParty?: string;
|
||||||
toUser?: string;
|
toUser?: string;
|
||||||
|
@ -69,6 +62,13 @@ export type TemplateFormData = {
|
||||||
contextAsBody?: boolean;
|
contextAsBody?: boolean;
|
||||||
body?: string;
|
body?: string;
|
||||||
};
|
};
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
provider: string;
|
||||||
|
description: string;
|
||||||
variableDefinitions: IVariableDefinitions[];
|
variableDefinitions: IVariableDefinitions[];
|
||||||
|
id?: string;
|
||||||
|
creatorId?: string;
|
||||||
|
createTime?: number;
|
||||||
|
configId?: string;
|
||||||
};
|
};
|
|
@ -33,7 +33,7 @@ export const NOTICE_METHOD: INoticeMethod[] = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// 消息类型
|
// 类型
|
||||||
export const MSG_TYPE = {
|
export const MSG_TYPE = {
|
||||||
dingTalk: [
|
dingTalk: [
|
||||||
{
|
{
|
||||||
|
@ -93,36 +93,52 @@ export const MSG_TYPE = {
|
||||||
// 配置
|
// 配置
|
||||||
export const CONFIG_FIELD_MAP = {
|
export const CONFIG_FIELD_MAP = {
|
||||||
dingTalk: {
|
dingTalk: {
|
||||||
appKey: undefined,
|
dingTalkMessage: {
|
||||||
appSecret: undefined,
|
appKey: '',
|
||||||
url: undefined,
|
appSecret: '',
|
||||||
|
},
|
||||||
|
dingTalkRobotWebHook: {
|
||||||
|
url: '',
|
||||||
|
}
|
||||||
},
|
},
|
||||||
weixin: {
|
weixin: {
|
||||||
corpId: undefined,
|
corpMessage: {
|
||||||
corpSecret: undefined,
|
corpId: '',
|
||||||
|
corpSecret: '',
|
||||||
|
},
|
||||||
|
// officialMessage: {},
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
host: undefined,
|
embedded: {
|
||||||
port: 25,
|
host: '',
|
||||||
ssl: false,
|
port: 25,
|
||||||
sender: undefined,
|
ssl: false,
|
||||||
username: undefined,
|
sender: '',
|
||||||
password: undefined,
|
username: '',
|
||||||
|
password: '',
|
||||||
|
}
|
||||||
},
|
},
|
||||||
voice: {
|
voice: {
|
||||||
regionId: undefined,
|
aliyun: {
|
||||||
accessKeyId: undefined,
|
regionId: '',
|
||||||
secret: undefined,
|
accessKeyId: '',
|
||||||
|
secret: '',
|
||||||
|
}
|
||||||
},
|
},
|
||||||
sms: {
|
sms: {
|
||||||
regionId: undefined,
|
aliyunSms: {
|
||||||
accessKeyId: undefined,
|
regionId: '',
|
||||||
secret: undefined,
|
accessKeyId: '',
|
||||||
|
secret: '',
|
||||||
|
}
|
||||||
},
|
},
|
||||||
webhook: {
|
webhook: {
|
||||||
url: undefined,
|
http: {
|
||||||
headers: [],
|
url: undefined,
|
||||||
|
headers: [],
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 模板
|
// 模板
|
||||||
|
@ -187,8 +203,20 @@ export const TEMPLATE_FIELD_MAP = {
|
||||||
},
|
},
|
||||||
webhook: {
|
webhook: {
|
||||||
http: {
|
http: {
|
||||||
contextAsBody: false,
|
contextAsBody: true,
|
||||||
body: ''
|
body: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 钉钉机器人-消息类型
|
||||||
|
export const ROBOT_MSG_TYPE = [
|
||||||
|
{ label: 'markdown', value: 'markdown' },
|
||||||
|
{ label: 'text', value: 'text' },
|
||||||
|
{ label: 'link', value: 'link' },
|
||||||
|
]
|
||||||
|
// 语音通知类型
|
||||||
|
export const VOICE_TYPE = [
|
||||||
|
{ label: '语音通知', value: 'voice' },
|
||||||
|
{ label: '语音验证码', value: 'tts' },
|
||||||
|
]
|
Loading…
Reference in New Issue