Merge branch 'dev' of github.com:jetlinks/jetlinks-ui-vue into dev

This commit is contained in:
jackhoo_98 2023-01-18 10:21:04 +08:00
commit a98f02d881
24 changed files with 1119 additions and 48 deletions

View File

@ -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}`

16
src/api/system/role.ts Normal file
View File

@ -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);

View File

@ -25,7 +25,8 @@ const iconKeys = [
'ImportOutlined',
'ExportOutlined',
'SyncOutlined',
'ExclamationCircleOutlined'
'ExclamationCircleOutlined',
'UploadOutlined'
]
const Icon = (props: {type: string}) => {

View File

@ -228,6 +228,10 @@ const handleClick = () => {
transform: skewX(-45deg);
}
}
:deep(.card-item-content-title) {
cursor: pointer;
}
}
.card-mask {

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -57,7 +57,7 @@
</div>
</div>
</div>
</template>
</template>
<script setup lang='ts' name='Search'>
import SearchItem from './Item.vue'

View File

@ -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'){

View File

@ -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>

View File

@ -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)
}
}

View File

@ -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',

View File

@ -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 = () => {

View File

@ -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>

View File

@ -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}
)

View File

@ -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>

View File

@ -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
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>