合并代码
This commit is contained in:
commit
9f94ab36e5
|
|
@ -3,7 +3,7 @@ const asyncRoutes = [
|
|||
path: '/system',
|
||||
component: 'Layout',
|
||||
redirect: '/system/menu',
|
||||
name: 'System',
|
||||
title: 'System',
|
||||
meta: {
|
||||
title: '系统管理'
|
||||
},
|
||||
|
|
@ -11,7 +11,7 @@ const asyncRoutes = [
|
|||
{
|
||||
path: 'menu',
|
||||
component: 'views/system/menu/index',
|
||||
name: 'SystemMenu',
|
||||
title: 'SystemMenu',
|
||||
meta: {
|
||||
title: '菜单管理'
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ const asyncRoutes = [
|
|||
{
|
||||
path: 'user',
|
||||
component: 'views/system/user/index',
|
||||
name: 'SystemUser',
|
||||
title: 'SystemUser',
|
||||
meta: {
|
||||
title: '用户管理'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,3 +14,10 @@ export const refreshToken = () => {
|
|||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
export function getMenu() {
|
||||
return request({
|
||||
url: '/index/getMenuList',
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ export default {
|
|||
|
||||
/* tableData-start */
|
||||
const tableData = ref([])
|
||||
const { getDataSourceRef, getRowKey, reload } = useDataSource(getProps, { getPaginationInfo, setPagination, tableData, setLoading }, emit)
|
||||
const { getDataSourceRef, getRowKey, reload, reFetch } = useDataSource(getProps, { getPaginationInfo, setPagination, tableData, setLoading }, emit)
|
||||
const isRequest = !!unref(getProps).request
|
||||
const getBindProps = computed(() => {
|
||||
return {
|
||||
|
|
@ -97,7 +97,8 @@ export default {
|
|||
pagination,
|
||||
updatePage,
|
||||
updatePageSize,
|
||||
reload
|
||||
reload,
|
||||
reFetch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export const tableProps = {
|
|||
// 每页数量字段名
|
||||
sizeField: 'limit',
|
||||
// 接口返回的数据字段名
|
||||
listField: 'list',
|
||||
listField: 'records',
|
||||
// 接口返回总页数字段名
|
||||
totalField: 'total',
|
||||
// 默认分页数量
|
||||
|
|
|
|||
|
|
@ -105,6 +105,19 @@ export function useDataSource(propsRef, { getPaginationInfo, setPagination, setL
|
|||
await fetch(opt)
|
||||
}
|
||||
|
||||
async function reFetch(opt) {
|
||||
console.log('opt', opt)
|
||||
const { paginationSetting } = unref(propsRef)
|
||||
const pageField = paginationSetting.pageField
|
||||
const sizeField = paginationSetting.sizeField
|
||||
const pageSize = paginationSetting.pageSize
|
||||
setPagination({
|
||||
[pageField]: 1,
|
||||
[sizeField]: pageSize
|
||||
})
|
||||
await fetch(opt)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
fetch()
|
||||
|
|
@ -117,6 +130,7 @@ export function useDataSource(propsRef, { getPaginationInfo, setPagination, setL
|
|||
getDataSource,
|
||||
setTableData,
|
||||
getRowKey,
|
||||
reload
|
||||
reload,
|
||||
reFetch
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,52 +1,6 @@
|
|||
<template />
|
||||
|
||||
<script setup>
|
||||
import { isNullOrUndef } from '@/utils/is'
|
||||
import { useDialog } from 'naive-ui'
|
||||
|
||||
const NDialog = useDialog()
|
||||
|
||||
class Dialog {
|
||||
success(title, option) {
|
||||
this.showDialog('success', { title, ...option })
|
||||
}
|
||||
|
||||
warning(title, option) {
|
||||
this.showDialog('warning', { title, ...option })
|
||||
}
|
||||
|
||||
error(title, option) {
|
||||
this.showDialog('error', { title, ...option })
|
||||
}
|
||||
|
||||
showDialog(type = 'success', option) {
|
||||
if (isNullOrUndef(option.title)) {
|
||||
// ! 没有title的情况
|
||||
option.showIcon = false
|
||||
}
|
||||
NDialog[type]({
|
||||
positiveText: 'OK',
|
||||
closable: false,
|
||||
...option
|
||||
})
|
||||
}
|
||||
|
||||
confirm(option = {}) {
|
||||
this.showDialog(option.type || 'error', {
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: option.confirm,
|
||||
onNegativeClick: option.cancel,
|
||||
onMaskClick: option.cancel,
|
||||
...option
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
window['$dialog'] = new Dialog()
|
||||
Object.freeze(window.$dialog)
|
||||
Object.defineProperty(window, '$dialog', {
|
||||
configurable: false,
|
||||
writable: false
|
||||
})
|
||||
window['$dialog'] = useDialog()
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
<template>
|
||||
<n-modal v-bind="getModalOptions" preset="dialog">
|
||||
<n-card>
|
||||
<slot name="Context" />
|
||||
</n-card>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { defineComponent, computed, unref } from 'vue'
|
||||
export default defineComponent({
|
||||
name: 'ModalModules',
|
||||
props: {
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
emits: {
|
||||
click: null, // click事件没有检验
|
||||
onClose: (value) => {
|
||||
return value
|
||||
}
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const getModalOptions = computed(() => {
|
||||
return unref(props.options)
|
||||
})
|
||||
console.log(getModalOptions)
|
||||
const handleClick = function() {
|
||||
emit('click')
|
||||
}
|
||||
const handleCancel = function() {
|
||||
emit('onClose', true)
|
||||
}
|
||||
return {
|
||||
getModalOptions,
|
||||
handleClick,
|
||||
handleCancel
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang='scss'>
|
||||
</style>
|
||||
|
|
@ -2,13 +2,25 @@
|
|||
<div>
|
||||
<n-form ref="formRef" v-bind="getFormOptions">
|
||||
<template v-for="(item, index) in getFormOptions.info" :key="`${index}-${item.label}`">
|
||||
<n-form-item v-if="!item.options" :label="item.label">
|
||||
<n-input v-model:value="getFormOptions.form[item.key]" :placeholder="item.placeholder" />
|
||||
<n-form-item v-if="['input'].includes(item.type) || !item.type" :label="item.label">
|
||||
<n-input v-model:value="getFormOptions.form[item.key]" v-bind="item.props" />
|
||||
</n-form-item>
|
||||
<n-form-item v-if="item.options" :label="item.label">
|
||||
<n-select v-model:value="getFormOptions.form[item.key]" :options="item.options" />
|
||||
<n-form-item v-if="['select'].includes(item.type) " :label="item.label">
|
||||
<n-select v-model:value="getFormOptions.form[item.key]" v-bind="item.props" />
|
||||
</n-form-item>
|
||||
<n-form-item v-if="['date'].includes(item.type) " :label="item.label">
|
||||
<n-date-picker v-model="getFormOptions.form[item.key]" v-bind="item.props" />
|
||||
</n-form-item>
|
||||
</template>
|
||||
<n-form-item class="form__button">
|
||||
<n-button @click="handleSearch">查询</n-button>
|
||||
<n-button @click="handleReset">重置</n-button>
|
||||
<n-button
|
||||
v-if="showButton"
|
||||
type="text"
|
||||
@click="showMoreItem"
|
||||
>{{ showItemNum === getFormOptions.info.length - 1 ? '收起' : '展开' }}</n-button>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -25,36 +37,49 @@ export default {
|
|||
default: () => []
|
||||
}
|
||||
},
|
||||
emits: ['search'],
|
||||
setup(props, { emit }) {
|
||||
// const formOption = reactive({
|
||||
// form: {
|
||||
|
||||
// }
|
||||
// })
|
||||
const getFormRef = computed(() => {
|
||||
const { info } = unref(props)
|
||||
const form = ref({})
|
||||
info.forEach((item) => {
|
||||
const showItemNum = ref(3)
|
||||
const len = ref(props.info.length - 1)
|
||||
const showButton = ref(!!(showItemNum.value < len.value))
|
||||
/* 获取传递的props */
|
||||
const data = reactive({
|
||||
info: toRaw(props.info)
|
||||
})
|
||||
const form = ref({})
|
||||
/* 初始化搜索表单信息 */
|
||||
function initForm() {
|
||||
data.info.forEach((item) => {
|
||||
form.value[item.key] = ''
|
||||
})
|
||||
return unref(form)
|
||||
})
|
||||
console.log(getFormRef)
|
||||
}
|
||||
|
||||
const getFormOptions = computed(() => {
|
||||
return {
|
||||
form: unref(getFormRef),
|
||||
form: unref(form),
|
||||
labelWidth: 'auto',
|
||||
labelPlacement: 'left',
|
||||
inline: true,
|
||||
info: [...unref(props).info]
|
||||
}
|
||||
})
|
||||
function getFormInfo() {
|
||||
console.log(getFormOptions)
|
||||
function handleSearch() {
|
||||
emit('search', getFormOptions.value.form)
|
||||
}
|
||||
function handleReset() {
|
||||
initForm()
|
||||
emit('search', getFormOptions.value.form)
|
||||
}
|
||||
function showMoreItem() {
|
||||
showItemNum.value = showItemNum.value === len.value ? 3 : len.value
|
||||
}
|
||||
return {
|
||||
showItemNum,
|
||||
showButton,
|
||||
getFormOptions,
|
||||
getFormInfo
|
||||
handleSearch,
|
||||
handleReset,
|
||||
showMoreItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -71,5 +96,11 @@ export default {
|
|||
.n-select{
|
||||
width: 200px;
|
||||
}
|
||||
transition: all 10s;
|
||||
}
|
||||
.form__button{
|
||||
button+button{
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<n-menu
|
||||
:mode="menuMode"
|
||||
:value="(currentRoute.meta && currentRoute.meta.activeMenu) || currentRoute.name"
|
||||
:value="(currentRoute.title && currentRoute.meta.activeMenu) || currentRoute.title"
|
||||
:options="getMenuOptions"
|
||||
@update:value="handleMenuSelect"
|
||||
/>
|
||||
|
|
@ -29,6 +29,8 @@ const getMenuOptions = computed(() => {
|
|||
return generateOptions(permissionStore.routes, '')
|
||||
})
|
||||
|
||||
console.log('getMenuOptions', getMenuOptions)
|
||||
|
||||
function resolvePath(basePath, path) {
|
||||
if (isExternal(path)) return path
|
||||
return (
|
||||
|
|
@ -43,10 +45,10 @@ function resolvePath(basePath, path) {
|
|||
function generateOptions(routes, basePath) {
|
||||
const options = []
|
||||
routes.forEach((route) => {
|
||||
if (route.name && !route.isHidden) {
|
||||
if (route.title && !route.isHidden) {
|
||||
const curOption = {
|
||||
label: (route.meta && route.meta.title) || route.name,
|
||||
key: route.name,
|
||||
label: (route.meta && route.meta.title) || route.title,
|
||||
key: route.title,
|
||||
path: resolvePath(basePath, route.path)
|
||||
}
|
||||
if (route.children && route.children.length) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import Home from '@/views/dashboard/index.vue'
|
|||
export const basicRoutes = [
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
title: 'Login',
|
||||
component: () => import('@/views/login/index.vue'),
|
||||
isHidden: true,
|
||||
meta: {
|
||||
|
|
@ -13,16 +13,16 @@ export const basicRoutes = [
|
|||
},
|
||||
{
|
||||
path: '/',
|
||||
name: 'Dashboard',
|
||||
title: '控制台',
|
||||
component: Layout,
|
||||
redirect: '/home',
|
||||
meta: {
|
||||
title: 'Dashboard'
|
||||
title: '控制台'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'home',
|
||||
name: 'Home',
|
||||
title: 'Home',
|
||||
component: Home,
|
||||
meta: {
|
||||
title: '首页',
|
||||
|
|
@ -34,7 +34,7 @@ export const basicRoutes = [
|
|||
]
|
||||
|
||||
export const NOT_FOUND_ROUTE = {
|
||||
name: 'NOT_FOUND',
|
||||
title: 'NOT_FOUND',
|
||||
path: '/:pathMatch(.*)*',
|
||||
redirect: '/404',
|
||||
isHidden: true
|
||||
|
|
@ -42,7 +42,7 @@ export const NOT_FOUND_ROUTE = {
|
|||
|
||||
export const REDIRECT_ROUTE = {
|
||||
path: '/redirect',
|
||||
name: 'Redirect',
|
||||
title: 'Redirect',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: 'Redirect',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { asyncRoutes, basicRoutes } from '@/router/routes'
|
||||
import { getMenu } from '@/api/system/menu'
|
||||
import { getMenu } from '@/api/auth/index'
|
||||
import Layout from '@/layout/index.vue'
|
||||
import modules from '@/utils/module.js'
|
||||
|
||||
|
|
@ -53,19 +53,36 @@ function dataArrayToRoutes(routes) {
|
|||
const res = []
|
||||
routes.forEach(item => {
|
||||
const tmp = { ...item }
|
||||
// 如果有component配置
|
||||
if (tmp.component) {
|
||||
// // 如果有component配置
|
||||
// if (tmp.component) {
|
||||
// // Layout引入
|
||||
// if (tmp.component === 'Layout') {
|
||||
// tmp.component = Layout
|
||||
// } else {
|
||||
// const sub_view = tmp.component.replace(/^\/*/g, '')
|
||||
// const component = `../${sub_view}.vue`
|
||||
// tmp.component = modules[component]
|
||||
// }
|
||||
// if (tmp.children) {
|
||||
// tmp.children = dataArrayToRoutes(tmp.children)
|
||||
// }
|
||||
// }
|
||||
// 如果pid为0
|
||||
if (tmp.pid === 0) {
|
||||
// Layout引入
|
||||
if (tmp.component === 'Layout') {
|
||||
tmp.component = Layout
|
||||
} else {
|
||||
const sub_view = tmp.component.replace(/^\/*/g, '')
|
||||
const component = `../${sub_view}.vue`
|
||||
tmp.component = modules[component]
|
||||
}
|
||||
tmp.component = Layout
|
||||
if (tmp.children) {
|
||||
tmp.children = dataArrayToRoutes(tmp.children)
|
||||
}
|
||||
} else {
|
||||
const sub_view = tmp.component.replace(/^\/*/g, '')
|
||||
const component = `../${sub_view}.vue`
|
||||
tmp.component = modules[component]
|
||||
}
|
||||
tmp.name = tmp.title
|
||||
tmp.meta = {
|
||||
...tmp.meta,
|
||||
title: tmp.title
|
||||
}
|
||||
res.push(tmp)
|
||||
})
|
||||
|
|
@ -97,6 +114,7 @@ export const usePermissionStore = defineStore('permission', {
|
|||
const res = await getMenu()
|
||||
if (res.code === 0) {
|
||||
const result = dataArrayToRoutes(res.data)
|
||||
console.log(result)
|
||||
this.accessRoutes = result
|
||||
return Promise.resolve(result)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
<template>
|
||||
<Modal :options="getModalOptions">
|
||||
<template #Context>
|
||||
111
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, computed } from 'vue'
|
||||
import Modal from '@/components/Modal/index.vue'
|
||||
export default defineComponent({
|
||||
name: 'MenuModal',
|
||||
components: { Modal },
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
row: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
emits: {
|
||||
'update:visible': null,
|
||||
onClose: null,
|
||||
done: null
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const getModalOptions = computed(() => {
|
||||
return {
|
||||
visible: props.visible
|
||||
}
|
||||
})
|
||||
|
||||
/* 关闭弹窗 */
|
||||
const handleClose = () => {
|
||||
// vodModal.value.handleUploadCancel()
|
||||
emit('update:visible', false)
|
||||
}
|
||||
|
||||
return {
|
||||
getModalOptions
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style scoped lang='scss'>
|
||||
</style>
|
||||
|
|
@ -3,24 +3,27 @@
|
|||
<n-card>
|
||||
<data-table :columns="data.columns" :pagination="false" :data="data.data" size="large">
|
||||
<template #tableTitle>
|
||||
<n-button type="primary">
|
||||
添加角色
|
||||
<n-button type="primary" @click="handlleRoleAdd">
|
||||
添加菜单
|
||||
</n-button>
|
||||
</template>
|
||||
</data-table>
|
||||
</n-card>
|
||||
</div>
|
||||
|
||||
<RoleModal :visible="data.modalShow" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dataTable from '@/components/DataTable/index.vue'
|
||||
import RoleModal from './components/RoleModal.vue'
|
||||
import Action from '@/components/DataTable/tools/Action.vue'
|
||||
import { getMenuList } from '@/api/system/index.js'
|
||||
import { h, onMounted } from 'vue'
|
||||
import { reactive } from 'vue'
|
||||
export default {
|
||||
name: 'MenuPage',
|
||||
components: { dataTable },
|
||||
components: { dataTable, RoleModal },
|
||||
setup() {
|
||||
const data = reactive({
|
||||
columns: [
|
||||
|
|
@ -118,7 +121,8 @@ export default {
|
|||
],
|
||||
data: [
|
||||
|
||||
]
|
||||
],
|
||||
modalShow: false
|
||||
})
|
||||
|
||||
function play(row) {
|
||||
|
|
@ -133,12 +137,15 @@ export default {
|
|||
const res = await getMenuList()
|
||||
data.data = res.data
|
||||
}
|
||||
function handlleRoleAdd() {
|
||||
data.modalShow = true
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchList()
|
||||
})
|
||||
|
||||
return { data }
|
||||
return { data, handlleRoleAdd }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<div>
|
||||
<n-card>
|
||||
|
||||
<headSearch :info="data.info" />
|
||||
<headSearch :info="data.info" @search="handleSearch" />
|
||||
|
||||
<data-table
|
||||
ref="tableRef"
|
||||
:columns="data.columns"
|
||||
:row-key="(row) => row.id"
|
||||
:request="loadDataTable"
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
import headSearch from '@/components/Search/index.vue'
|
||||
import dataTable from '@/components/DataTable/index.vue'
|
||||
import TableAction from '@/components/DataTable/tools/Action.vue'
|
||||
import { getUserList } from '@/api/system/index.js'
|
||||
import { getRoleList } from '@/api/system/role/index'
|
||||
import { h, ref, unref } from 'vue'
|
||||
import { reactive } from 'vue'
|
||||
import table from './table.js'
|
||||
|
|
@ -79,19 +79,27 @@ export default {
|
|||
console.log(row)
|
||||
}
|
||||
|
||||
const params = reactive({
|
||||
name: 'xiaoMa'
|
||||
})
|
||||
const params = ref({})
|
||||
|
||||
const tableRef = ref()
|
||||
|
||||
function handleSearch(data) {
|
||||
params.value = {
|
||||
...data
|
||||
}
|
||||
console.log(params.value)
|
||||
tableRef.value.reFetch({ ...unref(params) })
|
||||
}
|
||||
|
||||
const loadDataTable = async(res) => {
|
||||
const _params = {
|
||||
...unref(params),
|
||||
...res
|
||||
}
|
||||
return await getUserList(_params)
|
||||
return await getRoleList(_params)
|
||||
}
|
||||
|
||||
return { data, loadDataTable }
|
||||
return { data, tableRef, loadDataTable, handleSearch }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,19 +2,28 @@ const data = [
|
|||
{
|
||||
label: '角色名称',
|
||||
key: 'name',
|
||||
placeholder: '请输入角色名称'
|
||||
},
|
||||
{
|
||||
label: '角色备注',
|
||||
key: 'desc'
|
||||
},
|
||||
{
|
||||
label: '角色类型',
|
||||
key: 'op',
|
||||
options: [{
|
||||
label: 11, value: 1
|
||||
}]
|
||||
props: {
|
||||
placeholder: '请输入角色名称'
|
||||
}
|
||||
}
|
||||
// {
|
||||
// label: '角色类型',
|
||||
// type: 'select',
|
||||
// key: 'op',
|
||||
// props: {
|
||||
// options: [{
|
||||
// label: 11, value: 1
|
||||
// }]
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// label: '角色类型',
|
||||
// type: 'date',
|
||||
// key: 'date',
|
||||
// props: {
|
||||
// type: 'date'
|
||||
// }
|
||||
// }
|
||||
]
|
||||
|
||||
export default data
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<Modal :options="getModelOptions">
|
||||
<template #Context>
|
||||
添加用户
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Modal from '@/components/Modal/index.vue'
|
||||
import { defineComponent, computed } from 'vue'
|
||||
export default {
|
||||
name: 'UserModal',
|
||||
components: { Modal },
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
row: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
emits: {
|
||||
'update:visible': null,
|
||||
onClose: null,
|
||||
done: null
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const getModalOptions = computed(() => {
|
||||
return {
|
||||
visible: props.visible
|
||||
}
|
||||
})
|
||||
|
||||
/* 关闭弹窗 */
|
||||
const handleClose = () => {
|
||||
// vodModal.value.handleUploadCancel()
|
||||
emit('update:visible', false)
|
||||
}
|
||||
|
||||
return {
|
||||
getModalOptions
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Loading…
Reference in New Issue