Browse Source

auth

master
sprint 6 months ago
parent
commit
7fe5c0c841
  1. 2
      .prettierrc.json
  2. 6
      src/App.vue
  3. 10
      src/api/system.ts
  4. 16
      src/components/Snackbar.vue
  5. 10
      src/components/layout/NavbarsBox.vue
  6. 1
      src/main.ts
  7. 4
      src/plugins/axios.ts
  8. 13
      src/router/index.ts
  9. 3
      src/views/error/404.vue
  10. 17
      src/views/login/LoginView.vue
  11. 161
      src/views/system/ManagerList.vue
  12. 176
      src/views/system/RoleList.vue
  13. 202
      src/views/userSearch/index.vue

2
.prettierrc.json

@ -2,6 +2,6 @@
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"singleQuote": true,
"printWidth": 120,
"printWidth": 140,
"tabWidth": 4
}

6
src/App.vue

@ -18,13 +18,13 @@ const exitManager = () => {
store.$reset()
router.push('/login')
}
</script>
<template>
<v-layout v-if="route.name != undefined && route.name != 'Login' && route.name != 'NotFound'" >
<v-layout v-if="route.name != undefined && route.name != 'Login' && route.name != 'NotFound'">
<v-app-bar :title="title" color="primary">
<v-spacer></v-spacer>
<v-btn>{{ store.$state.name }}</v-btn>
<v-divider vertical inset></v-divider>
<v-btn @click="exitManager">退出</v-btn>
</v-app-bar>
@ -33,7 +33,7 @@ const exitManager = () => {
<Navbars />
</v-navigation-drawer>
<v-main class="d-flex" :height="viewportHeight">
<v-main class="d-flex" :min-height="viewportHeight">
<v-card width="100%" class="pa-2" style="height: 100%" elevation="0">
<RouterView />
</v-card>

10
src/api/system.ts

@ -1,10 +0,0 @@
export interface FormInfo {
avatar: string
id: number
account: string
nickname: string
password: string
roleID: number
secret: string
status: number
}

16
src/components/Snackbar.vue

@ -8,6 +8,11 @@ const data = reactive({
snackbar: { show: props.show, msg: props.msg } as any,
})
const close = () => {
emits('close', false)
data.snackbar.show = false
}
watch(
() => [props.show, props.msg],
(v) => {
@ -18,11 +23,16 @@ watch(
</script>
<template>
<v-snackbar v-model="data.snackbar.show" vertical>
<p>{{ data.snackbar.msg }}</p>
<v-snackbar v-model="data.snackbar.show" @update:model-value="close" vertical class="custom-snackbar">
<div class="text-subtitle-1 pb-2">{{ data.snackbar.msg }}</div>
<template v-slot:actions>
<v-btn color="indigo" variant="text" @click="data.snackbar.show = emits('close', false)"> Close </v-btn>
<v-btn color="pink" variant="text" @click="close"> Close </v-btn>
</template>
</v-snackbar>
</template>
<style scoped>
.custom-snackbar {
}
</style>

10
src/components/layout/NavbarsBox.vue

@ -9,8 +9,10 @@ const data = reactive({
menus: [] as any[],
})
const jump = (path: string) => {
router.push(path)
const jump = (item: any) => {
router.push(item.path)
//data.open = item.id
}
watch(
@ -23,6 +25,7 @@ watch(
onMounted(() => {
data.menus = managerStore.$state.menus
})
</script>
<template>
@ -32,8 +35,7 @@ onMounted(() => {
<v-list-item v-bind="props" :title="item.title"></v-list-item>
</template>
<v-list-item v-for="(v, k) in item.children" :key="k" :title="v.title" :value="v.id" @click="jump(v.path)">
</v-list-item>
<v-list-item v-for="(v, k) in item.children" :key="k" :title="v.title" :value="v.id" @click="jump(v)"> </v-list-item>
</v-list-group>
</v-list>
</template>

1
src/main.ts

@ -16,7 +16,6 @@ import * as directives from 'vuetify/directives'
import '@mdi/font/css/materialdesignicons.css'
import { md3 } from 'vuetify/blueprints'
const vuetify = createVuetify({
components,
directives,

4
src/plugins/axios.ts

@ -23,8 +23,8 @@ instance.interceptors.request.use(
//响应拦截器
instance.interceptors.response.use(
function (response) {
const expireCode = [1000, 1001, 7]
if (expireCode.includes(response.data.code)) router.push("/login")
const expireCode = [100]
if (expireCode.includes(response.data.code)) router.push('/login')
return response
},

13
src/router/index.ts

@ -7,7 +7,7 @@ const router = createRouter({
{
path: '/',
name: 'Home',
redirect: '/login'
redirect: '/login',
},
{
path: '/login',
@ -15,25 +15,25 @@ const router = createRouter({
component: () => import('../views/login/LoginView.vue'),
},
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('../views/error/404.vue') },
],
})
const components = import.meta.glob('../views/**/*.vue');
const components = import.meta.glob('../views/**/*.vue')
//设置导航栏
let initBars = false
export function initNavigation(menus: any) {
initBars = true
for (const item of menus) {
if (item.component != "") router.addRoute({ path: item.path, name: item.path, component: components[`../views${item.component}.vue`] })
if (item.component != '')
router.addRoute({ path: item.path, name: item.path, component: components[`../views${item.component}.vue`] })
if (item.children && item.children.length > 0) initNavigation(item.children)
}
}
router.beforeEach((to) => {
//未登录则跳转至登录
if (useManagerStore().token == "" && to.name != "Login") {
if (useManagerStore().token == '' && to.name != 'Login') {
return { name: 'Login' }
}
@ -49,10 +49,9 @@ router.beforeEach((to) => {
break
}
}
if (!issetMenu) return { name: "NotFound" }
if (!issetMenu) return { name: 'NotFound' }
return to.fullPath
}
})
export default router

3
src/views/error/404.vue

@ -1,6 +1,5 @@
<script lang="ts" setup>
import { RouterLink } from 'vue-router';
import { RouterLink } from 'vue-router'
</script>
<template>
<div>404</div>

17
src/views/login/LoginView.vue

@ -4,6 +4,7 @@ import { onMounted, reactive } from 'vue'
import Snackbar from '@/components/Snackbar.vue'
import { useManagerStore } from '@/stores/manager'
import { useRouter } from 'vue-router'
import { initNavigation } from '@/router'
const title = import.meta.env.VITE_APP_TITLE
const router = useRouter()
@ -15,22 +16,22 @@ const data = reactive({
})
const onSubmit = async () => {
const response = (
await instance.post('/login', { username: data.username, password: data.password, verification: '' })
).data
const response = (await instance.post('/login', { username: data.username, password: data.password })).data
if (!response.code) {
data.snackbar.show = true
data.snackbar.msg = '用户名或密码错误'
}
console.log(response.data)
managerStore.$patch({
name: response.data.username,
menus: response.data.menus,
token: response.data.token,
})
initNavigation(response.data.menus)
router.push(response.data.menus[0].children[0].path)
//setNavigation(response.data.menus)
}
</script>
@ -38,13 +39,7 @@ const onSubmit = async () => {
<div class="login-box">
<div class="svg-top">
<!--?xml version="1.0" encoding="utf-8"?-->
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
height="1337"
width="1337"
>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="1337" width="1337">
<defs>
<path
id="path-1"

161
src/views/system/ManagerList.vue

@ -2,135 +2,131 @@
import { pageLimit } from '@/common/params'
import instance from '@/plugins/axios'
import { onMounted, reactive, ref } from 'vue'
import type { FormInfo } from '@/api/system'
import ConfirmBox from '@/components/ConfirmBox.vue'
import Snackbar from '@/components/Snackbar.vue'
import DataTable from '@/components/DataTable.vue'
interface FormInfo {
id: number
username: string
password: string
role_id: number
status: number
}
const data = reactive({
headers: [
{ title: 'ID', key: 'id', align: 'center' },
{ title: '账号', key: 'account', align: 'center' },
{ title: '角色', key: 'roleName', align: 'center' },
{ title: '创建时间', key: 'createdAt', align: 'center' },
{ title: '状态', key: 'status', align: 'center' },
{ title: '操作', key: 'actions', align: 'center' },
{ title: 'ID', key: 'id' },
{ title: '账号', key: 'username' },
{ title: '角色', key: 'role_name' },
{ title: '状态', key: 'status' },
{ title: '创建时间', key: 'created_at' },
{ title: '操作', key: 'actions' },
] as any[],
desserts: [],
options: { roles: [] as unknown[] },
options: {
roles: [] as any[],
},
form: {} as FormInfo,
rules: {
account: [(v: any) => !!v || '参数不能为空'],
nickname: [(v: any) => !!v || '参数不能为空'],
username: [(v: any) => !!v || '参数不能为空'],
password: [(v: any) => (v.length >= 6 && v.length <= 20) || '密码字符在6-20之间'],
roles: [(v: any) => v > 0 || '请选择角色'],
},
confirmPassword: '',
dialog: false,
confirm: { show: false, msg: '确定要删除此记录吗' },
request: { method: 0, url: '' },
snackbar: { show: false, msg: '' },
page: { current: 1, total: 10 },
page: { current: 1, last_page: 1 },
})
const getItems = async (page: number = 1) => {
const params = { page: page, limit: pageLimit }
const response = (await instance.post('/admin/fetch-admin-list', params)).data
const params = {
username: '',
role_name: '',
page: page,
size: pageLimit,
}
const response = (await instance.post('/manager/list', params)).data
if (response.code != 0) {
if (!response.code) {
data.snackbar.show = true
data.snackbar.msg = response.msg
return
}
data.desserts = response.data.list
data.page.total = response.data.totalPage
data.desserts = response.data.data
data.page.last_page = response.data.last_page
}
//
const getRoles = async () => {
const params = { page: 1, limit: 100 }
const response = (await instance.post('/role/fetch-role-list', params)).data
const params = {
role_name: '',
page: 1,
size: 100,
}
const response = (await instance.post('/role/list', params)).data
if (response.code != 0) {
if (!response.code) {
data.snackbar.show = true
data.snackbar.msg = response.msg
return
}
data.options.roles = [{ title: '请选择', value: 0 }]
for (const item of response.data.list) {
data.options.roles.push({ title: item.name, value: item.id })
for (const item of response.data.data) {
data.options.roles.push({
title: item.name,
value: item.id,
})
}
}
const insertBox = (method: number) => {
data.dialog = true
data.form = { roleID: 0, status: 0 } as FormInfo
data.confirmPassword = ''
data.form = { status: 1, role_id: 0 } as FormInfo
data.request.method = method
data.request.url = '/admin/create-admin'
data.request.url = '/manager/add'
}
const changeBox = (method: number, item: any) => {
data.dialog = true
data.form = item
data.form = JSON.parse(JSON.stringify(item))
data.form.password = ''
data.confirmPassword = ''
data.request.method = method
data.request.url = '/admin/update-admin'
data.request.url = '/manager/update'
}
const destoryBox = (method: number, item: any) => {
data.confirm.show = true
data.form = { id: item.id } as FormInfo
data.form.id = item.id
data.request.method = method
data.request.url = '/admin/delete-admin'
data.request.url = '/manager/delete'
}
const form = ref()
const confirms = ref()
const onSubmit = async () => {
let params = {}
let params = { id: data.form.id } as FormInfo
if (data.request.method < 2) {
if (!(await form.value.validate()).valid) return
if (data.form.password != data.confirmPassword) {
data.snackbar.show = true
data.snackbar.msg = '两次密码不一致'
return
}
if (data.form.password != '' && (data.form.password.length < 6 || data.form.password.length > 20)) {
data.snackbar.show = true
data.snackbar.msg = '密码字符只能在6-20之间'
return
}
params = {
avatar: '',
id: data.form.id,
account: data.form.account,
nickname: data.form.nickname,
password: data.form.password,
roleID: data.form.roleID,
status: data.form.status,
secret: 'google',
}
} else {
params = { id: data.form.id }
params.username = data.form.username
params.password = data.form.password
params.role_id = data.form.role_id
params.status = data.form.status
}
const response = (await instance.post(data.request.url, params)).data
if (response.code != 0) {
data.snackbar.show = true
data.snackbar.msg = response.msg
return
}
data.snackbar.show = true
data.snackbar.msg = response.msg
if (!response.code) return
data.dialog = false
confirms.value.closeOverlay()
@ -153,24 +149,12 @@ onMounted(() => {
:headers="data.headers"
:items="data.desserts"
:page="data.page.current"
:size="data.page.total"
:size="data.page.last_page"
@get-items="(v) => getItems(v)"
>
<template #item.status="{ value }">
<v-btn
v-if="value === 0"
icon="mdi-close-thick"
color="pink"
variant="tonal"
size="x-small"
></v-btn>
<v-btn
v-if="value === 1"
icon="mdi-check-bold"
color="green"
variant="tonal"
size="x-small"
></v-btn>
<v-btn v-if="value === 1" icon="mdi-check-bold" color="green" variant="tonal" size="x-small"></v-btn>
<v-btn v-if="value === 2" icon="mdi-close-thick" color="pink" variant="tonal" size="x-small"></v-btn>
</template>
<template #item.actions="{ item }">
@ -188,28 +172,13 @@ onMounted(() => {
<v-form ref="form">
<v-text-field
label="账号"
v-model="data.form.account"
:rules="data.rules.account"
></v-text-field>
<v-text-field
label="昵称"
v-model="data.form.nickname"
:rules="data.rules.nickname"
v-model="data.form.username"
:rules="data.rules.username"
:disabled="data.request.method == 1 ? true : false"
></v-text-field>
<v-text-field label="密码" v-model="data.form.password"></v-text-field>
<v-text-field label="确认密码" v-model="data.confirmPassword"></v-text-field>
<v-select
v-model="data.form.roleID"
:items="data.options.roles"
:rules="data.rules.roles"
></v-select>
<v-switch
v-model="data.form.status"
:true-value="1"
:false-value="0"
inset
color="primary"
></v-switch>
<v-select v-model="data.form.role_id" :items="data.options.roles" :rules="data.rules.roles"></v-select>
<v-switch v-model="data.form.status" :true-value="1" :false-value="2" inset color="primary"></v-switch>
</v-form>
</v-card-text>
<v-card-actions>
@ -228,6 +197,6 @@ onMounted(() => {
@show="(v) => (data.confirm.show = v)"
/>
<Snackbar :show="data.snackbar.show" :msg="data.snackbar.msg" />
<Snackbar :show="data.snackbar.show" :msg="data.snackbar.msg" @close="(v) => (data.snackbar.show = v)" />
</v-card>
</template>

176
src/views/system/RoleList.vue

@ -0,0 +1,176 @@
<script setup lang="ts">
import { pageLimit } from '@/common/params'
import instance from '@/plugins/axios'
import { onMounted, reactive, ref } from 'vue'
import ConfirmBox from '@/components/ConfirmBox.vue'
import Snackbar from '@/components/Snackbar.vue'
import DataTable from '@/components/DataTable.vue'
interface FormInfo {
id: Number
name: String
permissions: Array<any>
}
const data = reactive({
headers: [
{ title: 'ID', key: 'id' },
{ title: '角色名称', key: 'name' },
{ title: '创建时间', key: 'created_at' },
{ title: '更新时间', key: 'updated_at' },
{ title: '操作', key: 'actions' },
] as any[],
desserts: [],
options: {
roles: [] as any[],
},
form: {} as FormInfo,
rules: {
name: [(v: any) => !!v || '参数不能为空'],
permissions: [(v: any) => v.length > 0 || '请至少选择一个权限'],
},
dialog: false,
confirm: { show: false, msg: '确定要删除此记录吗' },
request: { method: 0, url: '' },
snackbar: { show: false, msg: '' },
page: { current: 1, last_page: 1 },
})
const getItems = async (page: number = 1) => {
const params = {
role_name: '',
page: page,
size: pageLimit,
}
const response = (await instance.post('/role/list', params)).data
if (!response.code) {
data.snackbar.show = true
data.snackbar.msg = response.msg
return
}
data.desserts = response.data.data
data.page.last_page = response.data.last_page
}
//
const getMenus = async () => {
const response = (await instance.post('/getMenus')).data
if (!response.code) {
data.snackbar.show = true
data.snackbar.msg = response.msg
return
}
data.options.roles = [{ title: '请选择', value: 0 }]
for (const item of response.data.data) {
data.options.roles.push({
title: item.name,
value: item.id,
})
}
}
const insertBox = (method: number) => {
data.dialog = true
data.form = {} as FormInfo
data.request.method = method
data.request.url = '/role/add'
}
const changeBox = (method: number, item: any) => {
data.dialog = true
data.form = JSON.parse(JSON.stringify(item))
data.request.method = method
data.request.url = '/role/update'
}
const destoryBox = (method: number, item: any) => {
data.confirm.show = true
data.form.id = item.id
data.request.method = method
data.request.url = '/role/delete'
}
const form = ref()
const confirms = ref()
const onSubmit = async () => {
let params = { id: data.form.id } as FormInfo
if (data.request.method < 2) {
if (!(await form.value.validate()).valid) return
params.name = data.form.name
params.permissions = data.form.permissions
}
const response = (await instance.post(data.request.url, params)).data
data.snackbar.show = true
data.snackbar.msg = response.msg
if (!response.code) return
data.dialog = false
confirms.value.closeOverlay()
getItems()
}
onMounted(() => {
getItems()
getMenus()
})
</script>
<template>
<v-card>
<v-card-title>
<v-btn color="primary" @click="insertBox(0)">添加</v-btn>
</v-card-title>
<v-card-text>
<DataTable
:headers="data.headers"
:items="data.desserts"
:page="data.page.current"
:size="data.page.last_page"
@get-items="(v) => getItems(v)"
>
<template #item.actions="{ item }">
<v-btn size="small" class="me-2" @click="changeBox(1, item)">编辑</v-btn>
<v-btn color="pink" size="small" @click="destoryBox(2, item)">删除</v-btn>
</template>
</DataTable>
</v-card-text>
<!-- 编辑 -->
<v-dialog v-model="data.dialog" width="600">
<v-card>
<v-card-title>信息</v-card-title>
<v-card-text>
<v-form ref="form">
<v-text-field label="账号" v-model="data.form.name" :rules="data.rules.name"></v-text-field>
</v-form>
</v-card-text>
<v-card-actions>
<v-btn color="pink" @click="data.dialog = false">取消</v-btn>
<v-btn color="primary" variant="flat" @click="onSubmit">确定</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- 删除 -->
<ConfirmBox
ref="confirms"
:show="data.confirm.show"
:msg="data.confirm.msg"
@on-sumbit="onSubmit"
@show="(v) => (data.confirm.show = v)"
/>
<Snackbar :show="data.snackbar.show" :msg="data.snackbar.msg" />
</v-card>
</template>

202
src/views/userSearch/index.vue

@ -1,202 +0,0 @@
<script setup lang="ts">
import { pageLimit } from '@/common/params'
import instance from '@/plugins/axios'
import { onMounted, reactive, ref } from 'vue'
import Snackbar from '@/components/Snackbar.vue'
import DataTable from '@/components/DataTable.vue';
interface AccountLimit {
user_no: string[]
freeze: number
exchangeLimit: number
giveLimit: number
receiveLimit: number
emailLimit: number
gameLimit: number
}
function column(title: string, key: string, align: 'center' | 'start' | 'end' | undefined = 'center') {
return { title, key, align }
}
const data = reactive({
headers: [
{ title: '昵称', key: 'user_info.nickname', align: 'center' },
// { title: '', key: 'user_info.nickname', align: 'center' },
// { title: '', key: 'user_info.remark', align: 'center' },
// { title: 'VIP', key: 'user_info.vip', align: 'center' },
// { title: '', key: 'user_info.gold', align: 'center' },
// { title: '', key: 'user_info.total_bet', align: 'center' },
// { title: '', key: 'user_info.total_win_lose', align: 'center' },
// { title: '', key: 'user_info.total_deposit', align: 'center' },
// { title: '', key: 'user_info.total_withdraw', align: 'center' },
// { title: '', key: 'user_info.deposit_withdraw_difference', align: 'center' },
// { title: 'ID', key: 'user_info.package_id', align: 'center' },
// { title: 'Link Token', key: 'user_info.ad_token', align: 'center' },
// { title: '广', key: 'user_info.channel_name', align: 'center' },
// { title: '', key: 'user_info.register_time', align: 'center' },
// { title: '', key: 'user_info.last_login_time', align: 'center' },
// { title: '线', key: 'user_info.offline_days', align: 'center' },
// { title: '', key: 'user_info.account_type', align: 'center' },
// { title: '', key: 'user_info.account_status', align: 'center' },
] as any[],
desserts: [],
options: {
searchType: [
{ label: '玩家ID', value: 1 },
{ label: '玩家昵称', value: 2 },
{ label: '玩家手机', value: 3 },
{ label: '玩家备注', value: 4 },
{ label: '玩家IP', value: 5 },
{ label: '玩家设备', value: 6 },
],
accountType: [
{ label: '游客', value: 0 },
{ label: '普通', value: 1 },
{ label: '机器人', value: 2 },
{ label: '系统', value: 3 },
],
},
form: { type: 1, text: '' },
rules: {
account: [(v: any) => !!v || '参数不能为空'],
nickname: [(v: any) => !!v || '参数不能为空'],
password: [(v: any) => (v.length >= 6 && v.length <= 20) || '密码字符在6-20之间'],
roles: [(v: any) => v > 0 || '请选择角色'],
},
selected: [],
dialog: false,
accountAuth: {} as AccountLimit,
request: { method: 0, url: '' },
snackbar: { show: false, msg: '' },
page: { current: 1, total: 10 },
})
const getItems = async (page: number = 1) => {
const params = { page: page, limit: pageLimit, text: data.form.text, type: data.form.type }
const response = (await instance.post('/person/userSearch', params)).data
if (response.code != 0) {
data.snackbar.show = true
data.snackbar.msg = response.msg
return
}
data.desserts = response.data.data
data.page.total = response.data.total
}
const changeBox = () => {
if (data.selected.length == 0) {
data.snackbar.show = true
data.snackbar.msg = '请选择玩家'
return
}
data.dialog = true
}
const changeSelected = (item: any) => {
console.log(item)
}
const onSubmit = async () => {
if (data.selected.length == 0) {
data.snackbar.show = true
data.snackbar.msg = '请选择玩家'
return
}
data.accountAuth.user_no = data.selected
const response = (await instance.post(data.request.url, data.accountAuth)).data
if (response.code != 0) {
data.snackbar.show = true
data.snackbar.msg = response.msg
return
}
data.dialog = false
getItems()
}
onMounted(() => {
getItems()
})
</script>
<template>
<v-card>
<v-card-title>
<v-btn color="primary" variant="flat" @click="changeBox">账号限制</v-btn>
</v-card-title>
<v-card-text>
<DataTable :headers="data.headers" />
</v-card-text>
<!-- 编辑 -->
<v-dialog v-model="data.dialog" width="600">
<v-card>
<v-card-title>信息</v-card-title>
<v-card-text>
<v-form ref="form">
<v-switch
label="账号冻结"
v-model="data.accountAuth.freeze"
:true-value="1"
:false-value="0"
inset
color="primary"
></v-switch>
<v-switch
label="提现冻结"
v-model="data.accountAuth.exchangeLimit"
:true-value="1"
:false-value="0"
inset
color="primary"
></v-switch>
<v-switch
label="转账冻结"
v-model="data.accountAuth.giveLimit"
:true-value="1"
:false-value="0"
inset
color="primary"
></v-switch>
<v-switch
label="游戏冻结"
v-model="data.accountAuth.gameLimit"
:true-value="1"
:false-value="0"
inset
color="primary"
></v-switch>
<v-switch
label="接收冻结"
v-model="data.accountAuth.receiveLimit"
:true-value="1"
:false-value="0"
inset
color="primary"
></v-switch>
<v-switch
label="邮件冻结"
v-model="data.accountAuth.emailLimit"
:true-value="1"
:false-value="0"
inset
color="primary"
></v-switch>
</v-form>
</v-card-text>
<v-card-actions>
<v-btn color="pink" @click="data.dialog = false">取消</v-btn>
<v-btn color="primary" variant="flat" @click="onSubmit">确定</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<Snackbar :show="data.snackbar.show" :msg="data.snackbar.msg" />
</v-card>
</template>
Loading…
Cancel
Save