feat:对照需求表
This commit is contained in:
1 parent
5685ee9136
commit
8cc6972f1c
10 files changed
+1015
-320
No files matched your search
@@ -2,13 +2,9 @@
|
||||
<div class="top-right-btn" :style="style">
|
||||
<el-row>
|
||||
<!-- 搜索框控制 -->
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="showSearch ? $t('queryTree.hideSearch') : $t('queryTree.displayTheSearch')"
|
||||
placement="top"
|
||||
v-if="search"
|
||||
>
|
||||
<el-tooltip class="item" effect="dark"
|
||||
:content="showSearch ? $t('queryTree.hideSearch') : $t('queryTree.displayTheSearch')" placement="top"
|
||||
v-if="search">
|
||||
<el-button circle icon="Search" @click="toggleSearch()" />
|
||||
</el-tooltip>
|
||||
|
||||
@@ -22,9 +18,9 @@
|
||||
<el-button circle icon="Refresh" @click="handleRefresh()" />
|
||||
</el-tooltip>
|
||||
|
||||
<!-- 插槽 -->
|
||||
<slot></slot>
|
||||
</el-row>
|
||||
<!-- 插槽 -->
|
||||
<slot></slot>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -0,0 +1,360 @@
|
||||
<template>
|
||||
<el-tree-select :class="['select-org-tree', { 'select-org-tree--single': !props.multiple }]" :props="treeProps"
|
||||
node-key="id" v-model="state.innerValue" :placeholder="props.placeholder" :clearable="props.clearable"
|
||||
:disabled="props.disabled" :loading="state.loading" :data="state.options" :render-after-expand="false"
|
||||
:show-checkbox="props.multiple" :multiple="props.multiple" :check-strictly="props.checkStrictly"
|
||||
:check-on-click-node="props.multiple" :expand-on-click-node="false" :filter-node-method="filterNode" filterable
|
||||
default-expand-all style="width: 100%" @node-click="handleNodeClick" @check="handleCheck"
|
||||
@visible-change="handleVisibleChange" @clear="handleClear">
|
||||
<template #default="{ data, node }">
|
||||
<div class="flex items-center gap-2 w-full min-w-0">
|
||||
<el-tag v-if="node.level === 1" size="small" effect="dark" color="var(--el-color-primary)">顶级组织</el-tag>
|
||||
<span class="font-medium truncate">{{ data.treeLabel || data.name || data.orgName || node.label
|
||||
}}</span>
|
||||
<span v-if="data.orgCode" class="text-gray-400 truncate">{{ data.orgCode }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-tree-select>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="select-org-tree">
|
||||
import { onMounted, reactive, watch } from 'vue'
|
||||
import * as SysOrgApi from '/@/api/sys/sysOrg'
|
||||
|
||||
const emit = defineEmits(['change'])
|
||||
|
||||
const modelValue = defineModel<any>()
|
||||
|
||||
type OrgTreeNode = {
|
||||
id: string | number
|
||||
primaryId?: string | number
|
||||
name?: string
|
||||
orgName?: string
|
||||
orgCode?: string
|
||||
treeLabel?: string
|
||||
visible?: boolean
|
||||
children?: OrgTreeNode[]
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
type Props = {
|
||||
placeholder?: string
|
||||
disabled?: boolean
|
||||
clearable?: boolean
|
||||
autoFirst?: boolean
|
||||
multiple?: boolean
|
||||
checkStrictly?: boolean
|
||||
valueKey?: string
|
||||
apiParams?: Record<string, any>
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
placeholder: '请选择组织',
|
||||
disabled: false,
|
||||
clearable: true,
|
||||
autoFirst: false,
|
||||
multiple: false,
|
||||
checkStrictly: false,
|
||||
valueKey: 'id',
|
||||
apiParams: () => ({}),
|
||||
})
|
||||
|
||||
const treeProps = {
|
||||
label: 'treeLabel',
|
||||
children: 'children',
|
||||
value: 'id',
|
||||
}
|
||||
|
||||
const state = reactive<{
|
||||
loading: boolean
|
||||
innerValue: any
|
||||
options: OrgTreeNode[]
|
||||
loaded: boolean
|
||||
syncingFromModel: boolean
|
||||
}>({
|
||||
loading: false,
|
||||
innerValue: props.multiple ? [] : null,
|
||||
options: [],
|
||||
loaded: false,
|
||||
syncingFromModel: false,
|
||||
})
|
||||
|
||||
let loadOptionsTask: Promise<void> | null = null
|
||||
|
||||
const hasSelectedValue = (value: unknown) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.some((item) => item !== null && item !== undefined && `${item}`.trim() !== '')
|
||||
}
|
||||
return value !== null && value !== undefined && `${value}`.trim() !== ''
|
||||
}
|
||||
|
||||
const shouldLoadOptionsOnInit = (value: unknown) => props.autoFirst || hasSelectedValue(value)
|
||||
|
||||
const filterVisibleNodes = (nodes: OrgTreeNode[]): OrgTreeNode[] => {
|
||||
if (!Array.isArray(nodes)) return []
|
||||
return nodes.reduce<OrgTreeNode[]>((filtered, node) => {
|
||||
if (node.visible !== false) {
|
||||
const filteredNode: OrgTreeNode = {
|
||||
...node,
|
||||
treeLabel: node.name || node.orgName || `${node.id}`,
|
||||
}
|
||||
if (node.children?.length) {
|
||||
filteredNode.children = filterVisibleNodes(node.children)
|
||||
}
|
||||
filtered.push(filteredNode)
|
||||
return filtered
|
||||
}
|
||||
if (node.children?.length) {
|
||||
const filteredChildren = filterVisibleNodes(node.children)
|
||||
if (filteredChildren.length > 0) {
|
||||
filtered.push({
|
||||
...node,
|
||||
treeLabel: node.name || node.orgName || `${node.id}`,
|
||||
children: filteredChildren,
|
||||
})
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}, [])
|
||||
}
|
||||
|
||||
const filterNode = (value: string, data: OrgTreeNode, node: any) => {
|
||||
const kw = (value ?? '').toString().trim().toLowerCase()
|
||||
if (!kw) return true
|
||||
const selfName = (data?.treeLabel ?? '').toString().toLowerCase()
|
||||
const selfCode = (data?.orgCode ?? '').toString().toLowerCase()
|
||||
const parentName = (node?.parent?.data?.treeLabel ?? '').toString().toLowerCase()
|
||||
if (selfName && selfName.includes(kw)) return true
|
||||
if (selfCode && selfCode.includes(kw)) return true
|
||||
if (parentName && parentName.includes(kw)) return true
|
||||
return false
|
||||
}
|
||||
|
||||
const getCacheKey = () => `SelectOrgTree_Cache_${JSON.stringify(props.apiParams || {})}`
|
||||
|
||||
const flattenNodes = (nodes: OrgTreeNode[]): OrgTreeNode[] => {
|
||||
return nodes.reduce<OrgTreeNode[]>((result, node) => {
|
||||
result.push(node)
|
||||
if (node.children?.length) {
|
||||
result.push(...flattenNodes(node.children))
|
||||
}
|
||||
return result
|
||||
}, [])
|
||||
}
|
||||
|
||||
const findNodeById = (nodes: OrgTreeNode[], nodeId: string | number): OrgTreeNode | null => {
|
||||
for (const node of nodes) {
|
||||
if (node.id === nodeId) return node
|
||||
if (node.children?.length) {
|
||||
const childNode = findNodeById(node.children, nodeId)
|
||||
if (childNode) return childNode
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const getLocalEmitValue = (node: OrgTreeNode) => {
|
||||
if (props.valueKey === 'id') return node.id
|
||||
return node?.[props.valueKey]
|
||||
}
|
||||
|
||||
const resolveEmitValue = async (node: OrgTreeNode) => {
|
||||
const localValue = getLocalEmitValue(node)
|
||||
if (localValue !== undefined && localValue !== null && `${localValue}`.trim() !== '') {
|
||||
return localValue
|
||||
}
|
||||
if (props.valueKey === 'id' || !node.primaryId) return node.id
|
||||
try {
|
||||
const res: any = await SysOrgApi.getObj(String(node.primaryId))
|
||||
const resolvedValue = res?.data?.[props.valueKey]
|
||||
if (resolvedValue !== undefined && resolvedValue !== null && `${resolvedValue}`.trim() !== '') {
|
||||
node[props.valueKey] = resolvedValue
|
||||
if (res?.data?.orgCode) node.orgCode = res.data.orgCode
|
||||
if (res?.data?.orgName && !node.treeLabel) node.treeLabel = res.data.orgName
|
||||
return resolvedValue
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to resolve org node value', error)
|
||||
}
|
||||
return node.id
|
||||
}
|
||||
|
||||
const findNodeByModelValue = (nodes: OrgTreeNode[], value: any): OrgTreeNode | null => {
|
||||
const flatNodes = flattenNodes(nodes)
|
||||
return flatNodes.find((node) => getLocalEmitValue(node) === value) || null
|
||||
}
|
||||
|
||||
const syncInnerValueByModel = async (value: any) => {
|
||||
state.syncingFromModel = true
|
||||
try {
|
||||
if (props.multiple) {
|
||||
const values = Array.isArray(value) ? value : []
|
||||
const nodeIds = values
|
||||
.map((item) => findNodeByModelValue(state.options, item)?.id)
|
||||
.filter((item) => item !== undefined && item !== null)
|
||||
state.innerValue = nodeIds
|
||||
return
|
||||
}
|
||||
if (!hasSelectedValue(value)) {
|
||||
state.innerValue = null
|
||||
return
|
||||
}
|
||||
const matchedNode = findNodeByModelValue(state.options, value)
|
||||
state.innerValue = matchedNode?.id ?? null
|
||||
} finally {
|
||||
state.syncingFromModel = false
|
||||
}
|
||||
}
|
||||
|
||||
const emitChange = (value: any) => {
|
||||
modelValue.value = value
|
||||
emit('change', value)
|
||||
}
|
||||
|
||||
const findFirstSelectableNode = (nodes: OrgTreeNode[]): OrgTreeNode | null => {
|
||||
for (const node of nodes) {
|
||||
if (!props.checkStrictly || !node.children?.length) return node
|
||||
if (node.children?.length) {
|
||||
const childNode = findFirstSelectableNode(node.children)
|
||||
if (childNode) return childNode
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
watch(
|
||||
modelValue,
|
||||
async (newVal, oldValue) => {
|
||||
if (newVal === oldValue && state.loaded) return
|
||||
if (shouldLoadOptionsOnInit(newVal)) {
|
||||
await ensureOptionsLoaded()
|
||||
}
|
||||
await syncInnerValueByModel(newVal)
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.apiParams,
|
||||
async () => {
|
||||
if (shouldLoadOptionsOnInit(modelValue.value) || state.loaded) {
|
||||
state.loaded = false
|
||||
await ensureOptionsLoaded(true)
|
||||
await syncInnerValueByModel(modelValue.value)
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
if (shouldLoadOptionsOnInit(modelValue.value)) {
|
||||
void ensureOptionsLoaded()
|
||||
}
|
||||
})
|
||||
|
||||
async function ensureOptionsLoaded(force = false) {
|
||||
if (state.loaded && !force) return
|
||||
|
||||
if (!force) {
|
||||
try {
|
||||
const cacheKey = getCacheKey()
|
||||
const cached = localStorage.getItem(cacheKey)
|
||||
if (cached) {
|
||||
state.options = JSON.parse(cached)
|
||||
state.loaded = true
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to parse cached org tree data', e)
|
||||
}
|
||||
}
|
||||
|
||||
if (loadOptionsTask) return loadOptionsTask
|
||||
loadOptionsTask = getOptions(force).finally(() => {
|
||||
loadOptionsTask = null
|
||||
})
|
||||
return loadOptionsTask
|
||||
}
|
||||
|
||||
async function getOptions(force = false) {
|
||||
if (state.loaded && !force) return
|
||||
try {
|
||||
state.loading = true
|
||||
const { data } = await SysOrgApi.getTree({
|
||||
...(props.apiParams || {}),
|
||||
})
|
||||
state.options = filterVisibleNodes((data ?? []) as OrgTreeNode[])
|
||||
state.loaded = true
|
||||
|
||||
try {
|
||||
const cacheKey = getCacheKey()
|
||||
localStorage.setItem(cacheKey, JSON.stringify(state.options))
|
||||
} catch (e) {
|
||||
console.error('Failed to cache org tree data', e)
|
||||
}
|
||||
|
||||
if (props.autoFirst && !hasSelectedValue(modelValue.value) && state.options.length > 0) {
|
||||
const firstNode = findFirstSelectableNode(state.options)
|
||||
if (firstNode) {
|
||||
state.innerValue = props.multiple ? [firstNode.id] : firstNode.id
|
||||
const emitValue = await resolveEmitValue(firstNode)
|
||||
emitChange(props.multiple ? [emitValue] : emitValue)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
state.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleNodeClick = async (data: OrgTreeNode, node: any) => {
|
||||
if (props.multiple) return
|
||||
if (props.checkStrictly && node?.childNodes?.length > 0) return
|
||||
state.innerValue = data.id
|
||||
const emitValue = await resolveEmitValue(data)
|
||||
emitChange(emitValue)
|
||||
}
|
||||
|
||||
const handleCheck = async (_: OrgTreeNode, checkStatus: any) => {
|
||||
if (!props.multiple || state.syncingFromModel) return
|
||||
const checkedNodes = (checkStatus?.checkedNodes || []) as OrgTreeNode[]
|
||||
const values = await Promise.all(checkedNodes.map((node) => resolveEmitValue(node)))
|
||||
emitChange(values)
|
||||
}
|
||||
|
||||
const handleVisibleChange = (visible: boolean) => {
|
||||
if (!visible) return
|
||||
void ensureOptionsLoaded()
|
||||
}
|
||||
|
||||
const handleClear = () => {
|
||||
state.innerValue = props.multiple ? [] : null
|
||||
emitChange(props.multiple ? [] : null)
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
refresh: () => ensureOptionsLoaded(true),
|
||||
clearSelection: handleClear,
|
||||
getSelectedNode: () => {
|
||||
if (props.multiple) {
|
||||
return (Array.isArray(state.innerValue) ? state.innerValue : [])
|
||||
.map((id) => findNodeById(state.options, id))
|
||||
.filter(Boolean)
|
||||
}
|
||||
return findNodeById(state.options, state.innerValue)
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.select-org-tree {
|
||||
.el-tree-node__content {
|
||||
min-height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.select-org-tree--single {
|
||||
.el-tree-node__content .el-checkbox {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+109
-104
@@ -1,130 +1,135 @@
|
||||
import Pagination from '/@/components/Pagination/index.vue';
|
||||
import RightToolbar from '/@/components/RightToolbar/index.vue';
|
||||
import DictTag from '/@/components/DictTag/index.vue';
|
||||
import DictSelect from '/@/components/DictTag/Select.vue';
|
||||
import UploadExcel from '/@/components/Upload/Excel.vue';
|
||||
import UploadFile from '/@/components/Upload/index.vue';
|
||||
import UploadImg from '/@/components/Upload/Image.vue';
|
||||
import DelWrap from '/@/components/DelWrap/index.vue';
|
||||
import Editor from '/@/components/Editor/index.vue';
|
||||
import Tip from '/@/components/Tip/index.vue';
|
||||
import TagList from '/@/components/TagList/index.vue';
|
||||
import SvgIcon from '/@/components/SvgIcon/index.vue';
|
||||
import Sign from '/@/components/Sign/index.vue';
|
||||
import ChinaArea from '/@/components/ChinaArea/index.vue';
|
||||
import OrgSelector from '/@/components/OrgSelector/index.vue';
|
||||
import Pagination from "/@/components/Pagination/index.vue";
|
||||
import RightToolbar from "/@/components/RightToolbar/index.vue";
|
||||
import DictTag from "/@/components/DictTag/index.vue";
|
||||
import DictSelect from "/@/components/DictTag/Select.vue";
|
||||
import UploadExcel from "/@/components/Upload/Excel.vue";
|
||||
import UploadFile from "/@/components/Upload/index.vue";
|
||||
import UploadImg from "/@/components/Upload/Image.vue";
|
||||
import DelWrap from "/@/components/DelWrap/index.vue";
|
||||
import Editor from "/@/components/Editor/index.vue";
|
||||
import Tip from "/@/components/Tip/index.vue";
|
||||
import TagList from "/@/components/TagList/index.vue";
|
||||
import SvgIcon from "/@/components/SvgIcon/index.vue";
|
||||
import Sign from "/@/components/Sign/index.vue";
|
||||
import ChinaArea from "/@/components/ChinaArea/index.vue";
|
||||
import OrgSelector from "/@/components/OrgSelector/index.vue";
|
||||
|
||||
import DraggableDeptTree from "/@/components/DraggableDeptTree/index.vue"
|
||||
import DraggableOrgTree from "/@/components/DraggableOrgTree/index.vue"
|
||||
import DraggableDeviceGroup from "/@/components/DraggableDeviceGroup/index.vue"
|
||||
import DraggableDeviceGroupDraggable from "/@/components/DraggableDeviceGroupDraggable/index.vue"
|
||||
import DraggableDeptTree from "/@/components/DraggableDeptTree/index.vue";
|
||||
import DraggableOrgTree from "/@/components/DraggableOrgTree/index.vue";
|
||||
import DraggableDeviceGroup from "/@/components/DraggableDeviceGroup/index.vue";
|
||||
import DraggableDeviceGroupDraggable from "/@/components/DraggableDeviceGroupDraggable/index.vue";
|
||||
|
||||
import PreviewImage from "/@/components/PreviewImage/index.vue"
|
||||
import PreviewImage from "/@/components/PreviewImage/index.vue";
|
||||
|
||||
import TagOnline from "/@/components/Tag/TagOnline.vue"
|
||||
import TagSex from "/@/components/Tag/TagSex.vue"
|
||||
import TagSync from "/@/components/Tag/TagSync.vue"
|
||||
import TagUserStatus from "/@/components/Tag/TagUserStatus.vue"
|
||||
import TagOnline from "/@/components/Tag/TagOnline.vue";
|
||||
import TagSex from "/@/components/Tag/TagSex.vue";
|
||||
import TagSync from "/@/components/Tag/TagSync.vue";
|
||||
import TagUserStatus from "/@/components/Tag/TagUserStatus.vue";
|
||||
|
||||
import SelectArea from "/@/components/Select/SelectArea.vue"
|
||||
import SelectDevice from "/@/components/Select/SelectDevice.vue"
|
||||
import SelectDeviceGroup from "/@/components/Select/SelectDeviceGroup.vue"
|
||||
import SelectDuty from "/@/components/Select/SelectDuty.vue"
|
||||
import SelectDutyType from "/@/components/Select/SelectDutyType.vue"
|
||||
import SelectTreeChannel from "/@/components/Select/SelectTreeChannel.vue"
|
||||
import SelectTreeChannelRadio from "/@/components/Select/SelectTreeChannelRadio.vue"
|
||||
import SelectTreeDept from "/@/components/Select/SelectTreeDept.vue"
|
||||
import SelectDeviceTree from "/@/components/Select/SelectDeviceTree.vue"
|
||||
import SelectAreaType from "/@/components/Select/SelectAreaType.vue"
|
||||
import SelectPersonCarType from "/@/components/Select/SelectPersonCarType.vue"
|
||||
import SelectPerson from "/@/components/Select/SelectPerson.vue"
|
||||
import SelectArea from "/@/components/Select/SelectArea.vue";
|
||||
import SelectDevice from "/@/components/Select/SelectDevice.vue";
|
||||
import SelectDeviceGroup from "/@/components/Select/SelectDeviceGroup.vue";
|
||||
import SelectDuty from "/@/components/Select/SelectDuty.vue";
|
||||
import SelectDutyType from "/@/components/Select/SelectDutyType.vue";
|
||||
import SelectTreeChannel from "/@/components/Select/SelectTreeChannel.vue";
|
||||
import SelectTreeChannelRadio from "/@/components/Select/SelectTreeChannelRadio.vue";
|
||||
import SelectTreeDept from "/@/components/Select/SelectTreeDept.vue";
|
||||
import SelectOrgTree from "/@/components/Select/SelectOrgTree.vue";
|
||||
import SelectDeviceTree from "/@/components/Select/SelectDeviceTree.vue";
|
||||
import SelectAreaType from "/@/components/Select/SelectAreaType.vue";
|
||||
import SelectPersonCarType from "/@/components/Select/SelectPersonCarType.vue";
|
||||
import SelectPerson from "/@/components/Select/SelectPerson.vue";
|
||||
|
||||
import ColumnBtn from "/@/components/Column/ColumnBtn.vue"
|
||||
import ColumnBtn from "/@/components/Column/ColumnBtn.vue";
|
||||
|
||||
import PreviewJson from "/@/components/PreviewJson/index.vue"
|
||||
import PreviewJson from "/@/components/PreviewJson/index.vue";
|
||||
|
||||
import RtspPluginVideo from "/@/components/rtsp-plugin-video/video-plugin.vue"
|
||||
import RtspPluginVideo from "/@/components/rtsp-plugin-video/video-plugin.vue";
|
||||
|
||||
// 第三方组件
|
||||
import ElementPlus from 'element-plus';
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
|
||||
import 'element-plus/dist/index.css';
|
||||
import { Pane, Splitpanes } from 'splitpanes';
|
||||
import 'splitpanes/dist/splitpanes.css';
|
||||
import ElementPlus from "element-plus";
|
||||
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
|
||||
import "element-plus/dist/index.css";
|
||||
import { Pane, Splitpanes } from "splitpanes";
|
||||
import "splitpanes/dist/splitpanes.css";
|
||||
// 日历组件
|
||||
import { setupCalendar } from 'v-calendar';
|
||||
import { setupCalendar } from "v-calendar";
|
||||
|
||||
// 部门树组件
|
||||
import vue3TreeOrg from 'vue3-tree-org';
|
||||
import 'vue3-tree-org/lib/vue3-tree-org.css';
|
||||
import vue3TreeOrg from "vue3-tree-org";
|
||||
import "vue3-tree-org/lib/vue3-tree-org.css";
|
||||
|
||||
// 导入 FcDesigner
|
||||
import FcDesigner from 'form-create-designer';
|
||||
import FcDesigner from "form-create-designer";
|
||||
|
||||
// 导入声明
|
||||
import { App } from 'vue';
|
||||
import { App } from "vue";
|
||||
|
||||
export default {
|
||||
install(app: App) {
|
||||
app.component('DictTag', DictTag);
|
||||
app.component('DictSelect', DictSelect);
|
||||
app.component('Pagination', Pagination);
|
||||
app.component('RightToolbar', RightToolbar);
|
||||
app.component('uploadExcel', UploadExcel);
|
||||
app.component('UploadFile', UploadFile);
|
||||
app.component('UploadImg', UploadImg);
|
||||
app.component('Editor', Editor);
|
||||
app.component('Tip', Tip);
|
||||
app.component('DelWrap', DelWrap);
|
||||
app.component('TagList', TagList);
|
||||
app.component('SvgIcon', SvgIcon);
|
||||
app.component('ChinaArea', ChinaArea);
|
||||
app.component('OrgSelector', OrgSelector);
|
||||
app.component('Sign', Sign);
|
||||
app.component('PreviewImage', PreviewImage)
|
||||
install(app: App) {
|
||||
app.component("DictTag", DictTag);
|
||||
app.component("DictSelect", DictSelect);
|
||||
app.component("Pagination", Pagination);
|
||||
app.component("RightToolbar", RightToolbar);
|
||||
app.component("uploadExcel", UploadExcel);
|
||||
app.component("UploadFile", UploadFile);
|
||||
app.component("UploadImg", UploadImg);
|
||||
app.component("Editor", Editor);
|
||||
app.component("Tip", Tip);
|
||||
app.component("DelWrap", DelWrap);
|
||||
app.component("TagList", TagList);
|
||||
app.component("SvgIcon", SvgIcon);
|
||||
app.component("ChinaArea", ChinaArea);
|
||||
app.component("OrgSelector", OrgSelector);
|
||||
app.component("Sign", Sign);
|
||||
app.component("PreviewImage", PreviewImage);
|
||||
|
||||
app.component('DraggableDeptTree', DraggableDeptTree)
|
||||
app.component('DraggableOrgTree', DraggableOrgTree)
|
||||
app.component('DraggableDeviceGroup', DraggableDeviceGroup)
|
||||
app.component('DraggableDeviceGroupDraggable', DraggableDeviceGroupDraggable)
|
||||
app.component("DraggableDeptTree", DraggableDeptTree);
|
||||
app.component("DraggableOrgTree", DraggableOrgTree);
|
||||
app.component("DraggableDeviceGroup", DraggableDeviceGroup);
|
||||
app.component(
|
||||
"DraggableDeviceGroupDraggable",
|
||||
DraggableDeviceGroupDraggable
|
||||
);
|
||||
|
||||
app.component('TagOnline', TagOnline);
|
||||
app.component('TagSex', TagSex);
|
||||
app.component('TagSync', TagSync);
|
||||
app.component('TagUserStatus', TagUserStatus);
|
||||
app.component("TagOnline", TagOnline);
|
||||
app.component("TagSex", TagSex);
|
||||
app.component("TagSync", TagSync);
|
||||
app.component("TagUserStatus", TagUserStatus);
|
||||
|
||||
app.component('SelectArea', SelectArea);
|
||||
app.component('SelectDevice', SelectDevice);
|
||||
app.component('SelectDeviceGroup', SelectDeviceGroup)
|
||||
app.component('SelectDuty', SelectDuty);
|
||||
app.component('SelectDutyType', SelectDutyType);
|
||||
app.component('SelectTreeChannel', SelectTreeChannel);
|
||||
app.component('SelectTreeChannelRadio', SelectTreeChannelRadio);
|
||||
app.component('SelectTreeDept', SelectTreeDept);
|
||||
app.component('SelectDeviceTree', SelectDeviceTree);
|
||||
app.component('SelectAreaType', SelectAreaType);
|
||||
app.component('SelectPersonCarType', SelectPersonCarType);
|
||||
app.component('SelectPerson', SelectPerson);
|
||||
app.component("SelectArea", SelectArea);
|
||||
app.component("SelectDevice", SelectDevice);
|
||||
app.component("SelectDeviceGroup", SelectDeviceGroup);
|
||||
app.component("SelectDuty", SelectDuty);
|
||||
app.component("SelectDutyType", SelectDutyType);
|
||||
app.component("SelectTreeChannel", SelectTreeChannel);
|
||||
app.component("SelectTreeChannelRadio", SelectTreeChannelRadio);
|
||||
app.component("SelectTreeDept", SelectTreeDept);
|
||||
app.component("SelectOrgTree", SelectOrgTree);
|
||||
app.component("SelectDeviceTree", SelectDeviceTree);
|
||||
app.component("SelectAreaType", SelectAreaType);
|
||||
app.component("SelectPersonCarType", SelectPersonCarType);
|
||||
app.component("SelectPerson", SelectPerson);
|
||||
|
||||
app.component('ColumnBtn', ColumnBtn);
|
||||
app.component("ColumnBtn", ColumnBtn);
|
||||
|
||||
app.component('PreviewJson', PreviewJson);
|
||||
app.component("PreviewJson", PreviewJson);
|
||||
|
||||
app.component('RtspPluginVideo', RtspPluginVideo)
|
||||
app.component("RtspPluginVideo", RtspPluginVideo);
|
||||
|
||||
// 导入全部的elmenet-plus的图标
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component);
|
||||
// 兼容性
|
||||
app.component(`ele-${key}`, component);
|
||||
}
|
||||
// 导入布局插件
|
||||
app.component('Splitpanes', Splitpanes);
|
||||
app.component('Pane', Pane);
|
||||
app.use(ElementPlus); // ELEMENT 组件
|
||||
app.use(setupCalendar, {}); // 日历组件
|
||||
app.use(vue3TreeOrg); // 组织架构组件
|
||||
app.use(FcDesigner);
|
||||
app.use(FcDesigner.formCreate);
|
||||
},
|
||||
// 导入全部的elmenet-plus的图标
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component);
|
||||
// 兼容性
|
||||
app.component(`ele-${key}`, component);
|
||||
}
|
||||
// 导入布局插件
|
||||
app.component("Splitpanes", Splitpanes);
|
||||
app.component("Pane", Pane);
|
||||
app.use(ElementPlus); // ELEMENT 组件
|
||||
app.use(setupCalendar, {}); // 日历组件
|
||||
app.use(vue3TreeOrg); // 组织架构组件
|
||||
app.use(FcDesigner);
|
||||
app.use(FcDesigner.formCreate);
|
||||
},
|
||||
};
|
||||
@@ -2,25 +2,19 @@
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<!-- 顶部折线图-->
|
||||
<log-line-chart/>
|
||||
<log-line-chart />
|
||||
|
||||
<el-row class="mt-4 ml10" v-show="showSearch">
|
||||
<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList" ref="queryRef">
|
||||
<el-form-item :label="$t('syslog.logType')" prop="logType">
|
||||
<el-select :placeholder="$t('syslog.inputLogTypeTip')" clearable
|
||||
v-model="state.queryForm.logType">
|
||||
<el-option :key="item.value" :label="item.label" :value="item.value" v-for="item in log_type"/>
|
||||
<el-select :placeholder="$t('syslog.inputLogTypeTip')" clearable v-model="state.queryForm.logType">
|
||||
<el-option :key="item.value" :label="item.label" :value="item.value" v-for="item in log_type" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('syslog.createTime')" prop="createTime">
|
||||
<el-date-picker
|
||||
:end-placeholder="$t('syslog.inputEndPlaceholderTip')"
|
||||
:start-placeholder="$t('syslog.inputStartPlaceholderTip')"
|
||||
range-separator="To"
|
||||
type="datetimerange"
|
||||
v-model="state.queryForm.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
<el-date-picker :end-placeholder="$t('syslog.inputEndPlaceholderTip')"
|
||||
:start-placeholder="$t('syslog.inputStartPlaceholderTip')" range-separator="To" type="datetimerange"
|
||||
v-model="state.queryForm.createTime" value-format="YYYY-MM-DD HH:mm:ss" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList" icon="Search" type="primary">{{ $t('common.queryBtn') }}</el-button>
|
||||
@@ -32,31 +26,18 @@
|
||||
<el-row>
|
||||
<div class="mb-2" style="width: 100%">
|
||||
<el-button :disabled="multiple" v-auth="'sys_log_del'" @click="handleDelete(selectObjs)" class="ml10"
|
||||
icon="Delete" type="primary">
|
||||
icon="Delete" type="primary">
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
:export="'sys_log_export'"
|
||||
@exportExcel="exportExcel"
|
||||
@queryTable="getDataList"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
v-model:showSearch="showSearch"
|
||||
></right-toolbar>
|
||||
<right-toolbar :export="'sys_log_export'" @exportExcel="exportExcel" @queryTable="getDataList" class="ml10"
|
||||
style="float: right; margin-right: 20px" v-model:showSearch="showSearch"></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
:data="state.dataList"
|
||||
@selection-change="handleSelectionChange"
|
||||
@sort-change="sortChangeHandle"
|
||||
v-loading="state.loading"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column align="center" type="selection" width="40"/>
|
||||
<el-table-column :label="$t('syslog.index')" type="index" width="60"/>
|
||||
<el-table ref="tableRef" :data="state.dataList" @selection-change="handleSelectionChange"
|
||||
@sort-change="sortChangeHandle" v-loading="state.loading" border :cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle">
|
||||
<el-table-column align="center" type="selection" width="40" />
|
||||
<el-table-column :label="$t('syslog.index')" type="index" width="60" />
|
||||
<el-table-column :label="$t('syslog.logType')" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<dict-tag :options="log_type" :value="scope.row.logType"></dict-tag>
|
||||
@@ -71,44 +52,44 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('syslog.createTime')" prop="createTime" show-overflow-tooltip sortable="custom"
|
||||
width="200"></el-table-column>
|
||||
width="200"></el-table-column>
|
||||
<el-table-column :label="$t('syslog.createBy')" prop="createBy" show-overflow-tooltip sortable="custom"
|
||||
width="200"></el-table-column>
|
||||
width="200"></el-table-column>
|
||||
<el-table-column :label="$t('common.action')" width="150">
|
||||
<template #default="scope">
|
||||
<el-button icon="view" @click="LogDetailRef.openDialog(scope.row)" size="small" text type="primary">
|
||||
{{ $t('common.detailBtn') }}
|
||||
</el-button>
|
||||
<el-button v-auth="'sys_log_del'" icon="delete" @click="handleDelete([scope.row.id])" size="small" text
|
||||
type="primary">
|
||||
type="primary">
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle"
|
||||
v-bind="state.pagination"></pagination>
|
||||
<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle" v-bind="state.pagination">
|
||||
</pagination>
|
||||
<log-detail ref="LogDetailRef"></log-detail>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {BasicTableProps, useTable} from '/@/hooks/table';
|
||||
import {delObj, pageList} from '/@/api/admin/log';
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import {useMessage, useMessageBox} from '/@/hooks/message';
|
||||
import {useDict} from '/@/hooks/dict';
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { delObj, pageList } from '/@/api/admin/log';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
|
||||
const LogDetail = defineAsyncComponent(() => import('./detail.vue'));
|
||||
const LogLineChart = defineAsyncComponent(() => import('./line-chart.vue'));
|
||||
|
||||
const LogDetailRef = ref();
|
||||
|
||||
const {log_type} = useDict('log_type');
|
||||
const { log_type } = useDict('log_type');
|
||||
|
||||
const {t} = useI18n();
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const queryRef = ref();
|
||||
@@ -123,7 +104,7 @@ let tableRef = ref(null);
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
logType: '',
|
||||
createTime: '',
|
||||
createTime: [],
|
||||
serviceId: '',
|
||||
},
|
||||
selectObjs: [],
|
||||
@@ -134,7 +115,7 @@ const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
|
||||
// table hook
|
||||
const {
|
||||
downBlobFile,
|
||||
postDownBlobFile,
|
||||
getDataList,
|
||||
currentChangeHandle: baseCurrentChangeHandle,
|
||||
sortChangeHandle,
|
||||
@@ -158,12 +139,12 @@ const resetQuery = () => {
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/admin/log/export', state.queryForm, 'log.xlsx');
|
||||
postDownBlobFile('/admin/log/export', state.queryForm, 'log.xlsx');
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const handleSelectionChange = (objs: { id: string }[]) => {
|
||||
selectObjs.value = objs.map(({id}) => id);
|
||||
selectObjs.value = objs.map(({ id }) => id);
|
||||
multiple.value = !objs.length;
|
||||
};
|
||||
|
||||
@@ -187,7 +168,7 @@ const handleDelete = async (ids: string[]) => {
|
||||
// onMounted 通过路由参数给 serviceId 赋值
|
||||
const route = useRoute();
|
||||
onMounted(() => {
|
||||
const {serviceId} = route.query;
|
||||
const { serviceId } = route.query;
|
||||
if (serviceId) {
|
||||
state.queryForm.serviceId = serviceId;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="form.id ? '编辑摄像头' : '新增摄像头'" width="760px" destroy-on-close
|
||||
:close-on-click-modal="false" draggable>
|
||||
<el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="100px" v-loading="loading">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="设备编码" prop="deviceCode">
|
||||
<el-input v-model="form.deviceCode" disabled placeholder="自动带入当前设备编码" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="通道编号" prop="channelCode">
|
||||
<el-input v-model="form.channelCode" clearable placeholder="请输入通道编号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="通道名称" prop="channelName">
|
||||
<el-input v-model="form.channelName" clearable placeholder="请输入通道名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="摄像头类型" prop="cameraType">
|
||||
<dict-select v-model="form.cameraType" :options="camera_type" clearable
|
||||
placeholder="请选择摄像头类型" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="接入状态" prop="access">
|
||||
<dict-select v-model="form.access" :options="accessOptions" clearable placeholder="请选择接入状态" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="启用状态" prop="stat">
|
||||
<dict-select v-model="form.stat" :options="statOptions" clearable placeholder="请选择启用状态" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="通道能力" prop="capability">
|
||||
<el-input v-model="form.capability" type="textarea" :rows="3" placeholder="请输入通道能力集" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="通道描述" prop="memo">
|
||||
<el-input v-model="form.memo" type="textarea" :rows="3" placeholder="请输入通道描述" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="loading" @click="onSubmit">确认</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="deviceChannerForm">
|
||||
import { useDict } from '/@/hooks/dict'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { addObj, getObj, putObj } from '/@/api/device/deviceChannel'
|
||||
import { rule } from '/@/utils/validate'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const dataFormRef = ref()
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const currentDeviceCode = ref('')
|
||||
const { camera_type, yes_no_type } = useDict('camera_type', 'yes_no_type')
|
||||
|
||||
const accessOptions = [
|
||||
{ label: '未接入', value: '0' },
|
||||
{ label: '已接入', value: '1' },
|
||||
]
|
||||
|
||||
const statOptions = [
|
||||
{ label: '禁用', value: '0' },
|
||||
{ label: '启用', value: '1' },
|
||||
]
|
||||
|
||||
const initialForm = () => ({
|
||||
id: '',
|
||||
deviceCode: '',
|
||||
unitId: '',
|
||||
channelCode: '',
|
||||
channelName: '',
|
||||
channelSeq: '',
|
||||
channelSn: '',
|
||||
channelType: '',
|
||||
capability: '',
|
||||
cameraType: '',
|
||||
access: '',
|
||||
gpsX: '',
|
||||
gpsY: '',
|
||||
gpsZ: '',
|
||||
stat: '1',
|
||||
chExt: '',
|
||||
memo: '',
|
||||
sortOrder: 0,
|
||||
isSync: '',
|
||||
installLocation: '',
|
||||
})
|
||||
|
||||
const form = reactive(initialForm())
|
||||
|
||||
const dataRules = ref({
|
||||
deviceCode: [{ required: true, message: '缺少设备编码', trigger: 'blur' }],
|
||||
channelCode: [
|
||||
{ required: true, message: '请输入通道编号', trigger: 'blur' },
|
||||
{ validator: rule.checkSpace, trigger: 'blur' },
|
||||
{ validator: rule.overLength, trigger: 'blur' },
|
||||
],
|
||||
channelName: [
|
||||
{ required: true, message: '请输入通道名称', trigger: 'blur' },
|
||||
{ validator: rule.checkSpace, trigger: 'blur' },
|
||||
{ validator: rule.overLength, trigger: 'blur' },
|
||||
],
|
||||
cameraType: [{ required: true, message: '请选择摄像头类型', trigger: 'change' }],
|
||||
sortOrder: [{ required: true, message: '请输入排序', trigger: 'blur' }],
|
||||
memo: [{ validator: rule.overLength, trigger: 'blur' }],
|
||||
})
|
||||
|
||||
const resetForm = (deviceCode = '') => {
|
||||
Object.assign(form, initialForm(), {
|
||||
deviceCode,
|
||||
})
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
const getDeviceChannelData = async (id: string) => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res: any = await getObj(id)
|
||||
Object.assign(form, {
|
||||
...initialForm(),
|
||||
...res.data,
|
||||
sortOrder: res.data?.sortOrder !== null && res.data?.sortOrder !== undefined && res.data?.sortOrder !== ''
|
||||
? Number(res.data.sortOrder)
|
||||
: 0,
|
||||
deviceCode: res.data?.deviceCode || currentDeviceCode.value,
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const openDialog = (id = '', deviceCode = '') => {
|
||||
visible.value = true
|
||||
currentDeviceCode.value = deviceCode || ''
|
||||
resetForm(currentDeviceCode.value)
|
||||
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields()
|
||||
})
|
||||
|
||||
if (id) {
|
||||
form.id = id
|
||||
void getDeviceChannelData(id)
|
||||
}
|
||||
}
|
||||
|
||||
const onSubmit = async () => {
|
||||
const valid = await dataFormRef.value?.validate().catch(() => undefined)
|
||||
if (!valid) return false
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
const submitForm = {
|
||||
...form,
|
||||
sortOrder: form.sortOrder?.toString?.() ?? '0',
|
||||
}
|
||||
form.id ? await putObj(submitForm) : await addObj(submitForm)
|
||||
useMessage().success(form.id ? '修改成功' : '新增成功')
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openDialog,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.w100 {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,17 @@
|
||||
<template>
|
||||
<div class="containner">
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-left">
|
||||
<el-button icon="FolderAdd" type="primary" @click="handleAdd"
|
||||
v-auth="'admin_deviceChannel_add'">新增</el-button>
|
||||
<el-button icon="Delete" plain :disabled="multiple" @click="handleDelete(selectObjs)"
|
||||
v-auth="'admin_deviceChannel_del'">删除</el-button>
|
||||
</div>
|
||||
<el-button icon="Refresh" :loading="state.loading" circle @click="getDataList()" />
|
||||
</div>
|
||||
<el-table :data="state.dataList" v-loading="state.loading" border :cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle">
|
||||
:header-cell-style="tableStyle.headerCellStyle" @selection-change="selectionChangHandle">
|
||||
<el-table-column type="selection" width="48" align="center" />
|
||||
<el-table-column type="index" label="#" width="80" />
|
||||
<el-table-column prop="channelCode" align="left" width="150" label="通道编号" />
|
||||
<el-table-column prop="channelName" align="left" width="250" label="通道名称" />
|
||||
@@ -26,14 +36,28 @@
|
||||
<div class="color-danger" v-else>未安装</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="isSync" width="150" label="同步">
|
||||
<template #default="{ row }">
|
||||
<tag-sync :isSync="row.isSync == 1" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button icon="EditPen" text type="primary" @click="handleEdit(row.id)"
|
||||
v-auth="'admin_deviceChannel_edit'">编辑</el-button>
|
||||
<el-button icon="Delete" text type="primary" @click="handleDelete([row.id])"
|
||||
v-auth="'admin_deviceChannel_del'">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
|
||||
<div style="height: 15px;"></div>
|
||||
<device-channer-form ref="formDialogRef" @refresh="getDataList(false)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="deviceChannelList">
|
||||
import { ref, onMounted, reactive } from "vue"
|
||||
import { reactive } from "vue"
|
||||
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
|
||||
@@ -41,6 +65,7 @@ import * as DeviceChannelApi from "/@/api/device/deviceChannel";
|
||||
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { parseDeviceCapability } from "/@/utils/dispose"
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
deviceCode: string
|
||||
@@ -48,11 +73,16 @@ const props = withDefaults(defineProps<{
|
||||
deviceCode: ""
|
||||
})
|
||||
|
||||
const DeviceChannerForm = defineAsyncComponent(() => import('./device-channer-form.vue'))
|
||||
const formDialogRef = ref()
|
||||
const selectObjs = ref<string[]>([])
|
||||
const multiple = ref(true)
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
createdIsNeed: false,
|
||||
queryForm: {
|
||||
installLocation: true
|
||||
installLocation: true,
|
||||
deviceCode: props.deviceCode,
|
||||
},
|
||||
pageList: DeviceChannelApi.fetchList
|
||||
})
|
||||
@@ -64,24 +94,100 @@ const {
|
||||
getDataList,
|
||||
currentChangeHandle,
|
||||
sizeChangeHandle,
|
||||
sortChangeHandle,
|
||||
downBlobFile,
|
||||
tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
const selectionChangHandle = (objs: { id: string }[]) => {
|
||||
selectObjs.value = objs.map(({ id }) => id)
|
||||
multiple.value = !objs.length
|
||||
}
|
||||
|
||||
const handleAdd = () => {
|
||||
if (!props.deviceCode) {
|
||||
useMessage().warning('当前设备编码为空,无法新增摄像头')
|
||||
return
|
||||
}
|
||||
formDialogRef.value?.openDialog?.('', props.deviceCode)
|
||||
}
|
||||
|
||||
const handleEdit = (id: string) => {
|
||||
formDialogRef.value?.openDialog?.(id, props.deviceCode)
|
||||
}
|
||||
|
||||
const getDeleteConfirmMessage = (ids: string[]) => {
|
||||
const matchedRows = (state.dataList || []).filter((item: any) => ids.includes(item.id))
|
||||
const channelLabels = matchedRows
|
||||
.map((item: any) => {
|
||||
const channelName = item.channelName || '未命名通道'
|
||||
const channelCode = item.channelCode || '无编号'
|
||||
return `${channelName}(${channelCode})`
|
||||
})
|
||||
.filter(Boolean)
|
||||
|
||||
if (channelLabels.length === 1) {
|
||||
return `确定要删除通道“${channelLabels[0]}”吗`
|
||||
}
|
||||
|
||||
if (channelLabels.length > 1) {
|
||||
return `确定要删除以下通道吗: ${channelLabels.join('、')}`
|
||||
}
|
||||
|
||||
return ids.length === 1 ? '确定要删除该摄像头吗' : `确定要删除这 ${ids.length} 个摄像头吗`
|
||||
}
|
||||
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
if (!ids.length) {
|
||||
useMessage().warning('请先选择要删除的摄像头')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await useMessageBox().confirm(getDeleteConfirmMessage(ids))
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await DeviceChannelApi.delObjs(ids)
|
||||
selectObjs.value = []
|
||||
multiple.value = true
|
||||
getDataList()
|
||||
useMessage().success('删除成功')
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg)
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.deviceCode,
|
||||
(newVal, oldVal) => {
|
||||
if (newVal !== oldVal) {
|
||||
state.queryForm.deviceCode = newVal
|
||||
getDataList()
|
||||
if (newVal === oldVal && newVal) return
|
||||
state.queryForm.deviceCode = newVal
|
||||
selectObjs.value = []
|
||||
multiple.value = true
|
||||
if (!newVal) {
|
||||
state.dataList = []
|
||||
return
|
||||
}
|
||||
getDataList()
|
||||
}, { immediate: true }
|
||||
)
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
|
||||
&-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.tag-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -92,4 +198,4 @@ watch(
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -16,6 +16,9 @@
|
||||
<el-form-item style="width: 250px;" label="状态" prop="isOnline">
|
||||
<dict-select v-model="state.queryForm.isOnline" :options="is_nline" />
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 250px;" label="同步状态" prop="isSync">
|
||||
<dict-select v-model="state.queryForm.isSync" :options="is_sync" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button icon="search" type="primary" @click="getDataList">
|
||||
{{ $t('common.queryBtn') }}
|
||||
@@ -34,6 +37,9 @@
|
||||
v-auth="'admin_deviceManage_del'" @click="handleDelete(selectObjs)">
|
||||
删除
|
||||
</el-button>
|
||||
<el-button plain icon="upload-filled" class="ml10" @click="uploadExcelRef.show()">
|
||||
导 入
|
||||
</el-button>
|
||||
<right-toolbar v-model:showSearch="showSearch" :export="'admin_deviceManage_export'"
|
||||
@exportExcel="exportExcel" class="ml10 mr20" style="float: right;"
|
||||
@queryTable="getDataList"></right-toolbar>
|
||||
@@ -113,6 +119,8 @@
|
||||
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
|
||||
<preview-video-dialog ref="previewVideoRef" />
|
||||
<replay-video-dialog ref="replayVideoDialogRef" />
|
||||
<upload-excel ref="uploadExcelRef" title="设备导入" url="/admin/deviceManage/read-excel"
|
||||
temp-url="/admin/sys-file/local/file/device_import_temp.xlsx" @refreshDataList="getDataList(false)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -138,6 +146,7 @@ const formDialogRef = ref()
|
||||
const previewVideoRef = ref()
|
||||
const replayVideoDialogRef = ref()
|
||||
// 搜索变量
|
||||
const uploadExcelRef = ref()
|
||||
const queryRef = ref()
|
||||
const showSearch = ref(true)
|
||||
// 多选变量
|
||||
|
||||
@@ -30,13 +30,13 @@
|
||||
v-auth="'admin_deviceManage_add'">
|
||||
新 增
|
||||
</el-button>
|
||||
<el-button plain icon="upload-filled" class="ml10" @click="uploadExcelRef.show()">
|
||||
导 入
|
||||
</el-button>
|
||||
<el-button plain :disabled="multiple" icon="Delete" type="primary" v-auth="'admin_deviceManage_del'"
|
||||
@click="handleDelete(selectObjs)">
|
||||
删除
|
||||
</el-button>
|
||||
<el-button plain icon="upload-filled" class="ml10" @click="uploadExcelRef.show()">
|
||||
导 入
|
||||
</el-button>
|
||||
<right-toolbar v-model:showSearch="showSearch" :export="'admin_deviceManage_export'"
|
||||
@exportExcel="exportExcel" class="ml10 mr20" style="float: right;"
|
||||
@queryTable="getDataList"></right-toolbar>
|
||||
|
||||
@@ -2,96 +2,41 @@
|
||||
<el-dialog :title="form.id ? '编辑' : '新增'" v-model="visible" :close-on-click-modal="false" draggable>
|
||||
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="上级组织" prop="orgPreCode">
|
||||
<SelectOrgTree v-model="form.orgPreCode" value-key="orgCode" placeholder="请选择上级组织" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="组织编码" prop="orgCode">
|
||||
<el-input v-model="form.orgCode" placeholder="请输入组织编码" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="组织名称" prop="orgName">
|
||||
<el-input v-model="form.orgName" placeholder="请输入组织名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="唯一标识码" prop="orgSn">
|
||||
<el-input v-model="form.orgSn" placeholder="请输入唯一标识码" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="组织结构类型(字典)" prop="orgType">
|
||||
<el-input v-model="form.orgType" placeholder="请输入组织结构类型(字典)" />
|
||||
<el-form-item label="组织类型" prop="orgType">
|
||||
<dict-select disabled v-model="form.orgType" :options="dh_org_type" placeholder="请选择组织类型" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="省" prop="province">
|
||||
<el-input v-model="form.province" placeholder="请输入省" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="市" prop="city">
|
||||
<el-input v-model="form.city" placeholder="请输入市" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="县" prop="county">
|
||||
<el-input v-model="form.county" placeholder="请输入县" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="域ID" prop="domainId">
|
||||
<el-input v-model="form.domainId" placeholder="请输入域ID" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="状态" prop="stat">
|
||||
<el-input v-model="form.stat" placeholder="请输入状态" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="排序码" prop="sort">
|
||||
<el-input v-model="form.sort" placeholder="请输入排序码" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="所有组排序织" prop="allSort">
|
||||
<el-input v-model="form.allSort" placeholder="请输入所有组排序织" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="子系统服务编码,默认evo-thirdParty" prop="service">
|
||||
<el-input v-model="form.service" placeholder="请输入子系统服务编码,默认evo-thirdParty" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="禁止绑定用户 :0-可绑定用户,1-不可绑定用户" prop="forbidBindUser">
|
||||
<el-input v-model="form.forbidBindUser" placeholder="请输入禁止绑定用户 :0-可绑定用户,1-不可绑定用户" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="禁止绑定用户服务" prop="forbidBindService">
|
||||
<el-input v-model="form.forbidBindService" placeholder="请输入禁止绑定用户服务" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="是否为同步" prop="isSync">
|
||||
<el-input v-model="form.isSync" placeholder="请输入是否为同步" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -107,7 +52,6 @@
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { useMessage } from "/@/hooks/message";
|
||||
import { getObj, addObj, putObj } from '/@/api/sys/sysOrg'
|
||||
import { rule } from '/@/utils/validate';
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
// 定义变量内容
|
||||
@@ -115,14 +59,15 @@ const dataFormRef = ref();
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
// 定义字典
|
||||
const { dh_org_type } = useDict("dh_org_type")
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
const defaultForm = {
|
||||
id: '',
|
||||
orgCode: '',
|
||||
orgName: '',
|
||||
orgSn: '',
|
||||
orgType: '',
|
||||
orgType: 'org_base',
|
||||
orgPreCode: '',
|
||||
province: '',
|
||||
city: '',
|
||||
county: '',
|
||||
@@ -134,16 +79,30 @@ const form = reactive({
|
||||
forbidBindUser: '',
|
||||
forbidBindService: '',
|
||||
isSync: '',
|
||||
});
|
||||
}
|
||||
|
||||
const createForm = () => ({
|
||||
...defaultForm,
|
||||
})
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive(createForm());
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = ref({
|
||||
orgCode: [{ required: true, message: '组织编码不能为空', trigger: 'blur' }],
|
||||
orgName: [{ required: true, message: '组织名称不能为空', trigger: 'blur' }],
|
||||
orgType: [{ required: true, message: '组织类型不能为空', trigger: 'change' }],
|
||||
})
|
||||
|
||||
const resetForm = () => {
|
||||
Object.assign(form, createForm());
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string) => {
|
||||
const openDialog = (id = '') => {
|
||||
visible.value = true
|
||||
form.id = ''
|
||||
resetForm()
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
@@ -177,18 +136,24 @@ const onSubmit = async () => {
|
||||
|
||||
|
||||
// 初始化表单数据
|
||||
const getsysOrgData = (id: string) => {
|
||||
// 获取数据
|
||||
loading.value = true
|
||||
getObj(id).then((res: any) => {
|
||||
const getsysOrgData = async (id: string) => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res: any = await getObj(id)
|
||||
// default
|
||||
if (!res.data.orgType) {
|
||||
res.data.orgType = defaultForm.orgType
|
||||
}
|
||||
Object.assign(form, res.data)
|
||||
}).finally(() => {
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
+145
-68
@@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="h-full flex flex-row gap-2">
|
||||
<div class="h-full flex flex-row gap-2" v-loading="pageLoading">
|
||||
<div class="w-[250px]">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<div :class="data.selectTopIndex == i ? 'border-blue-400' : ''"
|
||||
class="flex gap-4 items-center p-3 border-2 rounded-lg mt-1" v-for="(v, i) in data.topList">
|
||||
class="flex gap-4 items-center p-3 border-2 rounded-lg mt-1 cursor-pointer transition-colors hover:border-blue-300"
|
||||
v-for="(v, i) in data.topList" :key="getNodeKey(v, i)" @click="handleTopOrgClick(v, i)">
|
||||
<el-icon size="40">
|
||||
<OfficeBuilding />
|
||||
</el-icon>
|
||||
@@ -43,6 +44,18 @@
|
||||
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<div class="flex items-center justify-between mb20">
|
||||
<div class="text-base font-medium">组织详情</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<el-button icon="FolderAdd" type="primary" @click="handleAdd">新增</el-button>
|
||||
<el-button icon="Edit" type="primary" plain :disabled="!data.currentOrgId"
|
||||
@click="handleEdit">编辑</el-button>
|
||||
<el-button icon="Delete" type="danger" plain :disabled="!data.currentOrgId"
|
||||
@click="handleDeleteCurrent">删除</el-button>
|
||||
<right-toolbar :search="false" :export="'admin_sysPerson_export'" @exportExcel="exportExcel"
|
||||
class="ml10 mr20" style="float: right;" @queryTable="handleRefresh" />
|
||||
</div>
|
||||
</div>
|
||||
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px"
|
||||
v-loading="data.formLoading">
|
||||
<el-col :span="24" class="mb20">
|
||||
@@ -65,6 +78,11 @@
|
||||
<dict-select v-model="form.orgType" disabled :options="dh_org_type" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="是否同步" prop="isSync">
|
||||
<dict-select v-model="form.isSync" disabled :options="yes_no_type" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -72,49 +90,55 @@
|
||||
|
||||
</div>
|
||||
<!-- 编辑、新增 -->
|
||||
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
|
||||
<form-dialog ref="formDialogRef" @refresh="handleRefresh" />
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemSysOrg">
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import * as SysOrgApi from "/@/api/sys/sysOrg";
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
|
||||
import other from "/@/utils/other"
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
// 定义查询字典
|
||||
const { dh_org_type } = useDict("dh_org_type")
|
||||
const { dh_org_type, yes_no_type } = useDict("dh_org_type", "yes_no_type")
|
||||
const orgTreeRef = ref()
|
||||
// 定义变量内容
|
||||
const formDialogRef = ref()
|
||||
// 搜索变量
|
||||
const queryRef = ref()
|
||||
const showSearch = ref(true)
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any
|
||||
const multiple = ref(true)
|
||||
|
||||
onMounted(() => {
|
||||
getTopOrg()
|
||||
getTree()
|
||||
})
|
||||
|
||||
const createDetailForm = () => ({
|
||||
id: '',
|
||||
orgPreName: '',
|
||||
orgCode: '',
|
||||
orgName: '',
|
||||
orgType: '',
|
||||
})
|
||||
|
||||
const data = reactive<any>({
|
||||
selectTopIndex: 0,
|
||||
topList: [],
|
||||
treeList: [],
|
||||
treeSearchName: '',
|
||||
topLoading: false,
|
||||
treeLoading: false,
|
||||
formLoading: false,
|
||||
refreshLoading: false,
|
||||
currentNodeKey: '',
|
||||
currentOrgId: '',
|
||||
})
|
||||
|
||||
const form = reactive<any>({
|
||||
const form = reactive<any>(createDetailForm())
|
||||
|
||||
})
|
||||
const pageLoading = computed(() => data.refreshLoading || data.topLoading || data.treeLoading || data.formLoading)
|
||||
|
||||
const dataRules = {
|
||||
orgPreName: [
|
||||
@@ -131,59 +155,69 @@ const dataRules = {
|
||||
],
|
||||
}
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {},
|
||||
pageList: SysOrgApi.fetchList
|
||||
})
|
||||
|
||||
// table hook
|
||||
const {
|
||||
getDataList,
|
||||
currentChangeHandle,
|
||||
sizeChangeHandle,
|
||||
sortChangeHandle,
|
||||
downBlobFile,
|
||||
tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
// 清空搜索条件
|
||||
queryRef.value?.resetFields()
|
||||
// 清空多选
|
||||
selectObjs.value = []
|
||||
getDataList()
|
||||
const resetDetailForm = () => {
|
||||
Object.assign(form, createDetailForm())
|
||||
}
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/admin/sysOrg/export', Object.assign(state.queryForm, { ids: selectObjs }), 'sysOrg.xlsx')
|
||||
const getOrgId = (node: any) => node?.primaryId || node?.id || ''
|
||||
|
||||
const getNodeKey = (node: any, index: string | number = 0) => node?.id || node?.primaryId || `${node?.orgCode || 'org'}-${index}`
|
||||
|
||||
const findTreeNode = (nodes: Array<any>, orgId: string): any | null => {
|
||||
if (!orgId || !Array.isArray(nodes)) return null
|
||||
for (const node of nodes) {
|
||||
if (getOrgId(node) === orgId) return node
|
||||
if (node.children?.length) {
|
||||
const childNode: any | null = findTreeNode(node.children, orgId)
|
||||
if (childNode) return childNode
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// 多选事件
|
||||
const selectionChangHandle = (objs: { id: string }[]) => {
|
||||
selectObjs.value = objs.map(({ id }) => id);
|
||||
multiple.value = !objs.length;
|
||||
};
|
||||
const loadOrgDetail = async (orgId: string) => {
|
||||
if (!orgId) {
|
||||
data.currentOrgId = ''
|
||||
resetDetailForm()
|
||||
return
|
||||
}
|
||||
try {
|
||||
data.formLoading = true
|
||||
const res = await SysOrgApi.getObj(orgId)
|
||||
if (res.code != 0) throw new Error()
|
||||
data.currentOrgId = orgId
|
||||
resetDetailForm()
|
||||
Object.assign(form, res.data)
|
||||
if (!form.orgPreName) form.orgPreName = '无'
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
data.formLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
// 点击树
|
||||
const handleNodeClick = async (e: any) => {
|
||||
// 查询详情
|
||||
const res = await SysOrgApi.getObj(e.primaryId)
|
||||
if (res.code == 0) {
|
||||
Object.assign(form, res.data)
|
||||
if (!form.orgPreName) form.orgPreName = '无'
|
||||
}
|
||||
data.currentNodeKey = e.id
|
||||
await loadOrgDetail(getOrgId(e))
|
||||
}
|
||||
|
||||
const handleTopOrgClick = async (row: any, index: string | number) => {
|
||||
data.selectTopIndex = index
|
||||
await loadOrgDetail(getOrgId(row))
|
||||
}
|
||||
|
||||
// 获取顶级组织
|
||||
const getTopOrg = async () => {
|
||||
try {
|
||||
data.topLoading = true
|
||||
const { code, data: orgData } = await SysOrgApi.getTopOrg();
|
||||
if (code != 0) throw new Error()
|
||||
data.topList = orgData
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
data.topLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,50 +243,93 @@ const filterVisibleNodes = (nodes: Array<any>) => {
|
||||
}
|
||||
|
||||
// 获取组织树状结构
|
||||
const getTree = async () => {
|
||||
const getTree = async (selectedOrgId = data.currentOrgId) => {
|
||||
try {
|
||||
data.treeLoading = true
|
||||
const _data = {
|
||||
orgName: data.treeSearchName
|
||||
}
|
||||
const { code, data: orgTree } = await SysOrgApi.getTree(_data);
|
||||
if (code != 0) throw new Error()
|
||||
data.treeList = orgTree
|
||||
if (orgTree && orgTree.length) {
|
||||
data.treeList = filterVisibleNodes(orgTree)
|
||||
}
|
||||
// 默认选择第一个
|
||||
if (orgTree && orgTree.length) {
|
||||
nextTick(() => {
|
||||
orgTreeRef.value.setCurrentKey(orgTree[0].id)
|
||||
})
|
||||
// 查询详情
|
||||
const res = await SysOrgApi.getObj(orgTree[0].primaryId)
|
||||
if (res.code == 0) {
|
||||
Object.assign(form, res.data)
|
||||
if (!form.orgPreName) form.orgPreName = '无'
|
||||
}
|
||||
data.treeList = Array.isArray(orgTree) ? filterVisibleNodes(orgTree) : []
|
||||
const selectedNode = findTreeNode(data.treeList, selectedOrgId) || data.treeList[0]
|
||||
if (!selectedNode) {
|
||||
data.currentNodeKey = ''
|
||||
await loadOrgDetail('')
|
||||
return
|
||||
}
|
||||
data.currentNodeKey = selectedNode.id
|
||||
nextTick(() => {
|
||||
orgTreeRef.value?.setCurrentKey(selectedNode.id)
|
||||
})
|
||||
await loadOrgDetail(getOrgId(selectedNode))
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
data.treeLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleAdd = () => {
|
||||
formDialogRef.value?.openDialog?.()
|
||||
}
|
||||
|
||||
const handleEdit = () => {
|
||||
if (!data.currentOrgId) {
|
||||
useMessage().warning('请先选择组织');
|
||||
return
|
||||
}
|
||||
formDialogRef.value?.openDialog?.(data.currentOrgId)
|
||||
}
|
||||
|
||||
const handleRefresh = async () => {
|
||||
try {
|
||||
data.refreshLoading = true
|
||||
await getTopOrg()
|
||||
await getTree()
|
||||
} finally {
|
||||
data.refreshLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteCurrent = async () => {
|
||||
if (!data.currentOrgId) {
|
||||
useMessage().warning('请先选择组织');
|
||||
return
|
||||
}
|
||||
await handleDelete([data.currentOrgId])
|
||||
}
|
||||
|
||||
const getDeleteConfirmMessage = (ids: string[]) => {
|
||||
if (ids.length === 1) {
|
||||
const currentOrgName = ids[0] === data.currentOrgId ? form.orgName : ''
|
||||
return currentOrgName ? `确定要删除组织“${currentOrgName}”吗` : '确定要删除该组织吗'
|
||||
}
|
||||
return `确定要删除这 ${ids.length} 个组织吗`
|
||||
}
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
try {
|
||||
await useMessageBox().confirm('此操作将永久删除');
|
||||
await useMessageBox().confirm(getDeleteConfirmMessage(ids));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await SysOrgApi.delObjs(ids);
|
||||
getDataList();
|
||||
await handleRefresh();
|
||||
useMessage().success('删除成功');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
const timeSuffix = dayjs().format('YYYY-MM-DD_HH-mm-ss')
|
||||
other.poseDownBlobFile('/admin/sysOrg/export', {}, `组织管理_${timeSuffix}.xlsx`)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user