forked from wangjiadong/comp
Compare commits
2 Commits
0820c60529
...
0c12dc5341
Author | SHA1 | Date |
---|---|---|
喻忠伟 | 0c12dc5341 | 4 months ago |
喻忠伟 | c930d4fd5e | 4 months ago |
21 changed files with 1197 additions and 32 deletions
@ -0,0 +1,78 @@ |
|||||||
|
<template> |
||||||
|
<BasicDrawer title="数据规则/按钮权限配置" :width="365" @close="onClose" @register="registerDrawer"> |
||||||
|
<a-spin :spinning="loading"> |
||||||
|
<a-tabs defaultActiveKey="1"> |
||||||
|
<a-tab-pane tab="数据规则" key="1"> |
||||||
|
<a-checkbox-group v-model:value="dataRuleChecked" v-if="dataRuleList.length > 0"> |
||||||
|
<a-row> |
||||||
|
<a-col :span="24" v-for="(item, index) in dataRuleList" :key="'dr' + index"> |
||||||
|
<a-checkbox :value="item.id">{{ item.ruleName }}</a-checkbox> |
||||||
|
</a-col> |
||||||
|
<a-col :span="24"> |
||||||
|
<div style="width: 100%; margin-top: 15px"> |
||||||
|
<a-button type="primary" :loading="loading" :size="'small'" preIcon="ant-design:save-filled" @click="saveDataRuleForRole"> |
||||||
|
<span>点击保存</span> |
||||||
|
</a-button> |
||||||
|
</div> |
||||||
|
</a-col> |
||||||
|
</a-row> |
||||||
|
</a-checkbox-group> |
||||||
|
<a-empty v-else description="无配置信息" /> |
||||||
|
</a-tab-pane> |
||||||
|
</a-tabs> |
||||||
|
</a-spin> |
||||||
|
</BasicDrawer> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script lang="ts" setup> |
||||||
|
import { ref, unref } from 'vue'; |
||||||
|
import { BasicDrawer, useDrawerInner } from '/@/components/Drawer'; |
||||||
|
|
||||||
|
import { queryDepartDataRule, saveDepartDataRule } from '../depart.api'; |
||||||
|
|
||||||
|
defineEmits(['register']); |
||||||
|
const loading = ref<boolean>(false); |
||||||
|
const departId = ref(''); |
||||||
|
const functionId = ref(''); |
||||||
|
const dataRuleList = ref<Array<any>>([]); |
||||||
|
const dataRuleChecked = ref<Array<any>>([]); |
||||||
|
|
||||||
|
// 注册抽屉组件 |
||||||
|
const [registerDrawer, { closeDrawer }] = useDrawerInner((data) => { |
||||||
|
departId.value = unref(data.departId); |
||||||
|
functionId.value = unref(data.functionId); |
||||||
|
loadData(); |
||||||
|
}); |
||||||
|
|
||||||
|
async function loadData() { |
||||||
|
try { |
||||||
|
loading.value = true; |
||||||
|
const { datarule, drChecked } = await queryDepartDataRule(functionId, departId); |
||||||
|
dataRuleList.value = datarule; |
||||||
|
if (drChecked) { |
||||||
|
dataRuleChecked.value = drChecked.split(','); |
||||||
|
} |
||||||
|
} finally { |
||||||
|
loading.value = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function saveDataRuleForRole() { |
||||||
|
let params = { |
||||||
|
departId: departId.value, |
||||||
|
permissionId: functionId.value, |
||||||
|
dataRuleIds: dataRuleChecked.value.join(','), |
||||||
|
}; |
||||||
|
saveDepartDataRule(params); |
||||||
|
} |
||||||
|
|
||||||
|
function onClose() { |
||||||
|
doReset(); |
||||||
|
} |
||||||
|
|
||||||
|
function doReset() { |
||||||
|
functionId.value = ''; |
||||||
|
dataRuleList.value = []; |
||||||
|
dataRuleChecked.value = []; |
||||||
|
} |
||||||
|
</script> |
@ -0,0 +1,92 @@ |
|||||||
|
<template> |
||||||
|
<BasicModal :title="title" :width="800" v-bind="$attrs" @ok="handleOk" @register="registerModal"> |
||||||
|
<BasicForm @register="registerForm" /> |
||||||
|
</BasicModal> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script lang="ts" setup> |
||||||
|
import { watch, computed, inject, ref, unref, onMounted } from 'vue'; |
||||||
|
|
||||||
|
import { BasicForm, useForm } from '/@/components/Form/index'; |
||||||
|
import { BasicModal, useModalInner } from '/@/components/Modal'; |
||||||
|
|
||||||
|
import { saveOrUpdateDepart } from '../depart.api'; |
||||||
|
import { useBasicFormSchema, orgCategoryOptions } from '../depart.data'; |
||||||
|
|
||||||
|
const emit = defineEmits(['success', 'register']); |
||||||
|
const props = defineProps({ |
||||||
|
rootTreeData: { type: Array, default: () => [] }, |
||||||
|
}); |
||||||
|
const prefixCls = inject('prefixCls'); |
||||||
|
// 当前是否是更新模式 |
||||||
|
const isUpdate = ref<boolean>(false); |
||||||
|
// 当前的弹窗数据 |
||||||
|
const model = ref<object>({}); |
||||||
|
const title = computed(() => (isUpdate.value ? '编辑' : '新增')); |
||||||
|
|
||||||
|
//注册表单 |
||||||
|
const [registerForm, { resetFields, setFieldsValue, validate, updateSchema }] = useForm({ |
||||||
|
schemas: useBasicFormSchema().basicFormSchema, |
||||||
|
showActionButtonGroup: false, |
||||||
|
}); |
||||||
|
|
||||||
|
// 注册弹窗 |
||||||
|
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { |
||||||
|
await resetFields(); |
||||||
|
isUpdate.value = unref(data?.isUpdate); |
||||||
|
// 当前是否为添加子级 |
||||||
|
let isChild = unref(data?.isChild); |
||||||
|
let categoryOptions = isChild ? orgCategoryOptions.child : orgCategoryOptions.root; |
||||||
|
// 隐藏不需要展示的字段 |
||||||
|
updateSchema([ |
||||||
|
{ |
||||||
|
field: 'parentId', |
||||||
|
show: isChild, |
||||||
|
componentProps: { |
||||||
|
// 如果是添加子部门,就禁用该字段 |
||||||
|
disabled: isChild, |
||||||
|
treeData: props.rootTreeData, |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
field: 'orgCode', |
||||||
|
show: false, |
||||||
|
}, |
||||||
|
{ |
||||||
|
field: 'orgCategory', |
||||||
|
componentProps: { options: categoryOptions }, |
||||||
|
}, |
||||||
|
]); |
||||||
|
|
||||||
|
let record = unref(data?.record); |
||||||
|
if (typeof record !== 'object') { |
||||||
|
record = {}; |
||||||
|
} |
||||||
|
// 赋默认值 |
||||||
|
record = Object.assign( |
||||||
|
{ |
||||||
|
departOrder: 0, |
||||||
|
orgCategory: categoryOptions[0].value, |
||||||
|
}, |
||||||
|
record |
||||||
|
); |
||||||
|
model.value = record; |
||||||
|
await setFieldsValue({ ...record }); |
||||||
|
}); |
||||||
|
|
||||||
|
// 提交事件 |
||||||
|
async function handleOk() { |
||||||
|
try { |
||||||
|
setModalProps({ confirmLoading: true }); |
||||||
|
let values = await validate(); |
||||||
|
//提交表单 |
||||||
|
await saveOrUpdateDepart(values, isUpdate.value); |
||||||
|
//关闭弹窗 |
||||||
|
closeModal(); |
||||||
|
//刷新列表 |
||||||
|
emit('success'); |
||||||
|
} finally { |
||||||
|
setModalProps({ confirmLoading: false }); |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
@ -0,0 +1,135 @@ |
|||||||
|
<template> |
||||||
|
<a-spin :spinning="loading"> |
||||||
|
<BasicForm @register="registerForm" /> |
||||||
|
<!-- <div class="j-box-bottom-button offset-20" style="margin-top: 30px"> |
||||||
|
<div class="j-box-bottom-button-float" :class="[`${prefixCls}`]"> |
||||||
|
<a-button preIcon="ant-design:sync-outlined" @click="onReset">重置</a-button> |
||||||
|
<a-button type="primary" preIcon="ant-design:save-filled" @click="onSubmit">保存</a-button> |
||||||
|
</div> |
||||||
|
</div>--> |
||||||
|
</a-spin> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script lang="ts" setup> |
||||||
|
import { watch, computed, inject, ref, unref, onMounted } from 'vue'; |
||||||
|
import { BasicForm, useForm } from '/@/components/Form/index'; |
||||||
|
import { saveOrUpdateDepart } from '../depart.api'; |
||||||
|
import { useBasicFormSchema, orgCategoryOptions } from '../depart.data'; |
||||||
|
import { Api, queryDepartSync } from '../depart.api'; |
||||||
|
import { useDesign } from '/@/hooks/web/useDesign'; |
||||||
|
|
||||||
|
const { prefixCls } = useDesign('j-depart-form-content'); |
||||||
|
|
||||||
|
const emit = defineEmits(['success']); |
||||||
|
const props = defineProps({ |
||||||
|
data: { type: Object, default: () => ({}) }, |
||||||
|
rootTreeData: { type: Array, default: () => [] }, |
||||||
|
}); |
||||||
|
const loading = ref<boolean>(false); |
||||||
|
// 当前是否是更新模式 |
||||||
|
const isUpdate = ref<boolean>(true); |
||||||
|
// 当前的弹窗数据 |
||||||
|
const model = ref<object>({}); |
||||||
|
|
||||||
|
//注册表单 |
||||||
|
const [registerForm, { resetFields, setFieldsValue, validate, updateSchema }] = useForm({ |
||||||
|
schemas: useBasicFormSchema().basicFormSchema, |
||||||
|
showActionButtonGroup: false, |
||||||
|
}); |
||||||
|
|
||||||
|
const categoryOptions = computed(() => { |
||||||
|
if (!!props?.data?.parentId) { |
||||||
|
return orgCategoryOptions.child; |
||||||
|
} else { |
||||||
|
return orgCategoryOptions.root; |
||||||
|
} |
||||||
|
}); |
||||||
|
const setFormValue = async() => { |
||||||
|
const res = await queryDepartSync() |
||||||
|
await setFieldsValue({ ...res }); |
||||||
|
} |
||||||
|
onMounted(() => { |
||||||
|
setFormValue() |
||||||
|
// 禁用字段 |
||||||
|
updateSchema([ |
||||||
|
{ field: 'parentId', componentProps: { disabled: true } }, |
||||||
|
{ field: 'orgCode', componentProps: { disabled: true } }, |
||||||
|
]); |
||||||
|
// // data 变化,重填表单 |
||||||
|
// watch( |
||||||
|
// () => props.data, |
||||||
|
// async () => { |
||||||
|
// let record = unref(props.data); |
||||||
|
// if (typeof record !== 'object') { |
||||||
|
// record = {}; |
||||||
|
// } |
||||||
|
// |
||||||
|
// model.value = record; |
||||||
|
// console.log(record) |
||||||
|
// await resetFields(); |
||||||
|
// await setFieldsValue({ ...record }); |
||||||
|
// }, |
||||||
|
// { deep: true, immediate: true } |
||||||
|
// ); |
||||||
|
// 更新 父部门 选项 |
||||||
|
watch( |
||||||
|
() => props.rootTreeData, |
||||||
|
async () => { |
||||||
|
updateSchema([ |
||||||
|
{ |
||||||
|
field: 'parentId', |
||||||
|
componentProps: { treeData: props.rootTreeData }, |
||||||
|
}, |
||||||
|
]); |
||||||
|
}, |
||||||
|
{ deep: true, immediate: true } |
||||||
|
); |
||||||
|
// 监听并更改 orgCategory options |
||||||
|
watch( |
||||||
|
categoryOptions, |
||||||
|
async () => { |
||||||
|
updateSchema([ |
||||||
|
{ |
||||||
|
field: 'orgCategory', |
||||||
|
componentProps: { options: categoryOptions.value }, |
||||||
|
}, |
||||||
|
]); |
||||||
|
}, |
||||||
|
{ immediate: true } |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
// 重置表单 |
||||||
|
async function onReset() { |
||||||
|
await resetFields(); |
||||||
|
await setFieldsValue({ ...model.value }); |
||||||
|
} |
||||||
|
|
||||||
|
// 提交事件 |
||||||
|
async function onSubmit() { |
||||||
|
try { |
||||||
|
loading.value = true; |
||||||
|
let values = await validate(); |
||||||
|
values = Object.assign({}, model.value, values); |
||||||
|
//提交表单 |
||||||
|
await saveOrUpdateDepart(values, isUpdate.value); |
||||||
|
//刷新列表 |
||||||
|
emit('success'); |
||||||
|
Object.assign(model.value, values); |
||||||
|
} finally { |
||||||
|
loading.value = false; |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
<style lang="less"> |
||||||
|
// update-begin-author:liusq date:20230625 for: [issues/563]暗色主题部分失效 |
||||||
|
|
||||||
|
@prefix-cls: ~'@{namespace}-j-depart-form-content'; |
||||||
|
/*begin 兼容暗夜模式*/ |
||||||
|
.@{prefix-cls} { |
||||||
|
background: @component-background; |
||||||
|
border-top: 1px solid @border-color-base; |
||||||
|
} |
||||||
|
/*end 兼容暗夜模式*/ |
||||||
|
// update-end-author:liusq date:20230625 for: [issues/563]暗色主题部分失效 |
||||||
|
</style> |
@ -0,0 +1,332 @@ |
|||||||
|
<template> |
||||||
|
<a-card :bordered="false" style="height: 100%"> |
||||||
|
<div class="j-table-operator" style="width: 100%"> |
||||||
|
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="onAddDepart">新增</a-button> |
||||||
|
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="onAddChildDepart()">添加下级</a-button> |
||||||
|
<a-upload name="file" :showUploadList="false" :customRequest="onImportXls"> |
||||||
|
<a-button type="primary" preIcon="ant-design:import-outlined">导入</a-button> |
||||||
|
</a-upload> |
||||||
|
<a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls">导出</a-button> |
||||||
|
<a-button type="primary" preIcon="ant-design:sync-outlined">同步企微?</a-button> |
||||||
|
<a-button type="primary" preIcon="ant-design:sync-outlined">同步钉钉?</a-button> |
||||||
|
<template v-if="checkedKeys.length > 0"> |
||||||
|
<a-dropdown> |
||||||
|
<template #overlay> |
||||||
|
<a-menu> |
||||||
|
<a-menu-item key="1" @click="onDeleteBatch"> |
||||||
|
<icon icon="ant-design:delete-outlined" /> |
||||||
|
<span>删除</span> |
||||||
|
</a-menu-item> |
||||||
|
</a-menu> |
||||||
|
</template> |
||||||
|
<a-button> |
||||||
|
<span>批量操作 </span> |
||||||
|
<icon icon="akar-icons:chevron-down" /> |
||||||
|
</a-button> |
||||||
|
</a-dropdown> |
||||||
|
</template> |
||||||
|
</div> |
||||||
|
<a-alert type="info" show-icon class="alert" style="margin-bottom: 8px"> |
||||||
|
<template #message> |
||||||
|
<template v-if="checkedKeys.length > 0"> |
||||||
|
<span>已选中 {{ checkedKeys.length }} 条记录</span> |
||||||
|
<a-divider type="vertical" /> |
||||||
|
<a @click="checkedKeys = []">清空</a> |
||||||
|
</template> |
||||||
|
<template v-else> |
||||||
|
<span>未选中任何数据</span> |
||||||
|
</template> |
||||||
|
</template> |
||||||
|
</a-alert> |
||||||
|
<a-spin :spinning="loading"> |
||||||
|
<a-input-search placeholder="按部门名称搜索…" style="margin-bottom: 10px" @search="onSearch" /> |
||||||
|
<!--组织机构树--> |
||||||
|
<template v-if="treeData.length > 0"> |
||||||
|
<a-tree |
||||||
|
v-if="!treeReloading" |
||||||
|
checkable |
||||||
|
:clickRowToExpand="false" |
||||||
|
:treeData="treeData" |
||||||
|
:selectedKeys="selectedKeys" |
||||||
|
:checkStrictly="checkStrictly" |
||||||
|
:load-data="loadChildrenTreeData" |
||||||
|
:checkedKeys="checkedKeys" |
||||||
|
v-model:expandedKeys="expandedKeys" |
||||||
|
@check="onCheck" |
||||||
|
@select="onSelect" |
||||||
|
> |
||||||
|
<template #title="{ key: treeKey, title, dataRef }"> |
||||||
|
<a-dropdown :trigger="['contextmenu']"> |
||||||
|
<Popconfirm |
||||||
|
:visible="visibleTreeKey === treeKey" |
||||||
|
title="确定要删除吗?" |
||||||
|
ok-text="确定" |
||||||
|
cancel-text="取消" |
||||||
|
placement="rightTop" |
||||||
|
@confirm="onDelete(dataRef)" |
||||||
|
@visibleChange="onVisibleChange" |
||||||
|
> |
||||||
|
<span>{{ title }}</span> |
||||||
|
</Popconfirm> |
||||||
|
|
||||||
|
<template #overlay> |
||||||
|
<a-menu @click=""> |
||||||
|
<a-menu-item key="1" @click="onAddChildDepart(dataRef)">添加子级</a-menu-item> |
||||||
|
<a-menu-item key="2" @click="visibleTreeKey = treeKey"> |
||||||
|
<span style="color: red">删除</span> |
||||||
|
</a-menu-item> |
||||||
|
</a-menu> |
||||||
|
</template> |
||||||
|
</a-dropdown> |
||||||
|
</template> |
||||||
|
</a-tree> |
||||||
|
</template> |
||||||
|
<a-empty v-else description="暂无数据" /> |
||||||
|
</a-spin> |
||||||
|
<DepartFormModal :rootTreeData="treeData" @register="registerModal" @success="loadRootTreeData" /> |
||||||
|
</a-card> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script lang="ts" setup> |
||||||
|
import { inject, nextTick, ref, unref, defineExpose } from 'vue'; |
||||||
|
import { useModal } from '/@/components/Modal'; |
||||||
|
import { useMessage } from '/@/hooks/web/useMessage'; |
||||||
|
import { useMethods } from '/@/hooks/system/useMethods'; |
||||||
|
import { Api, deleteBatchDepart, queryDepartTreeSync } from '../depart.api'; |
||||||
|
import { searchByKeywords } from '/@/views/system/departUser/depart.user.api'; |
||||||
|
import DepartFormModal from '/@/views/system/depart/components/DepartFormModal.vue'; |
||||||
|
import { Popconfirm } from 'ant-design-vue'; |
||||||
|
|
||||||
|
const prefixCls = inject('prefixCls'); |
||||||
|
const emit = defineEmits(['select', 'rootTreeData']); |
||||||
|
const { createMessage } = useMessage(); |
||||||
|
const { handleImportXls, handleExportXls } = useMethods(); |
||||||
|
|
||||||
|
const loading = ref<boolean>(false); |
||||||
|
// 部门树列表数据 |
||||||
|
const treeData = ref<any[]>([]); |
||||||
|
// 当前选中的项 |
||||||
|
const checkedKeys = ref<any[]>([]); |
||||||
|
// 当前展开的项 |
||||||
|
const expandedKeys = ref<any[]>([]); |
||||||
|
// 当前选中的项 |
||||||
|
const selectedKeys = ref<any[]>([]); |
||||||
|
// 树组件重新加载 |
||||||
|
const treeReloading = ref<boolean>(false); |
||||||
|
// 树父子是否关联 |
||||||
|
const checkStrictly = ref<boolean>(true); |
||||||
|
// 当前选中的部门 |
||||||
|
const currentDepart = ref<any>(null); |
||||||
|
// 控制确认删除提示框是否显示 |
||||||
|
const visibleTreeKey = ref<any>(null); |
||||||
|
// 搜索关键字 |
||||||
|
const searchKeyword = ref(''); |
||||||
|
|
||||||
|
// 注册 modal |
||||||
|
const [registerModal, { openModal }] = useModal(); |
||||||
|
|
||||||
|
// 加载顶级部门信息 |
||||||
|
async function loadRootTreeData() { |
||||||
|
try { |
||||||
|
loading.value = true; |
||||||
|
treeData.value = []; |
||||||
|
const result = await queryDepartTreeSync(); |
||||||
|
if (Array.isArray(result)) { |
||||||
|
treeData.value = result; |
||||||
|
} |
||||||
|
if (expandedKeys.value.length === 0) { |
||||||
|
autoExpandParentNode(); |
||||||
|
} else { |
||||||
|
if (selectedKeys.value.length === 0) { |
||||||
|
let item = treeData.value[0]; |
||||||
|
if (item) { |
||||||
|
// 默认选中第一个 |
||||||
|
setSelectedKey(item.id, item); |
||||||
|
} |
||||||
|
} else { |
||||||
|
emit('select', currentDepart.value); |
||||||
|
} |
||||||
|
} |
||||||
|
emit('rootTreeData', treeData.value); |
||||||
|
} finally { |
||||||
|
loading.value = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
loadRootTreeData(); |
||||||
|
|
||||||
|
// 加载子级部门信息 |
||||||
|
async function loadChildrenTreeData(treeNode) { |
||||||
|
try { |
||||||
|
const result = await queryDepartTreeSync({ |
||||||
|
pid: treeNode.dataRef.id, |
||||||
|
}); |
||||||
|
if (result.length == 0) { |
||||||
|
treeNode.dataRef.isLeaf = true; |
||||||
|
} else { |
||||||
|
treeNode.dataRef.children = result; |
||||||
|
if (expandedKeys.value.length > 0) { |
||||||
|
// 判断获取的子级是否有当前展开的项 |
||||||
|
let subKeys: any[] = []; |
||||||
|
for (let key of expandedKeys.value) { |
||||||
|
if (result.findIndex((item) => item.id === key) !== -1) { |
||||||
|
subKeys.push(key); |
||||||
|
} |
||||||
|
} |
||||||
|
if (subKeys.length > 0) { |
||||||
|
expandedKeys.value = [...expandedKeys.value]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
treeData.value = [...treeData.value]; |
||||||
|
emit('rootTreeData', treeData.value); |
||||||
|
} catch (e) { |
||||||
|
console.error(e); |
||||||
|
} |
||||||
|
return Promise.resolve(); |
||||||
|
} |
||||||
|
|
||||||
|
// 自动展开父节点,只展开一级 |
||||||
|
function autoExpandParentNode() { |
||||||
|
let item = treeData.value[0]; |
||||||
|
if (item) { |
||||||
|
if (!item.isLeaf) { |
||||||
|
expandedKeys.value = [item.key]; |
||||||
|
} |
||||||
|
// 默认选中第一个 |
||||||
|
setSelectedKey(item.id, item); |
||||||
|
reloadTree(); |
||||||
|
} else { |
||||||
|
emit('select', null); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 重新加载树组件,防止无法默认展开数据 |
||||||
|
async function reloadTree() { |
||||||
|
await nextTick(); |
||||||
|
treeReloading.value = true; |
||||||
|
await nextTick(); |
||||||
|
treeReloading.value = false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 设置当前选中的行 |
||||||
|
*/ |
||||||
|
function setSelectedKey(key: string, data?: object) { |
||||||
|
selectedKeys.value = [key]; |
||||||
|
if (data) { |
||||||
|
currentDepart.value = data; |
||||||
|
emit('select', data); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 添加一级部门 |
||||||
|
function onAddDepart() { |
||||||
|
openModal(true, { isUpdate: false, isChild: false }); |
||||||
|
} |
||||||
|
|
||||||
|
// 添加子级部门 |
||||||
|
function onAddChildDepart(data = currentDepart.value) { |
||||||
|
if (data == null) { |
||||||
|
createMessage.warning('请先选择一个部门'); |
||||||
|
return; |
||||||
|
} |
||||||
|
const record = { parentId: data.id }; |
||||||
|
openModal(true, { isUpdate: false, isChild: true, record }); |
||||||
|
} |
||||||
|
|
||||||
|
// 搜索事件 |
||||||
|
async function onSearch(value: string) { |
||||||
|
if (value) { |
||||||
|
try { |
||||||
|
loading.value = true; |
||||||
|
treeData.value = []; |
||||||
|
let result = await searchByKeywords({ keyWord: value }); |
||||||
|
if (Array.isArray(result)) { |
||||||
|
treeData.value = result; |
||||||
|
} |
||||||
|
autoExpandParentNode(); |
||||||
|
} finally { |
||||||
|
loading.value = false; |
||||||
|
} |
||||||
|
} else { |
||||||
|
loadRootTreeData(); |
||||||
|
} |
||||||
|
searchKeyword.value = value; |
||||||
|
} |
||||||
|
|
||||||
|
// 树复选框选择事件 |
||||||
|
function onCheck(e) { |
||||||
|
if (Array.isArray(e)) { |
||||||
|
checkedKeys.value = e; |
||||||
|
} else { |
||||||
|
checkedKeys.value = e.checked; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 树选择事件 |
||||||
|
function onSelect(selKeys, event) { |
||||||
|
console.log('select: ', selKeys, event); |
||||||
|
if (selKeys.length > 0 && selectedKeys.value[0] !== selKeys[0]) { |
||||||
|
setSelectedKey(selKeys[0], event.selectedNodes[0]); |
||||||
|
} else { |
||||||
|
// 这样可以防止用户取消选择 |
||||||
|
setSelectedKey(selectedKeys.value[0]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 根据 ids 删除部门 |
||||||
|
* @param idListRef array |
||||||
|
* @param confirm 是否显示确认提示框 |
||||||
|
*/ |
||||||
|
async function doDeleteDepart(idListRef, confirm = true) { |
||||||
|
const idList = unref(idListRef); |
||||||
|
if (idList.length > 0) { |
||||||
|
try { |
||||||
|
loading.value = true; |
||||||
|
await deleteBatchDepart({ ids: idList.join(',') }, confirm); |
||||||
|
await loadRootTreeData(); |
||||||
|
} finally { |
||||||
|
loading.value = false; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 删除单个部门 |
||||||
|
async function onDelete(data) { |
||||||
|
if (data) { |
||||||
|
onVisibleChange(false); |
||||||
|
doDeleteDepart([data.id], false); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 批量删除部门 |
||||||
|
async function onDeleteBatch() { |
||||||
|
try { |
||||||
|
await doDeleteDepart(checkedKeys); |
||||||
|
checkedKeys.value = []; |
||||||
|
} finally { |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function onVisibleChange(visible) { |
||||||
|
if (!visible) { |
||||||
|
visibleTreeKey.value = null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function onImportXls(d) { |
||||||
|
handleImportXls(d, Api.importExcelUrl, () => { |
||||||
|
loadRootTreeData(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function onExportXls() { |
||||||
|
handleExportXls('部门信息', Api.exportXlsUrl); |
||||||
|
} |
||||||
|
|
||||||
|
defineExpose({ |
||||||
|
loadRootTreeData, |
||||||
|
}); |
||||||
|
</script> |
@ -0,0 +1,170 @@ |
|||||||
|
<template> |
||||||
|
<a-spin :spinning="loading"> |
||||||
|
<template v-if="treeData.length > 0"> |
||||||
|
<BasicTree |
||||||
|
ref="basicTree" |
||||||
|
class="depart-rule-tree" |
||||||
|
checkable |
||||||
|
:treeData="treeData" |
||||||
|
:checkedKeys="checkedKeys" |
||||||
|
:selectedKeys="selectedKeys" |
||||||
|
:expandedKeys="expandedKeys" |
||||||
|
:checkStrictly="checkStrictly" |
||||||
|
style="height: 500px; overflow: auto" |
||||||
|
@check="onCheck" |
||||||
|
@expand="onExpand" |
||||||
|
@select="onSelect" |
||||||
|
> |
||||||
|
<template #title="{ slotTitle, ruleFlag }"> |
||||||
|
<span>{{ slotTitle }}</span> |
||||||
|
<Icon v-if="ruleFlag" icon="ant-design:align-left-outlined" style="margin-left: 5px; color: red" /> |
||||||
|
</template> |
||||||
|
</BasicTree> |
||||||
|
</template> |
||||||
|
<a-empty v-else description="无可配置部门权限" /> |
||||||
|
|
||||||
|
<div class="j-box-bottom-button offset-20" style="margin-top: 30px"> |
||||||
|
<div class="j-box-bottom-button-float" :class="[`${prefixCls}`]"> |
||||||
|
<a-dropdown :trigger="['click']" placement="top"> |
||||||
|
<template #overlay> |
||||||
|
<a-menu> |
||||||
|
<a-menu-item key="3" @click="toggleCheckALL(true)">全部勾选</a-menu-item> |
||||||
|
<a-menu-item key="4" @click="toggleCheckALL(false)">取消全选</a-menu-item> |
||||||
|
<a-menu-item key="5" @click="toggleExpandAll(true)">展开所有</a-menu-item> |
||||||
|
<a-menu-item key="6" @click="toggleExpandAll(false)">收起所有</a-menu-item> |
||||||
|
</a-menu> |
||||||
|
</template> |
||||||
|
<a-button style="float: left"> |
||||||
|
树操作 |
||||||
|
<Icon icon="ant-design:up-outlined" /> |
||||||
|
</a-button> |
||||||
|
</a-dropdown> |
||||||
|
<a-button type="primary" preIcon="ant-design:save-filled" @click="onSubmit">保存</a-button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</a-spin> |
||||||
|
<DepartDataRuleDrawer @register="registerDataRuleDrawer" /> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script lang="ts" setup> |
||||||
|
import { watch, computed, inject, ref, nextTick } from 'vue'; |
||||||
|
import { useDrawer } from '/@/components/Drawer'; |
||||||
|
import { BasicTree } from '/@/components/Tree/index'; |
||||||
|
import DepartDataRuleDrawer from './DepartDataRuleDrawer.vue'; |
||||||
|
import { queryRoleTreeList, queryDepartPermission, saveDepartPermission } from '../depart.api'; |
||||||
|
import { useDesign } from '/@/hooks/web/useDesign'; |
||||||
|
|
||||||
|
const { prefixCls } = useDesign('j-depart-form-content'); |
||||||
|
const props = defineProps({ |
||||||
|
data: { type: Object, default: () => ({}) }, |
||||||
|
}); |
||||||
|
// 当前选中的部门ID,可能会为空,代表未选择部门 |
||||||
|
const departId = computed(() => props.data?.id); |
||||||
|
|
||||||
|
const basicTree = ref(); |
||||||
|
const loading = ref<boolean>(false); |
||||||
|
const treeData = ref<any[]>([]); |
||||||
|
const expandedKeys = ref<Array<any>>([]); |
||||||
|
const selectedKeys = ref<Array<any>>([]); |
||||||
|
const checkedKeys = ref<Array<any>>([]); |
||||||
|
const lastCheckedKeys = ref<Array<any>>([]); |
||||||
|
const checkStrictly = ref(true); |
||||||
|
|
||||||
|
// 注册数据规则授权弹窗抽屉 |
||||||
|
const [registerDataRuleDrawer, dataRuleDrawer] = useDrawer(); |
||||||
|
|
||||||
|
// onCreated |
||||||
|
loadData(); |
||||||
|
watch(departId, () => loadDepartPermission(), { immediate: true }); |
||||||
|
|
||||||
|
async function loadData() { |
||||||
|
try { |
||||||
|
loading.value = true; |
||||||
|
let { treeList } = await queryRoleTreeList(); |
||||||
|
treeData.value = treeList; |
||||||
|
await nextTick(); |
||||||
|
toggleExpandAll(true); |
||||||
|
} finally { |
||||||
|
loading.value = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async function loadDepartPermission() { |
||||||
|
if (departId.value) { |
||||||
|
try { |
||||||
|
loading.value = true; |
||||||
|
let keys = await queryDepartPermission({ departId: departId.value }); |
||||||
|
checkedKeys.value = keys; |
||||||
|
lastCheckedKeys.value = [...keys]; |
||||||
|
} finally { |
||||||
|
loading.value = false; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async function onSubmit() { |
||||||
|
try { |
||||||
|
loading.value = true; |
||||||
|
await saveDepartPermission({ |
||||||
|
departId: departId.value, |
||||||
|
permissionIds: checkedKeys.value.join(','), |
||||||
|
lastpermissionIds: lastCheckedKeys.value.join(','), |
||||||
|
}); |
||||||
|
await loadData(); |
||||||
|
await loadDepartPermission(); |
||||||
|
} finally { |
||||||
|
loading.value = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// tree勾选复选框事件 |
||||||
|
function onCheck(event) { |
||||||
|
if (!Array.isArray(event)) { |
||||||
|
checkedKeys.value = event.checked; |
||||||
|
} else { |
||||||
|
checkedKeys.value = event; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// tree展开事件 |
||||||
|
function onExpand($expandedKeys) { |
||||||
|
expandedKeys.value = $expandedKeys; |
||||||
|
} |
||||||
|
|
||||||
|
// tree选中事件 |
||||||
|
function onSelect($selectedKeys, { selectedNodes }) { |
||||||
|
if (selectedNodes[0]?.ruleFlag) { |
||||||
|
let functionId = $selectedKeys[0]; |
||||||
|
dataRuleDrawer.openDrawer(true, { departId, functionId }); |
||||||
|
} |
||||||
|
selectedKeys.value = []; |
||||||
|
} |
||||||
|
|
||||||
|
// 切换父子关联 |
||||||
|
async function toggleCheckStrictly(flag) { |
||||||
|
checkStrictly.value = flag; |
||||||
|
await nextTick(); |
||||||
|
checkedKeys.value = basicTree.value.getCheckedKeys(); |
||||||
|
} |
||||||
|
|
||||||
|
// 切换展开收起 |
||||||
|
async function toggleExpandAll(flag) { |
||||||
|
basicTree.value.expandAll(flag); |
||||||
|
await nextTick(); |
||||||
|
expandedKeys.value = basicTree.value.getExpandedKeys(); |
||||||
|
} |
||||||
|
|
||||||
|
// 切换全选 |
||||||
|
async function toggleCheckALL(flag) { |
||||||
|
basicTree.value.checkAll(flag); |
||||||
|
await nextTick(); |
||||||
|
checkedKeys.value = basicTree.value.getCheckedKeys(); |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style lang="less" scoped> |
||||||
|
// 【VUEN-188】解决滚动条不灵敏的问题 |
||||||
|
.depart-rule-tree :deep(.scrollbar__bar) { |
||||||
|
pointer-events: none; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,129 @@ |
|||||||
|
import { unref } from 'vue'; |
||||||
|
import { defHttp } from '/@/utils/http/axios'; |
||||||
|
import { useMessage } from '/@/hooks/web/useMessage'; |
||||||
|
|
||||||
|
const { createConfirm } = useMessage(); |
||||||
|
|
||||||
|
export enum Api { |
||||||
|
queryDepartTreeSync = '/sys/sysDepart/queryDepartTreeSync', |
||||||
|
queryDepartSync = '/sys/sysDepart/queryDepartSync', |
||||||
|
save = '/sys/sysDepart/add', |
||||||
|
edit = '/sys/sysDepart/edit', |
||||||
|
delete = '/sys/sysDepart/delete', |
||||||
|
deleteBatch = '/sys/sysDepart/deleteBatch', |
||||||
|
exportXlsUrl = '/sys/sysDepart/exportXls', |
||||||
|
importExcelUrl = '/sys/sysDepart/importExcel', |
||||||
|
|
||||||
|
roleQueryTreeList = '/sys/role/queryTreeList', |
||||||
|
queryDepartPermission = '/sys/permission/queryDepartPermission', |
||||||
|
saveDepartPermission = '/sys/permission/saveDepartPermission', |
||||||
|
|
||||||
|
dataRule = '/sys/sysDepartPermission/datarule', |
||||||
|
|
||||||
|
getCurrentUserDeparts = '/sys/user/getCurrentUserDeparts', |
||||||
|
selectDepart = '/sys/selectDepart', |
||||||
|
getUpdateDepartInfo = '/sys/user/getUpdateDepartInfo', |
||||||
|
doUpdateDepartInfo = '/sys/user/doUpdateDepartInfo', |
||||||
|
changeDepartChargePerson = '/sys/user/changeDepartChargePerson', |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取部门树列表 |
||||||
|
*/ |
||||||
|
export const queryDepartTreeSync = (params?) => defHttp.get({ url: Api.queryDepartTreeSync, params }); |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取部门树列表 |
||||||
|
*/ |
||||||
|
export const queryDepartSync = (params?) => defHttp.get({ url: Api.queryDepartSync, params }); |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 保存或者更新部门角色 |
||||||
|
*/ |
||||||
|
export const saveOrUpdateDepart = (params, isUpdate) => { |
||||||
|
if (isUpdate) { |
||||||
|
return defHttp.put({ url: Api.edit, params }); |
||||||
|
} else { |
||||||
|
return defHttp.post({ url: Api.save, params }); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* 批量删除部门角色 |
||||||
|
*/ |
||||||
|
export const deleteBatchDepart = (params, confirm = false) => { |
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
const doDelete = () => { |
||||||
|
resolve(defHttp.delete({ url: Api.deleteBatch, params }, { joinParamsToUrl: true })); |
||||||
|
}; |
||||||
|
if (confirm) { |
||||||
|
createConfirm({ |
||||||
|
iconType: 'warning', |
||||||
|
title: '删除', |
||||||
|
content: '确定要删除吗?', |
||||||
|
onOk: () => doDelete(), |
||||||
|
onCancel: () => reject(), |
||||||
|
}); |
||||||
|
} else { |
||||||
|
doDelete(); |
||||||
|
} |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取权限树列表 |
||||||
|
*/ |
||||||
|
export const queryRoleTreeList = (params?) => defHttp.get({ url: Api.roleQueryTreeList, params }); |
||||||
|
/** |
||||||
|
* 查询部门权限 |
||||||
|
*/ |
||||||
|
export const queryDepartPermission = (params?) => defHttp.get({ url: Api.queryDepartPermission, params }); |
||||||
|
/** |
||||||
|
* 保存部门权限 |
||||||
|
*/ |
||||||
|
export const saveDepartPermission = (params) => defHttp.post({ url: Api.saveDepartPermission, params }); |
||||||
|
|
||||||
|
/** |
||||||
|
* 查询部门数据权限列表 |
||||||
|
*/ |
||||||
|
export const queryDepartDataRule = (functionId, departId, params?) => { |
||||||
|
let url = `${Api.dataRule}/${unref(functionId)}/${unref(departId)}`; |
||||||
|
return defHttp.get({ url, params }); |
||||||
|
}; |
||||||
|
/** |
||||||
|
* 保存部门数据权限 |
||||||
|
*/ |
||||||
|
export const saveDepartDataRule = (params) => defHttp.post({ url: Api.dataRule, params }); |
||||||
|
/** |
||||||
|
* 获取登录用户部门信息 |
||||||
|
*/ |
||||||
|
export const getUserDeparts = (params?) => defHttp.get({ url: Api.getCurrentUserDeparts, params }); |
||||||
|
/** |
||||||
|
* 切换选择部门 |
||||||
|
*/ |
||||||
|
export const selectDepart = (params?) => defHttp.put({ url: Api.selectDepart, params }); |
||||||
|
|
||||||
|
/** |
||||||
|
* 编辑部门前获取部门相关信息 |
||||||
|
* @param id |
||||||
|
*/ |
||||||
|
export const getUpdateDepartInfo = (id) => defHttp.get({ url: Api.getUpdateDepartInfo, params: {id} }); |
||||||
|
|
||||||
|
/** |
||||||
|
* 编辑部门 |
||||||
|
* @param params |
||||||
|
*/ |
||||||
|
export const doUpdateDepartInfo = (params) => defHttp.put({ url: Api.doUpdateDepartInfo, params }); |
||||||
|
|
||||||
|
/** |
||||||
|
* 删除部门 |
||||||
|
* @param id |
||||||
|
*/ |
||||||
|
export const deleteDepart = (id) => defHttp.delete({ url: Api.delete, params:{ id } }, { joinParamsToUrl: true }); |
||||||
|
|
||||||
|
/** |
||||||
|
* 设置负责人 取消负责人 |
||||||
|
* @param params |
||||||
|
*/ |
||||||
|
export const changeDepartChargePerson = (params) => defHttp.put({ url: Api.changeDepartChargePerson, params }); |
@ -0,0 +1,83 @@ |
|||||||
|
import { FormSchema } from '/@/components/Form'; |
||||||
|
|
||||||
|
// 部门基础表单
|
||||||
|
export function useBasicFormSchema() { |
||||||
|
const basicFormSchema: FormSchema[] = [ |
||||||
|
{ |
||||||
|
field: 'departName', |
||||||
|
label: '部门名称', |
||||||
|
component: 'Input', |
||||||
|
componentProps: { |
||||||
|
placeholder: '部门名称', |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
field: 'parentId', |
||||||
|
label: '上级部门', |
||||||
|
component: 'TreeSelect', |
||||||
|
componentProps: { |
||||||
|
treeData: [], |
||||||
|
placeholder: '无', |
||||||
|
dropdownStyle: { maxHeight: '200px', overflow: 'auto' }, |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
field: 'orgCode', |
||||||
|
label: '部门编码', |
||||||
|
component: 'Input', |
||||||
|
componentProps: { |
||||||
|
placeholder: '请输入部门编码', |
||||||
|
}, |
||||||
|
}, |
||||||
|
/* { |
||||||
|
field: 'orgCategory', |
||||||
|
label: '部门类型', |
||||||
|
component: 'RadioButtonGroup', |
||||||
|
componentProps: { options: [] }, |
||||||
|
},*/ |
||||||
|
{ |
||||||
|
field: 'mobile', |
||||||
|
label: '电话', |
||||||
|
component: 'Input', |
||||||
|
componentProps: { |
||||||
|
placeholder: '请输入电话', |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
field: 'fax', |
||||||
|
label: '传真', |
||||||
|
component: 'Input', |
||||||
|
componentProps: { |
||||||
|
placeholder: '请输入传真', |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
field: 'address', |
||||||
|
label: '地址', |
||||||
|
component: 'Input', |
||||||
|
componentProps: { |
||||||
|
placeholder: '请输入地址', |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
field: 'memo', |
||||||
|
label: '备注', |
||||||
|
component: 'InputTextArea', |
||||||
|
componentProps: { |
||||||
|
placeholder: '请输入备注', |
||||||
|
}, |
||||||
|
}, |
||||||
|
]; |
||||||
|
return { basicFormSchema }; |
||||||
|
} |
||||||
|
|
||||||
|
// 部门类型选项
|
||||||
|
export const orgCategoryOptions = { |
||||||
|
// 一级部门
|
||||||
|
root: [{ value: '1', label: '公司' }], |
||||||
|
// 子级部门
|
||||||
|
child: [ |
||||||
|
{ value: '2', label: '部门' }, |
||||||
|
{ value: '3', label: '岗位' }, |
||||||
|
], |
||||||
|
}; |
@ -0,0 +1,14 @@ |
|||||||
|
//noinspection LessUnresolvedVariable |
||||||
|
@prefix-cls: ~'@{namespace}-depart-manage'; |
||||||
|
|
||||||
|
.@{prefix-cls} { |
||||||
|
// update-begin-author:liusq date:20230625 for: [issues/563]暗色主题部分失效 |
||||||
|
background: @component-background; |
||||||
|
// update-end-author:liusq date:20230625 for: [issues/563]暗色主题部分失效 |
||||||
|
|
||||||
|
&--box { |
||||||
|
.ant-tabs-nav { |
||||||
|
padding: 0 20px; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
<template> |
||||||
|
<a-row :class="['p-4', `${prefixCls}--box`]" type="flex" :gutter="10"> |
||||||
|
<a-col :xl="12" :lg="24" :md="24" style="margin-bottom: 10px"> |
||||||
|
<div style="height: 100%;" :class="[`${prefixCls}`]"> |
||||||
|
<a-tabs v-show="departData != null" defaultActiveKey="base-info"> |
||||||
|
<a-tab-pane tab="基本信息" key="base-info" forceRender style="position: relative"> |
||||||
|
<div style="padding: 20px"> |
||||||
|
<DepartFormTab :data="departData" :rootTreeData="rootTreeData" @success="onSuccess" /> |
||||||
|
</div> |
||||||
|
</a-tab-pane> |
||||||
|
</a-tabs> |
||||||
|
</div> |
||||||
|
</a-col> |
||||||
|
</a-row> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script lang="ts" setup name="system-depart"> |
||||||
|
import { provide, ref } from 'vue'; |
||||||
|
import { useDesign } from '/@/hooks/web/useDesign'; |
||||||
|
import DepartLeftTree from './components/DepartLeftTree.vue'; |
||||||
|
import DepartFormTab from './components/DepartFormTab.vue'; |
||||||
|
import DepartRuleTab from './components/DepartRuleTab.vue'; |
||||||
|
|
||||||
|
const { prefixCls } = useDesign('depart-manage'); |
||||||
|
provide('prefixCls', prefixCls); |
||||||
|
|
||||||
|
// 给子组件定义一个ref变量 |
||||||
|
const leftTree = ref(); |
||||||
|
|
||||||
|
// 当前选中的部门信息 |
||||||
|
const departData = ref({}); |
||||||
|
const rootTreeData = ref<any[]>([]); |
||||||
|
|
||||||
|
// 左侧树选择后触发 |
||||||
|
function onTreeSelect(data) { |
||||||
|
console.log('onTreeSelect: ', data); |
||||||
|
departData.value = data; |
||||||
|
} |
||||||
|
|
||||||
|
// 左侧树rootTreeData触发 |
||||||
|
function onRootTreeData(data) { |
||||||
|
rootTreeData.value = data; |
||||||
|
} |
||||||
|
|
||||||
|
function onSuccess() { |
||||||
|
leftTree.value.loadRootTreeData(); |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style lang="less"> |
||||||
|
@import './index.less'; |
||||||
|
</style> |
Loading…
Reference in new issue