@@ -9,7 +9,16 @@ | |||
<!--顶部右侧区域--> | |||
<slot name="toolbar" /> | |||
<!--刷新--> | |||
<span @click="reload">刷新</span> | |||
<n-tooltip trigger="hover"> | |||
<template #trigger> | |||
<div class="table-toolbar-right-icon" @click="reload"> | |||
<n-icon size="18"> | |||
<ReloadOutlined /> | |||
</n-icon> | |||
</div> | |||
</template> | |||
<span>刷新</span> | |||
</n-tooltip> | |||
</div> | |||
</div> | |||
<div class="s-table"> | |||
@@ -24,12 +33,14 @@ | |||
</template> | |||
<script> | |||
import { ReloadOutlined } from '@vicons/antd' | |||
import { tableProps } from './tools/props.js' | |||
import { useDataSource } from './tools/useDataSource.js' | |||
import { usePagination } from './tools/usePagination.js' | |||
import { ref, unref, computed, toRaw, provide } from 'vue' | |||
export default { | |||
name: 'DataTable', | |||
components: { ReloadOutlined }, | |||
props: { | |||
...tableProps | |||
}, | |||
@@ -66,12 +77,13 @@ export default { | |||
/* tableData-start */ | |||
const tableData = ref([]) | |||
const { getDataSourceRef, getRowKey, reload } = useDataSource(getProps, { getPaginationInfo, setPagination, tableData, setLoading }, emit) | |||
const isRequest = !!unref(getProps).request | |||
const getBindProps = computed(() => { | |||
return { | |||
...unref(getProps), | |||
loading: unref(getLoading), | |||
rowKey: unref(getRowKey), | |||
data: unref(getDataSourceRef), | |||
data: isRequest ? unref(getDataSourceRef) : unref(getProps).data, | |||
remote: true | |||
} | |||
}) | |||
@@ -84,7 +96,8 @@ export default { | |||
getBindProps, | |||
pagination, | |||
updatePage, | |||
updatePageSize | |||
updatePageSize, | |||
reload | |||
} | |||
} | |||
} | |||
@@ -104,12 +117,15 @@ export default { | |||
.table-toolbar-right { | |||
display: flex; | |||
justify-content: flex-end; | |||
align-items: center; | |||
flex: 1; | |||
.table-toolbar-icon { | |||
.table-toolbar-right-icon { | |||
margin-left: 12px; | |||
font-size: 16px; | |||
cursor: pointer; | |||
color: var(--text-color); | |||
.n-icon{ | |||
vertical-align: middle; | |||
} | |||
&:hover { | |||
color: #1890ff; | |||
} |
@@ -0,0 +1,75 @@ | |||
<template> | |||
<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> | |||
<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> | |||
</template> | |||
</n-form> | |||
</div> | |||
</template> | |||
<script> | |||
import { computed, reactive, ref, unref, toRaw } from 'vue' | |||
import { NForm } from 'naive-ui' | |||
export default { | |||
name: 'SearchPage', | |||
props: { | |||
...NForm.props, | |||
info: { | |||
type: Array, | |||
default: () => [] | |||
} | |||
}, | |||
setup(props, { emit }) { | |||
// const formOption = reactive({ | |||
// form: { | |||
// } | |||
// }) | |||
const getFormRef = computed(() => { | |||
const { info } = unref(props) | |||
const form = ref({}) | |||
info.forEach((item) => { | |||
form.value[item.key] = '' | |||
}) | |||
return unref(form) | |||
}) | |||
console.log(getFormRef) | |||
const getFormOptions = computed(() => { | |||
return { | |||
form: unref(getFormRef), | |||
labelWidth: 'auto', | |||
labelPlacement: 'left', | |||
inline: true, | |||
info: [...unref(props).info] | |||
} | |||
}) | |||
function getFormInfo() { | |||
console.log(getFormOptions) | |||
} | |||
return { | |||
getFormOptions, | |||
getFormInfo | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped lang='scss'> | |||
.n-form{ | |||
flex-wrap: wrap; | |||
} | |||
.n-form-item{ | |||
.n-input{ | |||
width: 200px; | |||
} | |||
.n-select{ | |||
width: 200px; | |||
} | |||
} | |||
</style> |
@@ -1,6 +1,6 @@ | |||
import { defineStore } from 'pinia' | |||
import { asyncRoutes, basicRoutes } from '@/router/routes' | |||
import { getMenu } from '@/api/system' | |||
import { getMenu } from '@/api/system/menu' | |||
import Layout from '@/layout/index.vue' | |||
import modules from '@/utils/module.js' | |||
@@ -17,12 +17,7 @@ export function setupInterceptor(service) { | |||
const token = getToken() | |||
if (token) { | |||
/** | |||
* * jwt token | |||
* ! 认证方案: Bearer | |||
*/ | |||
config.headers.Authorization = token | |||
return config | |||
} | |||
/** | |||
@@ -40,43 +35,25 @@ export function setupInterceptor(service) { | |||
) | |||
service.interceptors.response.use( | |||
(response) => response?.data, | |||
(error) => { | |||
const { code, message } = error.response?.data | |||
// return Promise.reject({ code, message }) | |||
/** | |||
* TODO 此处可以根据后端返回的错误码自定义框架层面的错误处理 | |||
*/ | |||
// const { currentRoute } = router | |||
(response) => { | |||
const { code } = response?.data | |||
const { currentRoute } = router | |||
switch (code) { | |||
case 401: | |||
// 未登录(可能是token过期或者无效了) | |||
console.error(message) | |||
removeToken() | |||
router.replace({ | |||
path: '/login' | |||
// query: { ...currentRoute.query, redirect: currentRoute.path } | |||
}) | |||
break | |||
case 403: | |||
// 没有权限 | |||
console.error(message) | |||
break | |||
case 404: | |||
// 资源不存在 | |||
console.error(message) | |||
break | |||
default: | |||
break | |||
} | |||
// 已知错误resolve,在业务代码中作提醒,未知错误reject,捕获错误统一提示接口异常(9000以上为业务类型错误,需要跟后端确定好) | |||
if ([401, 403, 404].includes(code) || code >= 9000) { | |||
return Promise.resolve({ code, message }) | |||
} else { | |||
console.error('【err】' + error) | |||
return Promise.reject({ message: '接口异常,请稍后重试!' }) | |||
} | |||
return response?.data | |||
}, | |||
(error) => { | |||
return Promise.reject(error) | |||
} | |||
) | |||
} |
@@ -1,43 +1,124 @@ | |||
<template> | |||
<div> | |||
<n-button @click="handleLogin">登录</n-button> | |||
<div class="login_bg"> | |||
<n-form | |||
ref="formRef" | |||
:model="loginForm" | |||
:rules="rules" | |||
label-placement="left" | |||
label-width="auto" | |||
require-mark-placement="right-hanging" | |||
:style="{ | |||
maxWidth: '640px', | |||
backgroundColor: '#fff', | |||
padding: '20px', | |||
borderRadius: '10px' | |||
}" | |||
@keyup.enter="handleLogin" | |||
> | |||
<n-form-item label="用户名" path="username"> | |||
<n-input v-model:value="loginForm.username" placeholder="请输入用户名" /> | |||
</n-form-item> | |||
<n-form-item label="密码" path="password"> | |||
<n-input v-model:value="loginForm.password" type="password" placeholder="请输入用户名" /> | |||
</n-form-item> | |||
<n-form-item label="验证码" path="captcha"> | |||
<n-input v-model:value="loginForm.captcha" placeholder="请输入验证码" /> | |||
<img v-if="captcha" :src="captcha" alt="" @click="changeCode"> | |||
</n-form-item> | |||
<n-form-item path="remember"> | |||
<n-checkbox v-model:checked="loginForm.remember" size="medium" label="记住密码" /> | |||
</n-form-item> | |||
<n-form-item> | |||
<n-button @click="handleLogin">登录</n-button> | |||
</n-form-item> | |||
</n-form> | |||
</div> | |||
</template> | |||
<script> | |||
import { userLogin, userCaptcha } from '@/api/login/index.js' | |||
import { setToken } from '@/utils/token' | |||
import { ref, reactive, onMounted } from 'vue' | |||
export default { | |||
name: 'LoginPage', | |||
setup() { | |||
async function handleLogin() { | |||
try { | |||
const params = { | |||
captcha: '1', | |||
key: '9132e06a-e2f0-4a9d-88da-b43ced72bb2e', | |||
password: '123456', | |||
remember: false, | |||
username: 'admin' | |||
} | |||
const res = await userLogin(params) | |||
if (res.code === 0) { | |||
setToken(res.data.access_token) | |||
} | |||
} catch (error) { | |||
// console.log(error) | |||
const loginForm = reactive({ | |||
username: '', | |||
password: '', | |||
key: '', | |||
captcha: '', | |||
remember: false | |||
}) | |||
onMounted(() => { | |||
changeCode(loginForm) | |||
}) | |||
// 获取图形验证码 | |||
const captcha = ref('') | |||
async function changeCode(form) { | |||
const params = { | |||
username: form.username, | |||
password: form.password, | |||
captcha: form.captcha | |||
} | |||
const captForm = await userCaptcha(params) | |||
captcha.value = captForm.data.captcha | |||
loginForm.key = captForm.data.key | |||
} | |||
// userCaptcha() | |||
return { | |||
handleLogin | |||
loginForm, | |||
captcha, | |||
changeCode, | |||
rules: reactive({ | |||
username: { | |||
required: true, | |||
trigger: ['blur', 'input'], | |||
message: '请输入用户名' | |||
}, | |||
password: { | |||
required: true, | |||
trigger: ['blur', 'input'], | |||
message: '请输入密码' | |||
}, | |||
captcha: { | |||
required: true, | |||
trigger: ['blur', 'input'], | |||
message: '请输入验证码' | |||
} | |||
}) | |||
} | |||
}, | |||
methods: { | |||
handleLogin() { | |||
console.log(this.loginForm) | |||
userLogin(this.loginForm).then((res) => { | |||
if (res.code === 0) { | |||
// 登录成功存储token,跳转首页 | |||
setToken(res.data.access_token) | |||
this.goPage() | |||
} else { | |||
console.log(res) | |||
} | |||
}) | |||
}, | |||
// 登录成功跳转 | |||
goPage() { | |||
// const query = this.$route.query | |||
// const path = query && query.from ? query.from : '/' | |||
// this.$router.push(path) | |||
this.$router.replace('/home') | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
.login_bg { | |||
width: 100%; | |||
height: 100%; | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
} | |||
</style> | |||
@@ -1,6 +1,9 @@ | |||
<template> | |||
<div> | |||
<n-card> | |||
<headSearch :info="data.info" /> | |||
<data-table | |||
:columns="data.columns" | |||
:row-key="(row) => row.id" | |||
@@ -22,87 +25,21 @@ | |||
</template> | |||
<script> | |||
import headSearch from '@/components/Search/index.vue' | |||
import dataTable from '@/components/DataTable/index.vue' | |||
import TableAction from '@/components/DataTable/tools/Action.vue' | |||
import TableImage from '@/components/DataTable/tools/Image.vue' | |||
import { getUserList } from '@/api/system/index.js' | |||
import { h, onMounted, unref } from 'vue' | |||
import { h, ref, unref } from 'vue' | |||
import { reactive } from 'vue' | |||
import table from './table.js' | |||
import info from './info.js' | |||
export default { | |||
name: 'MenuPage', | |||
components: { dataTable }, | |||
components: { headSearch, dataTable }, | |||
setup() { | |||
const data = reactive({ | |||
columns: [ | |||
{ | |||
title: '用户编号', | |||
key: 'code', | |||
align: 'center', | |||
minWidth: 80 | |||
}, | |||
{ | |||
title: '头像', | |||
key: 'avatar', | |||
align: 'center', | |||
render(row) { | |||
return h(TableImage, { | |||
images: { | |||
width: 36, | |||
height: 36, | |||
src: row.avatar | |||
} | |||
}) | |||
}, | |||
minWidth: 80 | |||
}, | |||
{ | |||
title: '用户账号', | |||
key: 'username', | |||
align: 'center', | |||
minWidth: 80 | |||
}, | |||
{ | |||
title: '用户姓名', | |||
key: 'realname', | |||
align: 'center', | |||
minWidth: 80 | |||
}, | |||
{ | |||
title: '用户类型', | |||
key: 'type', | |||
align: 'center', | |||
minWidth: 80 | |||
}, | |||
{ | |||
title: '角色', | |||
key: 'roles', | |||
align: 'center', | |||
minWidth: 100 | |||
}, | |||
{ | |||
title: '状态', | |||
key: 'status', | |||
align: 'center', | |||
minWidth: 80 | |||
}, | |||
{ | |||
title: '部门', | |||
key: 'deptName', | |||
align: 'center', | |||
minWidth: 120 | |||
}, | |||
{ | |||
title: '创建时间', | |||
key: 'createTime', | |||
align: 'center', | |||
minWidth: 160 | |||
}, | |||
{ | |||
title: '更新时间', | |||
key: 'updateTime', | |||
align: 'center', | |||
minWidth: 160 | |||
}, | |||
...table.columns, | |||
{ | |||
title: '操作', | |||
align: 'center', | |||
@@ -135,29 +72,13 @@ export default { | |||
} | |||
} | |||
], | |||
data: [], | |||
pagination: { | |||
pageSize: 10 | |||
} | |||
info: ref(info) | |||
}) | |||
function play(row) { | |||
console.log(row) | |||
} | |||
/** | |||
* @description: 获取用户数据 | |||
* @return {*} | |||
*/ | |||
async function fetchList() { | |||
const params = { | |||
page: 1, | |||
limit: 10 | |||
} | |||
const res = await getUserList(params) | |||
data.data = res.data | |||
} | |||
const params = reactive({ | |||
name: 'xiaoMa' | |||
}) | |||
@@ -170,10 +91,6 @@ export default { | |||
return await getUserList(_params) | |||
} | |||
onMounted(() => { | |||
fetchList() | |||
}) | |||
return { data, loadDataTable } | |||
} | |||
} |
@@ -0,0 +1,20 @@ | |||
const data = [ | |||
{ | |||
label: '角色名称', | |||
key: 'name', | |||
placeholder: '请输入角色名称' | |||
}, | |||
{ | |||
label: '角色备注', | |||
key: 'desc' | |||
}, | |||
{ | |||
label: '角色类型', | |||
key: 'op', | |||
options: [{ | |||
label: 11, value: 1 | |||
}] | |||
} | |||
] | |||
export default data |
@@ -0,0 +1,73 @@ | |||
import TableImage from '@/components/DataTable/tools/Image.vue' | |||
import { h } from 'vue' | |||
const data = { | |||
columns: [ | |||
{ | |||
title: '用户编号', | |||
key: 'code', | |||
align: 'center' | |||
}, | |||
{ | |||
title: '头像', | |||
key: 'avatar', | |||
align: 'center', | |||
render(row) { | |||
return h(TableImage, { | |||
images: { | |||
width: 36, | |||
height: 36, | |||
src: row.avatar | |||
} | |||
}) | |||
} | |||
}, | |||
{ | |||
title: '用户账号', | |||
key: 'username', | |||
align: 'center' | |||
}, | |||
{ | |||
title: '用户姓名', | |||
key: 'realname', | |||
align: 'center' | |||
}, | |||
{ | |||
title: '用户类型', | |||
key: 'type', | |||
align: 'center', | |||
width: 100 | |||
}, | |||
{ | |||
title: '角色', | |||
key: 'roles', | |||
align: 'center' | |||
}, | |||
{ | |||
title: '状态', | |||
key: 'status', | |||
align: 'center', | |||
width: 100 | |||
}, | |||
{ | |||
title: '部门', | |||
key: 'deptName', | |||
align: 'center' | |||
}, | |||
{ | |||
title: '创建时间', | |||
key: 'createTime', | |||
align: 'center', | |||
width: 160 | |||
}, | |||
{ | |||
title: '更新时间', | |||
key: 'updateTime', | |||
align: 'center', | |||
width: 160 | |||
} | |||
] | |||
} | |||
export default data |
@@ -147,7 +147,7 @@ export default { | |||
limit: 10 | |||
} | |||
const res = await getUserList(params) | |||
data.data = res.data | |||
data.data = res.data.list | |||
} | |||
const params = reactive({ |