Merge branch 'dev' of github.com:jetlinks/jetlinks-ui-vue into dev
This commit is contained in:
commit
a98f02d881
|
@ -1,4 +1,5 @@
|
|||
import server from '@/utils/request'
|
||||
import { BASE_API_PATH } from '@/utils/variable'
|
||||
import { DeviceInstance } from '@/views/device/instance/typings'
|
||||
|
||||
/**
|
||||
|
@ -73,3 +74,28 @@ export const batchUndeployDevice = (data: string[]) => server.put(`/device-insta
|
|||
* @returns
|
||||
*/
|
||||
export const batchDeleteDevice = (data: string[]) => server.put(`/device-instance/batch/_delete`, data)
|
||||
|
||||
/**
|
||||
* 下载设备模板
|
||||
* @param productId 产品id
|
||||
* @param type 文件类型
|
||||
* @returns
|
||||
*/
|
||||
export const deviceTemplateDownload = (productId: string, type: string) => `${BASE_API_PATH}/device-instance/${productId}/template.${type}`
|
||||
|
||||
/**
|
||||
* 设备导入
|
||||
* @param productId 产品id
|
||||
* @param type 文件类型
|
||||
* @returns
|
||||
*/
|
||||
export const deviceImport = (productId: string, fileUrl: string, autoDeploy: boolean) => `${BASE_API_PATH}/device-instance/${productId}/import?fileUrl=${fileUrl}&autoDeploy=${autoDeploy}&:X_Access_Token=${LocalStore.get(TOKEN_KEY)}`
|
||||
|
||||
/**
|
||||
* 设备导出
|
||||
* @param productId 产品id
|
||||
* @param type 文件类型
|
||||
* @returns
|
||||
*/
|
||||
export const deviceExport = (productId: string, type: string) => `${BASE_API_PATH}/device-instance${!!productId ? '/' + productId : ''}/export.${type}`
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import server from '@/utils/request';
|
||||
|
||||
// 获取角色列表
|
||||
export const getRoleList_api = (data: any): Promise<any> => server.post(`/role/_query/`, data);
|
||||
// 删除角色
|
||||
export const delRole_api = (id: string): Promise<any> => server.remove(`/role/${id}`);
|
||||
// 保存角色
|
||||
export const saveRole_api = (data: any): Promise<any> => server.post(`/role`, data);
|
||||
// 获取角色对应的权限树
|
||||
export const getPrimissTree_api = (id: string): Promise<any> => server.get(`/menu/role/${id}/_grant/tree`);
|
||||
|
||||
|
||||
// 获取用户列表
|
||||
export const getUserByRole_api = (data: any): Promise<any> => server.post(`/user/_query/`, data);
|
||||
// 将用户与该角色进行绑定
|
||||
export const bindUser_api = (roleId:string, data: string[]): Promise<any> => server.post(`/role/${roleId}/users/_bind`, data);
|
|
@ -25,7 +25,8 @@ const iconKeys = [
|
|||
'ImportOutlined',
|
||||
'ExportOutlined',
|
||||
'SyncOutlined',
|
||||
'ExclamationCircleOutlined'
|
||||
'ExclamationCircleOutlined',
|
||||
'UploadOutlined'
|
||||
]
|
||||
|
||||
const Icon = (props: {type: string}) => {
|
||||
|
|
|
@ -228,6 +228,10 @@ const handleClick = () => {
|
|||
transform: skewX(-45deg);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.card-item-content-title) {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.card-mask {
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<template>
|
||||
<a-space align="end">
|
||||
<a-radio-group button-style="solid" v-model:value="modelValue.fileType" placeholder="请选择文件格式">
|
||||
<a-radio-button value="xlsx">xlsx</a-radio-button>
|
||||
<a-radio-button value="csv">csv</a-radio-button>
|
||||
</a-radio-group>
|
||||
<a-checkbox v-model:checked="modelValue.autoDeploy">自动启用</a-checkbox>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PropType } from 'vue'
|
||||
|
||||
type Props = {
|
||||
autoDeploy: boolean,
|
||||
fileType: 'xlsx' | 'csv'
|
||||
}
|
||||
type Emits = {
|
||||
(e: 'update:modelValue', data: Partial<Props>): void;
|
||||
};
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const props = defineProps({
|
||||
// 组件双向绑定的值
|
||||
modelValue: {
|
||||
type: Object as PropType<Props>,
|
||||
default: () => {
|
||||
return {
|
||||
fileType: 'xlsx',
|
||||
autoDeploy: false
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
</script>
|
|
@ -0,0 +1,70 @@
|
|||
<template>
|
||||
<a-upload
|
||||
v-model:file-list="fileList"
|
||||
name="avatar"
|
||||
list-type="picture-card"
|
||||
class="avatar-uploader"
|
||||
:show-upload-list="false"
|
||||
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
|
||||
:before-upload="beforeUpload"
|
||||
@change="handleChange"
|
||||
>
|
||||
<img v-if="imageUrl" :src="imageUrl" alt="avatar" />
|
||||
<div v-else>
|
||||
<loading-outlined v-if="loading"></loading-outlined>
|
||||
<plus-outlined v-else></plus-outlined>
|
||||
<div class="ant-upload-text">Upload</div>
|
||||
</div>
|
||||
</a-upload>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { message, UploadChangeParam, UploadProps } from 'ant-design-vue';
|
||||
|
||||
const handleChange = (info: UploadChangeParam) => {
|
||||
// if (info.file.status === 'uploading') {
|
||||
// loading.value = true;
|
||||
// return;
|
||||
// }
|
||||
// if (info.file.status === 'done') {
|
||||
// // Get this url from response in real world.
|
||||
// getBase64(info.file.originFileObj, (base64Url: string) => {
|
||||
// imageUrl.value = base64Url;
|
||||
// loading.value = false;
|
||||
// });
|
||||
// }
|
||||
// if (info.file.status === 'error') {
|
||||
// loading.value = false;
|
||||
// message.error('upload error');
|
||||
// }
|
||||
};
|
||||
|
||||
const beforeUpload = (file: UploadProps['fileList'][number]) => {
|
||||
// const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
|
||||
// if (!isJpgOrPng) {
|
||||
// message.error('You can only upload JPG file!');
|
||||
// }
|
||||
// const isLt2M = file.size / 1024 / 1024 < 2;
|
||||
// if (!isLt2M) {
|
||||
// message.error('Image must smaller than 2MB!');
|
||||
// }
|
||||
// return isJpgOrPng && isLt2M;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.avatar-uploader {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
padding: 8px;
|
||||
background-color: rgba(0,0,0,.06);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
:deep(.ant-upload.ant-upload-select-picture-card) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,116 @@
|
|||
<template>
|
||||
<a-space align="end">
|
||||
<a-upload
|
||||
v-model:fileList="modelValue.upload"
|
||||
name="file"
|
||||
:action="FILE_UPLOAD"
|
||||
:headers="{
|
||||
'X-Access-Token': LocalStore.get(TOKEN_KEY)
|
||||
}"
|
||||
accept=".xlsx,.csv"
|
||||
:maxCount="1"
|
||||
:showUploadList="false"
|
||||
@change="uploadChange"
|
||||
>
|
||||
<a-button>
|
||||
<template #icon><AIcon type="UploadOutlined" /></template>
|
||||
文件上传
|
||||
</a-button>
|
||||
</a-upload>
|
||||
<div style="margin-left: 20px">
|
||||
<a-space>
|
||||
<a @click="downFile('xlsx')">.xlsx</a>
|
||||
<a @click="downFile('csv')">.csv</a>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-space>
|
||||
<div style="margin-top: 20px" v-if="importLoading">
|
||||
<a-badge v-if="flag" status="processing" text="进行中" />
|
||||
<a-badge v-else status="success" text="已完成" />
|
||||
<span>总数量:{{count}}</span>
|
||||
<p style="color: red">{{errMessage}}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { FILE_UPLOAD } from '@/api/comm'
|
||||
import { TOKEN_KEY } from '@/utils/variable';
|
||||
import { LocalStore } from '@/utils/comm';
|
||||
import { downloadFile } from '@/utils/utils';
|
||||
import { deviceImport, deviceTemplateDownload } from '@/api/device/instance'
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill'
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
type Emits = {
|
||||
(e: 'update:modelValue', data: string[]): void;
|
||||
};
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const props = defineProps({
|
||||
// 组件双向绑定的值
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
product: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
file: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
fileType: 'xlsx',
|
||||
autoDeploy: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const importLoading = ref<boolean>(false)
|
||||
const flag = ref<boolean>(false)
|
||||
const count = ref<number>(0)
|
||||
const errMessage = ref<string>('')
|
||||
|
||||
const downFile = (type: string) => {
|
||||
downloadFile(deviceTemplateDownload(props.product, type));
|
||||
}
|
||||
|
||||
const submitData = async (fileUrl: string) => {
|
||||
if (!!fileUrl) {
|
||||
count.value = 0
|
||||
errMessage.value = ''
|
||||
flag.value = true
|
||||
const autoDeploy = !!props?.file?.autoDeploy || false;
|
||||
importLoading.value = true
|
||||
let dt = 0;
|
||||
const source = new EventSourcePolyfill(deviceImport(props.product, fileUrl, autoDeploy));
|
||||
source.onmessage = (e: any) => {
|
||||
const res = JSON.parse(e.data);
|
||||
if (res.success) {
|
||||
const temp = res.result.total;
|
||||
dt += temp;
|
||||
count.value = dt
|
||||
} else {
|
||||
errMessage.value = res.message || '失败'
|
||||
}
|
||||
};
|
||||
source.onerror = (e: { status: number; }) => {
|
||||
if (e.status === 403) errMessage.value = '暂无权限,请联系管理员'
|
||||
flag.value = false
|
||||
source.close();
|
||||
};
|
||||
source.onopen = () => {};
|
||||
} else {
|
||||
message.error('请先上传文件')
|
||||
}
|
||||
}
|
||||
|
||||
const uploadChange = async (info: Record<string, any>) => {
|
||||
if (info.file.status === 'done') {
|
||||
const resp: any = info.file.response || { result: '' };
|
||||
await submitData(resp?.result || '');
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
|
@ -174,8 +174,14 @@ const JTable = defineComponent<JTableProps>({
|
|||
loading.value = true
|
||||
if(props.request) {
|
||||
const resp = await props.request({
|
||||
pageIndex: 0,
|
||||
pageSize: 12,
|
||||
...props.defaultParams,
|
||||
..._params
|
||||
..._params,
|
||||
terms: [
|
||||
...(props.defaultParams?.terms || []),
|
||||
...(_params?.terms || [])
|
||||
]
|
||||
})
|
||||
if(resp.status === 200){
|
||||
if(props.type === 'PAGE'){
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-table rowKey="id" :rowSelection="rowSelection" :columns="[..._columns]" :dataSource="_dataSource" :pagination="false" :scroll="{ x: 1366 }">
|
||||
<a-table rowKey="id" :rowSelection="rowSelection" :columns="[..._columns]" :dataSource="_dataSource" :pagination="false">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<!-- <template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
|
|
|
@ -6,6 +6,9 @@ import TitleComponent from "./TitleComponent/index.vue";
|
|||
import Form from './Form';
|
||||
import CardBox from './CardBox/index.vue';
|
||||
import Search from './Search'
|
||||
import NormalUpload from './NormalUpload/index.vue'
|
||||
import FileFormat from './FileFormat/index.vue'
|
||||
import JUpload from './JUpload/index.vue'
|
||||
|
||||
export default {
|
||||
install(app: App) {
|
||||
|
@ -16,5 +19,8 @@ export default {
|
|||
.component('Form', Form)
|
||||
.component('CardBox', CardBox)
|
||||
.component('Search', Search)
|
||||
.component('NormalUpload', NormalUpload)
|
||||
.component('FileFormat', FileFormat)
|
||||
.component('JUpload', JUpload)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -101,7 +101,14 @@ export default [
|
|||
path:'/system/api',
|
||||
component: ()=>import('@/views/system/apiPage/index.vue')
|
||||
},
|
||||
|
||||
{
|
||||
path:'/system/Role',
|
||||
component: ()=>import('@/views/system/Role/index.vue')
|
||||
},
|
||||
{
|
||||
path:'/system/Role/detail/:id',
|
||||
component: ()=>import('@/views/system/Role/Detail/index.vue')
|
||||
},
|
||||
// 初始化
|
||||
{
|
||||
path: '/init-home',
|
||||
|
|
|
@ -29,6 +29,7 @@ import { queryNoPagingPost } from '@/api/device/product'
|
|||
import { downloadFile } from '@/utils/utils'
|
||||
import encodeQuery from '@/utils/encodeQuery'
|
||||
import { BASE_API_PATH } from '@/utils/variable'
|
||||
import { deviceExport } from '@/api/device/instance'
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
const props = defineProps({
|
||||
|
@ -58,15 +59,8 @@ watch(
|
|||
|
||||
const handleOk = () => {
|
||||
const params = encodeQuery(props.data);
|
||||
if(modelRef.product){
|
||||
downloadFile(
|
||||
`${BASE_API_PATH}/device/instance/${modelRef.product}/export.${modelRef.fileType}`,
|
||||
params
|
||||
);
|
||||
} else {
|
||||
downloadFile(`${BASE_API_PATH}/device/instance/export.${modelRef.fileType}`, params);
|
||||
}
|
||||
emit('close')
|
||||
downloadFile(deviceExport(modelRef.product || "", modelRef.fileType),params);
|
||||
emit('close')
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
|
|
|
@ -1,14 +1,67 @@
|
|||
<template>
|
||||
<a-modal :maskClosable="false" width="800px" :visible="true" title="导入" @ok="handleOk" @cancel="handleCancel">
|
||||
123
|
||||
<a-modal :maskClosable="false" width="800px" :visible="true" title="导入" @ok="handleCancel" @cancel="handleCancel">
|
||||
<div style="margin-top: 10px">
|
||||
<a-form :layout="'vertical'">
|
||||
<a-row>
|
||||
<a-col span="24">
|
||||
<a-form-item label="产品" required>
|
||||
<a-select showSearch v-model:value="modelRef.product" placeholder="请选择产品">
|
||||
<a-select-option :value="item.id" v-for="item in productList" :key="item.id" :title="item.name"></a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col span="24">
|
||||
<a-form-item label="文件格式" v-if="modelRef.product">
|
||||
<FileFormat v-model="modelRef.file" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col span="12">
|
||||
<a-form-item label="文件上传" v-if="modelRef.product">
|
||||
<NormalUpload :product="modelRef.product" v-model="modelRef.upload" :file="modelRef.file" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const handleOk = () => {
|
||||
import { queryNoPagingPost } from '@/api/device/product'
|
||||
import { Form } from 'ant-design-vue';
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: undefined
|
||||
}
|
||||
})
|
||||
const productList = ref<Record<string, any>[]>([])
|
||||
const useForm = Form.useForm;
|
||||
|
||||
const modelRef = reactive({
|
||||
product: undefined,
|
||||
upload: [],
|
||||
file: {
|
||||
fileType: 'xlsx',
|
||||
autoDeploy: false,
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
() => {
|
||||
queryNoPagingPost({paging: false}).then(resp => {
|
||||
if(resp.status === 200){
|
||||
productList.value = resp.result as Record<string, any>[]
|
||||
}
|
||||
})
|
||||
},
|
||||
{immediate: true, deep: true}
|
||||
)
|
||||
|
||||
}
|
||||
const handleCancel = () => {
|
||||
|
||||
emit('close')
|
||||
}
|
||||
</script>
|
|
@ -1,16 +1,16 @@
|
|||
<template>
|
||||
<a-modal :maskClosable="false" width="800px" :visible="true" title="当前进度" @ok="handleOk" @cancel="handleCancel">
|
||||
<a-modal :maskClosable="false" width="800px" :visible="true" title="当前进度" @ok="handleCancel" @cancel="handleCancel">
|
||||
<div>
|
||||
<a-badge v-if="flag" status="processing" text="进行中" />
|
||||
<a-badge v-else status="success" text="已完成" />
|
||||
</div>
|
||||
<p>总数量:{{count}}</p>
|
||||
<a></a>
|
||||
<a style="color: red">{{errMessage}}</a>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { downloadFile } from '@/utils/utils'
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill'
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
const props = defineProps({
|
||||
|
@ -29,22 +29,63 @@ const flag = ref<boolean>(false)
|
|||
const errMessage = ref<string>('')
|
||||
const isSource = ref<boolean>(false)
|
||||
const id = ref<string>('')
|
||||
|
||||
const handleOk = () => {
|
||||
emit('close')
|
||||
}
|
||||
const source = ref<Record<string, any>>({})
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
const getData = () => {
|
||||
|
||||
const getData = (api: string) => {
|
||||
let dt = 0
|
||||
const _source = new EventSourcePolyfill(api)
|
||||
source.value = _source
|
||||
_source.onmessage = (e: any) => {
|
||||
const res = JSON.parse(e.data);
|
||||
switch (props.type) {
|
||||
case 'active':
|
||||
if (res.success) {
|
||||
dt += res.total;
|
||||
count.value = dt
|
||||
} else {
|
||||
if (res.source) {
|
||||
const msg = `${res.source.name}: ${res.message}`;
|
||||
errMessage.value = msg
|
||||
id.value = res.source.id
|
||||
isSource.value = true
|
||||
} else {
|
||||
errMessage.value = res.message
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'sync':
|
||||
dt += res;
|
||||
count.value = dt
|
||||
break;
|
||||
case 'import':
|
||||
if (res.success) {
|
||||
const temp = res.result.total;
|
||||
dt += temp;
|
||||
count.value = dt
|
||||
} else {
|
||||
errMessage.value = res.message
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
_source.onerror = () => {
|
||||
flag.value = false
|
||||
_source.close();
|
||||
};
|
||||
_source.onopen = () => {};
|
||||
}
|
||||
|
||||
watch(() => props.api,
|
||||
() => {
|
||||
getData()
|
||||
(newValue) => {
|
||||
if(newValue) {
|
||||
getData(newValue)
|
||||
}
|
||||
},
|
||||
{deep: true, immediate: true}
|
||||
)
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
<template>
|
||||
<a-modal :maskClosable="false" width="650px" :visible="true" title="新增" @ok="handleCancel" @cancel="handleCancel">
|
||||
<div style="margin-top: 10px">
|
||||
<a-form :layout="'vertical'">
|
||||
<a-row type="flex">
|
||||
<a-col flex="180px">
|
||||
<a-form-item required>
|
||||
<JUpload />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col flex="auto">
|
||||
<a-form-item label="ID">
|
||||
<a-input v-model:value="modelRef.id" placeholder="请输入ID" />
|
||||
</a-form-item>
|
||||
<a-form-item label="名称" required>
|
||||
<a-input v-model:value="modelRef.name" placeholder="请输入名称" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="产品" required>
|
||||
<a-select showSearch v-model:value="modelRef.productId" placeholder="请选择产品">
|
||||
<a-select-option :value="item.id" v-for="item in productList" :key="item.id" :title="item.name"></a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="说明">
|
||||
<a-textarea v-model:value="modelRef.describe" placeholder="请输入说明" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { queryNoPagingPost } from '@/api/device/product'
|
||||
import { Form } from 'ant-design-vue';
|
||||
|
||||
const emit = defineEmits(['close', 'save'])
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: undefined
|
||||
}
|
||||
})
|
||||
const productList = ref<Record<string, any>[]>([])
|
||||
const useForm = Form.useForm;
|
||||
|
||||
const modelRef = reactive({
|
||||
productId: undefined,
|
||||
id: '',
|
||||
name: '',
|
||||
describe: '',
|
||||
photoUrl: ''
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
() => {
|
||||
queryNoPagingPost({paging: false}).then(resp => {
|
||||
if(resp.status === 200){
|
||||
productList.value = resp.result as Record<string, any>[]
|
||||
}
|
||||
})
|
||||
},
|
||||
{immediate: true, deep: true}
|
||||
)
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('close')
|
||||
}
|
||||
</script>
|
|
@ -73,7 +73,7 @@
|
|||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<h3 @click="handleView(slotProps.id)">{{ slotProps.name }}</h3>
|
||||
<h3 class="card-item-content-title" @click.stop="handleView(slotProps.id)">{{ slotProps.name }}</h3>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">设备类型</div>
|
||||
|
@ -151,6 +151,7 @@
|
|||
<Import v-if="importVisible" @close="importVisible = false" />
|
||||
<Export v-if="exportVisible" @close="exportVisible = false" :data="params" />
|
||||
<Process v-if="operationVisible" @close="operationVisible = false" :api="api" :type="type" />
|
||||
<Save v-if="visible" :data="current" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -161,13 +162,15 @@ import { message } from "ant-design-vue";
|
|||
import Import from './Import/index.vue'
|
||||
import Export from './Export/index.vue'
|
||||
import Process from './Process/index.vue'
|
||||
import Save from './Save/index.vue'
|
||||
import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
|
||||
|
||||
const instanceRef = ref<Record<string, any>>({});
|
||||
const params = ref<Record<string, any>>({pageIndex: 0, pageSize: 12})
|
||||
const params = ref<Record<string, any>>({})
|
||||
const _selectedRowKeys = ref<string[]>([])
|
||||
const importVisible = ref<boolean>(false)
|
||||
const exportVisible = ref<boolean>(false)
|
||||
const visible = ref<boolean>(false)
|
||||
const current = ref<Record<string, any>>({})
|
||||
const operationVisible = ref<boolean>(false)
|
||||
const api = ref<string>('')
|
||||
|
@ -220,9 +223,9 @@ const columns = [
|
|||
}
|
||||
]
|
||||
|
||||
const paramsFormat = (config: any, _terms: any, name?: string) => {
|
||||
const paramsFormat = (config: Record<string, any>, _terms: Record<string, any>, name?: string) => {
|
||||
if (config?.terms && Array.isArray(config.terms) && config?.terms.length > 0) {
|
||||
(config?.terms || []).map((item: any, index: number) => {
|
||||
(config?.terms || []).map((item: Record<string, any>, index: number) => {
|
||||
if (item?.type) {
|
||||
_terms[`${name ? `${name}.` : ''}terms[${index}].type`] = item.type;
|
||||
}
|
||||
|
@ -237,28 +240,33 @@ const paramsFormat = (config: any, _terms: any, name?: string) => {
|
|||
}
|
||||
}
|
||||
|
||||
const handleParams = (config: any) => {
|
||||
const _terms: any = {};
|
||||
const handleParams = (config: Record<string, any>) => {
|
||||
const _terms: Record<string, any> = {};
|
||||
paramsFormat(config, _terms);
|
||||
const url = new URLSearchParams();
|
||||
Object.keys(_terms).forEach((key) => {
|
||||
url.append(key, _terms[key]);
|
||||
});
|
||||
return url.toString();
|
||||
if(Object.keys(_terms._value).length && Object.keys(_terms).length) {
|
||||
const url = new URLSearchParams();
|
||||
Object.keys(_terms).forEach((key) => {
|
||||
url.append(key, _terms[key]);
|
||||
});
|
||||
return url.toString();
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增
|
||||
*/
|
||||
const handleAdd = () => {
|
||||
message.warn('新增')
|
||||
visible.value = true
|
||||
current.value = {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看
|
||||
*/
|
||||
const handleView = (dt: any) => {
|
||||
// message.warn('查看')
|
||||
const handleView = (id: string) => {
|
||||
message.warn(id + '暂未开发')
|
||||
}
|
||||
|
||||
const getActions = (data: Partial<Record<string, any>>, type: 'card' | 'table'): ActionsType[] => {
|
||||
|
@ -272,7 +280,7 @@ const getActions = (data: Partial<Record<string, any>>, type: 'card' | 'table'):
|
|||
},
|
||||
icon: 'EyeOutlined',
|
||||
onClick: () => {
|
||||
handleView(data)
|
||||
handleView(data.id)
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -283,7 +291,8 @@ const getActions = (data: Partial<Record<string, any>>, type: 'card' | 'table'):
|
|||
},
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => {
|
||||
message.warn('edit')
|
||||
visible.value = true
|
||||
current.value = data
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -356,14 +365,14 @@ const handleClick = (dt: any) => {
|
|||
|
||||
const activeAllDevice = () => {
|
||||
type.value = 'active'
|
||||
const activeAPI = `/${BASE_API_PATH}/device-instance/deploy?:X_Access_Token=${LocalStore.get(TOKEN_KEY)}&${handleParams(params)}`;
|
||||
const activeAPI = `${BASE_API_PATH}/device-instance/deploy?:X_Access_Token=${LocalStore.get(TOKEN_KEY)}&${handleParams(params)}`;
|
||||
api.value = activeAPI
|
||||
operationVisible.value = true
|
||||
}
|
||||
|
||||
const syncDeviceStatus = () => {
|
||||
type.value = 'sync'
|
||||
const syncAPI = `/${BASE_API_PATH}/device-instance/state/_sync?:X_Access_Token=${LocalStore.get(TOKEN_KEY)}&${handleParams(params)}`;
|
||||
const syncAPI = `${BASE_API_PATH}/device-instance/state/_sync?:X_Access_Token=${LocalStore.get(TOKEN_KEY)}&${handleParams(params)}`;
|
||||
api.value = syncAPI
|
||||
operationVisible.value = true
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
<div class="role-permiss-container">
|
||||
<a-card>
|
||||
<h5>基本信息</h5>
|
||||
<a-form ref="formRef" :model="form.data" layout="vertical">
|
||||
<a-form-item
|
||||
name="name"
|
||||
label="名称"
|
||||
:rules="[{ required: true, message: '请输入名称' }]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="form.data.name"
|
||||
placeholder="请输入角色名称"
|
||||
allow-clear
|
||||
:maxlength="64"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item name="name" label="说明">
|
||||
<a-textarea
|
||||
v-model:value="form.data.description"
|
||||
placeholder="请输入说明"
|
||||
allow-clear
|
||||
:maxlength="200"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card>
|
||||
<h5>权限分配</h5>
|
||||
<PermissTree />
|
||||
|
||||
<a-button type="primary" :disabled="form.loading" @click="form.clickSave">保存</a-button>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="RolePermiss">
|
||||
import { FormInstance } from 'ant-design-vue';
|
||||
import PermissTree from '../components/PermissTree.vue'
|
||||
|
||||
// 表单相关
|
||||
const formRef = ref<FormInstance>();
|
||||
const form = reactive({
|
||||
loading: false,
|
||||
data: {
|
||||
name: '',
|
||||
description: '',
|
||||
},
|
||||
getForm: ()=>{},
|
||||
clickSave: ()=>{}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,116 @@
|
|||
<template>
|
||||
<a-card class="role-user-container">
|
||||
<Search :columns="query.columns" />
|
||||
|
||||
<JTable
|
||||
ref="tableRef"
|
||||
:columns="table.columns"
|
||||
:request="getUserByRole_api"
|
||||
model="TABLE"
|
||||
:params="query.params"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-button type="primary" @click="table.clickAdd"
|
||||
><plus-outlined />新增</a-button
|
||||
>
|
||||
</template>
|
||||
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16"> </a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
|
||||
<div class="dialogs">
|
||||
<AddUserDialog :open="dialog.openAdd" @refresh="table.refresh" />
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="RoleUser">
|
||||
import AddUserDialog from '../components/AddUserDialog.vue';
|
||||
import { getUserByRole_api } from '@/api/system/role';
|
||||
|
||||
const route = useRoute()
|
||||
const query = reactive({
|
||||
columns: [
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
dataIndex: 'username',
|
||||
key: 'username',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
},
|
||||
],
|
||||
params: {
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: 'id$in-dimension$role',
|
||||
value: route.params.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const tableRef = ref<Record<string, any>>({});
|
||||
const table = reactive({
|
||||
columns: [
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
dataIndex: 'username',
|
||||
key: 'username',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
scopedSlots: true,
|
||||
},
|
||||
],
|
||||
tableData: [],
|
||||
clickAdd: () => {
|
||||
dialog.openAdd += 1;
|
||||
},
|
||||
refresh: ()=>{
|
||||
tableRef.value.reload()
|
||||
}
|
||||
});
|
||||
|
||||
// 弹窗相关
|
||||
const dialog = reactive({
|
||||
openAdd: 0,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,123 @@
|
|||
<template>
|
||||
<a-modal
|
||||
v-model:visible="dialog.visible"
|
||||
title="新增"
|
||||
width="1000px"
|
||||
@ok="dialog.handleOk"
|
||||
>
|
||||
<Search :columns="query.columns" type="simple" />
|
||||
|
||||
<JTable
|
||||
ref="tableRef"
|
||||
:columns="table.columns"
|
||||
:request="getUserByRole_api"
|
||||
model="TABLE"
|
||||
:params="query.params"
|
||||
:rowSelection="{
|
||||
selectedRowKeys: table._selectedRowKeys,
|
||||
onChange: table.onSelectChange,
|
||||
}"
|
||||
@cancelSelect="table.cancelSelect"
|
||||
>
|
||||
</JTable>
|
||||
|
||||
<template #footer>
|
||||
<a-button key="back" @click="dialog.visible = false">取消</a-button>
|
||||
<a-button key="submit" type="primary" @click="dialog.handleOk"
|
||||
>确定</a-button
|
||||
>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getUserByRole_api, bindUser_api } from '@/api/system/role';
|
||||
import { message } from 'ant-design-vue';
|
||||
const route = useRoute();
|
||||
|
||||
const emits = defineEmits(['refresh']);
|
||||
const props = defineProps({
|
||||
open: Number,
|
||||
});
|
||||
|
||||
const query = reactive({
|
||||
columns: [
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
dataIndex: 'username',
|
||||
key: 'username',
|
||||
},
|
||||
],
|
||||
params: {
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: 'id$in-dimension$role$not',
|
||||
value: route.params.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const tableRef = ref<Record<string, any>>({});
|
||||
const table = reactive({
|
||||
_selectedRowKeys: [] as string[],
|
||||
columns: [
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
dataIndex: 'username',
|
||||
key: 'username',
|
||||
},
|
||||
],
|
||||
tableData: [],
|
||||
onSelectChange: (keys: string[]) => {
|
||||
table._selectedRowKeys = [...keys];
|
||||
},
|
||||
cancelSelect: () => {
|
||||
table._selectedRowKeys = [];
|
||||
},
|
||||
});
|
||||
|
||||
// 弹窗相关
|
||||
const dialog = reactive({
|
||||
visible: false,
|
||||
handleOk: () => {
|
||||
if (table._selectedRowKeys.length < 1) {
|
||||
message.error('请至少选择一项');
|
||||
} else {
|
||||
bindUser_api(
|
||||
route.params.id as string,
|
||||
table._selectedRowKeys,
|
||||
).then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功');
|
||||
emits('refresh');
|
||||
dialog.visible = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
() => {
|
||||
dialog.visible = true;
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,67 @@
|
|||
<template>
|
||||
<div class="permiss-tree-container">
|
||||
<a-table :data-source="dataSource">
|
||||
<a-table-column key="menu" data-index="menu">
|
||||
<template #title><span style="">菜单权限</span></template>
|
||||
</a-table-column>
|
||||
<a-table-column key="action" title="操作权限" data-index="action" />
|
||||
<a-table-column key="data" data-index="data">
|
||||
<template #title>
|
||||
<span style="">数据权限</span>
|
||||
<a-checkbox v-model:checked="checked">批量设置</a-checkbox>
|
||||
<a-select
|
||||
v-show="checked"
|
||||
v-model:value="selectValue"
|
||||
:size="'middle'"
|
||||
style="width: 200px"
|
||||
:options="options"
|
||||
></a-select>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getPrimissTree_api } from '@/api/system/role';
|
||||
|
||||
const route = useRoute();
|
||||
const props = defineProps({
|
||||
selectItems: Array,
|
||||
});
|
||||
const dataSource = ref([]);
|
||||
const checked = ref<boolean>(false);
|
||||
const options = [
|
||||
{
|
||||
label: '全部数据',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: '所在组织及下级组织',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
label: '所在组织',
|
||||
value: '3',
|
||||
},
|
||||
{
|
||||
label: '自己创建的',
|
||||
value: '4',
|
||||
},
|
||||
];
|
||||
const selectValue = ref<string>('');
|
||||
|
||||
const getAllPermiss = () => {
|
||||
const id = route.params.id as string;
|
||||
getPrimissTree_api(id).then((resp) => {
|
||||
console.log(resp);
|
||||
});
|
||||
};
|
||||
|
||||
getAllPermiss();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.permiss-tree-container {
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,19 @@
|
|||
<template>
|
||||
<div class="details-container">
|
||||
{{ route.params.id }}
|
||||
<a-tabs v-model:activeKey="activeKey">
|
||||
<a-tab-pane key="1" tab="权限分配"><Permiss /></a-tab-pane>
|
||||
<a-tab-pane key="2" tab="用户管理"><User /></a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="Detail">
|
||||
import Permiss from './Permiss/index.vue';
|
||||
import User from './User/index.vue';
|
||||
const route = useRoute();
|
||||
|
||||
const activeKey = ref('1');
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,91 @@
|
|||
<template>
|
||||
<a-modal
|
||||
v-model:visible="dialog.visible"
|
||||
title="新增"
|
||||
width="670px"
|
||||
@ok="dialog.handleOk"
|
||||
>
|
||||
<a-form ref="formRef" :model="form.data" layout="vertical">
|
||||
<a-form-item
|
||||
name="name"
|
||||
label="名称"
|
||||
:rules="[{ required: true, message: '请输入名称' }]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="form.data.name"
|
||||
placeholder="请输入角色名称"
|
||||
allow-clear
|
||||
:maxlength="64"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item name="name" label="说明">
|
||||
<a-textarea
|
||||
v-model:value="form.data.description"
|
||||
placeholder="请输入说明"
|
||||
allow-clear
|
||||
:maxlength="200"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<template #footer>
|
||||
<a-button key="back" @click="dialog.visible = false">取消</a-button>
|
||||
<a-button
|
||||
key="submit"
|
||||
type="primary"
|
||||
:loading="form.loading"
|
||||
@click="dialog.handleOk"
|
||||
>确定</a-button
|
||||
>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { FormInstance, message } from 'ant-design-vue';
|
||||
import { saveRole_api } from '@/api/system/role';
|
||||
const router = useRouter()
|
||||
const props = defineProps({
|
||||
open: Number,
|
||||
});
|
||||
// 弹窗相关
|
||||
const dialog = reactive({
|
||||
visible: false,
|
||||
handleOk: () => {
|
||||
formRef.value
|
||||
?.validate()
|
||||
.then(() => saveRole_api(form.data))
|
||||
.then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功');
|
||||
dialog.visible = false;
|
||||
router.push(`/system/Role/detail/${resp.result.id}`)
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
// 表单相关
|
||||
const formRef = ref<FormInstance>();
|
||||
const form = reactive({
|
||||
loading: false,
|
||||
data: {
|
||||
name: '',
|
||||
description: '',
|
||||
},
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
() => {
|
||||
// 重置表单
|
||||
form.data = {
|
||||
name: '',
|
||||
description: '',
|
||||
};
|
||||
formRef.value?.resetFields();
|
||||
dialog.visible = true;
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,145 @@
|
|||
<template>
|
||||
<a-card class="role-container">
|
||||
<Search :columns="query.columns" />
|
||||
|
||||
<JTable
|
||||
ref="tableRef"
|
||||
:columns="table.columns"
|
||||
:request="getRoleList_api"
|
||||
model="TABLE"
|
||||
:params="query.params"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-button type="primary" @click="table.clickAdd"
|
||||
><plus-outlined />新增</a-button
|
||||
>
|
||||
</template>
|
||||
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip>
|
||||
<template #title>编辑</template>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
@click="table.clickEdit(slotProps)"
|
||||
>
|
||||
<edit-outlined />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-popconfirm
|
||||
title="确定要删除吗?"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
@confirm="table.clickDel(slotProps)"
|
||||
>
|
||||
<a-tooltip>
|
||||
<template #title>删除</template>
|
||||
<a-button style="padding: 0" type="link">
|
||||
<delete-outlined />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
|
||||
<div class="dialogs">
|
||||
<AddDialog :open="dialog.openAdd" />
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="Role">
|
||||
import {
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
PlusOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import AddDialog from './components/AddDialog.vue';
|
||||
import { getRoleList_api, delRole_api } from '@/api/system/role';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
const router = useRouter()
|
||||
// 筛选
|
||||
const query = reactive({
|
||||
columns: [
|
||||
{
|
||||
title: '标识',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
ellipsis: true,
|
||||
fixed: 'left',
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
key: 'description',
|
||||
ellipsis: true,
|
||||
dataIndex: 'description',
|
||||
filters: true,
|
||||
onFilter: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
width: 200,
|
||||
fixed: 'right',
|
||||
},
|
||||
],
|
||||
params: {},
|
||||
});
|
||||
// 表格
|
||||
const tableRef = ref<Record<string, any>>({});
|
||||
const table = reactive({
|
||||
columns: [
|
||||
{
|
||||
title: '标识',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
scopedSlots: true,
|
||||
},
|
||||
],
|
||||
tableData: [],
|
||||
clickAdd: () => {
|
||||
dialog.openAdd += 1;
|
||||
},
|
||||
clickDel: (row: any) => {
|
||||
delRole_api(row.id).then((resp:any)=>{
|
||||
if(resp.status === 200){
|
||||
tableRef.value?.reload()
|
||||
message.success('操作成功!')
|
||||
}
|
||||
})
|
||||
},
|
||||
clickEdit: (row: any) => {
|
||||
router.push(`/system/Role/detail/${row.id}`)
|
||||
},
|
||||
});
|
||||
// 弹窗相关
|
||||
const dialog = reactive({
|
||||
openAdd: 0,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
Loading…
Reference in New Issue