Browse Source

数据效果2

master
sprint 5 months ago
parent
commit
99ed54c075
  1. 4
      .editorconfig
  2. 1
      .gitignore
  3. 8
      .vscode/extensions.json
  4. 2
      README.md
  5. 4
      eslint.config.js
  6. 6084
      package-lock.json
  7. 89
      package.json
  8. 17
      src/common/func.ts
  9. 7
      src/common/interface.ts
  10. 28
      src/components/CardBox.vue
  11. 12
      src/main.ts
  12. 21
      src/views/assignment/BasicOprations.vue
  13. 63
      src/views/assignment/DataIntention.vue
  14. 112
      src/views/assignment/NumberAssign.vue
  15. 98
      src/views/assignment/NumberImport.vue
  16. 3
      src/views/login/LoginView.vue
  17. 9
      src/views/parameter/PhoneScore.vue
  18. 26
      src/views/system/GroupList.vue
  19. 196
      src/views/system/ManagerList.vue
  20. 28
      src/views/system/RoleList.vue
  21. 21
      tsconfig.app.json
  22. 1
      tsconfig.node.json

4
.editorconfig

@ -1,6 +1,6 @@
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}]
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}]
charset = utf-8
indent_size = 4
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

1
.gitignore

@ -28,4 +28,3 @@ coverage
*.sw?
*.tsbuildinfo
package-lock.json

8
.vscode/extensions.json

@ -0,0 +1,8 @@
{
"recommendations": [
"Vue.volar",
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig",
"esbenp.prettier-vscode"
]
}

2
README.md

@ -1,4 +1,4 @@
# backend_web
# telemarket
This template should help get you started developing with Vue 3 in Vite.

4
eslint.config.js

@ -20,8 +20,8 @@ export default [
skipFormatting,
{
rules: {
'max-len': 'off',
'@typescript-eslint/no-explicit-any': 'off', //关闭any类型警告
'vue/valid-v-slot': 'off', //关闭v-slot警告
},
},
'prettier/prettier',
]

6084
package-lock.json

File diff suppressed because it is too large

89
package.json

@ -1,47 +1,46 @@
{
"name": "telemarketing_web",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"build:dev": "run-p type-check \"build-only -- --mode staging\" --",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build --force",
"lint:oxlint": "oxlint . --fix -D correctness --ignore-path .gitignore",
"lint:eslint": "eslint . --fix",
"lint": "run-s lint:*",
"format": "prettier --write src/"
},
"dependencies": {
"axios": "^1.7.9",
"date-fns": "^4.1.0",
"pinia": "^2.3.0",
"pinia-plugin-persistedstate": "^4.2.0",
"vue": "^3.5.13",
"vue-router": "^4.5.0",
"vuetify": "^3.7.6"
},
"devDependencies": {
"@mdi/font": "^7.4.47",
"@tsconfig/node22": "^22.0.0",
"@types/node": "^22.9.0",
"@vitejs/plugin-vue": "^5.1.4",
"@vue/eslint-config-prettier": "^10.1.0",
"@vue/eslint-config-typescript": "^14.1.3",
"@vue/tsconfig": "^0.5.1",
"eslint": "^9.14.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-oxlint": "^0.11.0",
"eslint-plugin-vue": "^9.30.0",
"npm-run-all2": "^7.0.1",
"oxlint": "^0.11.0",
"prettier": "^3.3.3",
"typescript": "~5.6.3",
"vite": "^5.4.10",
"vite-plugin-vue-devtools": "^7.5.4",
"vue-tsc": "^2.1.10"
}
"name": "telemarket",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"build:dev": "run-p type-check \"build-only -- --mode staging\" --",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build",
"lint:oxlint": "oxlint . --fix -D correctness --ignore-path .gitignore",
"lint:eslint": "eslint . --fix",
"lint": "run-s lint:*",
"format": "prettier --write src/"
},
"dependencies": {
"axios": "^1.7.9",
"date-fns": "^4.1.0",
"pinia": "^2.3.0",
"pinia-plugin-persistedstate": "^4.2.0",
"vue": "^3.5.13",
"vue-router": "^4.5.0",
"vuetify": "^3.7.6"
},
"devDependencies": {
"@mdi/font": "^7.4.47",
"@tsconfig/node22": "^22.0.0",
"@types/node": "^22.10.2",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/eslint-config-prettier": "^10.1.0",
"@vue/eslint-config-typescript": "^14.1.3",
"@vue/tsconfig": "^0.7.0",
"eslint": "^9.14.0",
"eslint-plugin-oxlint": "^0.11.0",
"eslint-plugin-vue": "^9.30.0",
"npm-run-all2": "^7.0.1",
"oxlint": "^0.11.0",
"prettier": "^3.3.3",
"typescript": "~5.6.3",
"vite": "^6.0.5",
"vite-plugin-vue-devtools": "^7.6.8",
"vue-tsc": "^2.1.10"
}
}

17
src/common/func.ts

@ -1,10 +1,19 @@
import { format } from "date-fns";
import { format } from 'date-fns'
export function FormatDateTime(date: Date) {
return format(date, "yyyy-MM-dd HH:mm:ss")
return format(date, 'yyyy-MM-dd HH:mm:ss')
}
//格式化日期
export function FormatSearchDate(date: Date, method: number) {
if (method == 0) return format(date, "yyyy-MM-dd 00:00:00")
if (method == 1) return format(date, "yyyy-MM-dd 23:59:59")
if (method == 0) return format(date, 'yyyy-MM-dd 00:00:00')
if (method == 1) return format(date, 'yyyy-MM-dd 23:59:59')
}
//解析Token参数
export function ParseToken(token: string) {
const tokenArr = token.split('.')
if (tokenArr.length != 3) return null
const tokenObj = JSON.parse(atob(tokenArr[1]))
return tokenObj
}

7
src/common/interface.ts

@ -0,0 +1,7 @@
interface CustomResponse {
code: number
data: any
msg: string
}
export type { CustomResponse }

28
src/components/CardBox.vue

@ -0,0 +1,28 @@
<script setup lang="ts">
const props = defineProps({
title: String,
actions: {
default: true,
type: Boolean,
},
})
const emits = defineEmits(['close', 'submit'])
</script>
<template>
<v-card>
<v-toolbar color="primary" dark>
<v-card-title>{{ props.title }}</v-card-title>
<v-spacer></v-spacer>
<v-btn icon="mdi-close-thick" @click="() => emits(`close`, false)"></v-btn>
</v-toolbar>
<v-card-text>
<slot name="text"></slot>
</v-card-text>
<v-card-actions v-show="props.actions">
<v-btn color="pink" @click="() => emits(`close`, false)">取消</v-btn>
<v-btn color="primary" variant="flat" @click="() => emits('submit')">确定</v-btn>
</v-card-actions>
</v-card>
</template>

12
src/main.ts

@ -17,12 +17,12 @@ import '@mdi/font/css/materialdesignicons.css'
import { md3 } from 'vuetify/blueprints'
const vuetify = createVuetify({
components,
directives,
icons: {
defaultSet: 'mdi',
},
blueprint: md3,
components,
directives,
icons: {
defaultSet: 'mdi',
},
blueprint: md3,
})
const app = createApp(App)

21
src/views/assignment/BasicOprations.vue

@ -6,6 +6,7 @@ import { pageLimit } from '@/common/params'
import instance from '@/plugins/axios'
import Snackbar from '@/components/Snackbar.vue'
import { FormatSearchDate } from '@/common/func'
import type { CustomResponse } from '@/common/interface'
const data = reactive({
task_process: [
@ -93,7 +94,7 @@ const getItems = async (newPage: number = 1) => {
page: newPage,
size: pageLimit,
}
const response = (await instance.post('/assignment/phoneList', params)).data
const response: CustomResponse = (await instance.post('/assignment/phoneList', params)).data
if (!response.code) {
data.snackbar.show = true
@ -110,7 +111,7 @@ const getGroups = async () => {
const params = {
group_name: '',
}
const response = (await instance.post('/system/groupList', params)).data
const response: CustomResponse = (await instance.post('/system/groupList', params)).data
if (!response.code) {
data.snackbar.show = true
@ -133,7 +134,7 @@ const getTasks = async () => {
start_time: '',
end_time: '',
}
const response = (await instance.post('/assignment/statisticTask', params)).data
const response: CustomResponse = (await instance.post('/assignment/statisticTask', params)).data
if (!response.code) {
data.snackbar.show = true
@ -154,7 +155,7 @@ const getTasks = async () => {
//
const getHistory = async () => {
const response = (await instance.post('/assignment/statisticHistory')).data
const response: CustomResponse = (await instance.post('/assignment/statisticHistory')).data
if (!response.code) {
data.snackbar.show = true
@ -177,7 +178,7 @@ const showBox = (item: any, method: number) => {
//
const onSubmit = async () => {
const params = { id: data.dialog.id, intention: data.dialog.intention, explain: data.dialog.text }
const response = (await instance.post('/assignment/phoneUpdate', params)).data
const response: CustomResponse = (await instance.post('/assignment/phoneUpdate', params)).data
data.snackbar.show = true
data.snackbar.msg = response.msg
@ -201,7 +202,7 @@ onMounted(() => {
<v-col cols="12">
<v-alert border="start" border-color="primary" variant="tonal">
<v-row>
<v-col v-for="(item, index) in data.task_process">
<v-col v-for="(item, index) in data.task_process" :key="index">
<v-card class="mx-auto" variant="flat" color="primary">
<v-card-text class="text-center">
<p>{{ item.title }}</p>
@ -273,15 +274,17 @@ onMounted(() => {
@get-items="getItems"
>
<template #item.final_intention="{ value, item }">
<span v-for="v in data.options.intention">
<v-btn v-if="v.value == value" size="small" variant="tonal" color="pink" @click="showBox(item, 1)">{{ v.title }}</v-btn>
<span v-for="v in data.options.intention" :key="v.value">
<v-btn v-if="v.value == value" size="small" variant="tonal" color="pink" @click="showBox(item, 1)">{{
v.title
}}</v-btn>
</span>
</template>
<template #item.explain="{ item }">
<v-btn variant="text" color="warning" @click="showBox(item, 2)" icon="mdi-book-open-outline"></v-btn>
</template>
<template #item.first_intention="{ value }">
<span v-for="v in data.options.intention">
<span v-for="v in data.options.intention" :key="v.value">
<v-btn v-if="v.value == value" size="small" variant="text">{{ v.title }}</v-btn>
</span>
</template>

63
src/views/assignment/DataIntention.vue

@ -2,10 +2,10 @@
import DataTable from '@/components/DataTable.vue'
import { VDateInput } from 'vuetify/labs/VDateInput'
import { onMounted, reactive } from 'vue'
import { pageLimit } from '@/common/params'
import instance from '@/plugins/axios'
import Snackbar from '@/components/Snackbar.vue'
import { FormatSearchDate } from '@/common/func'
import type { CustomResponse } from '@/common/interface'
const data = reactive({
desserts: [],
@ -59,7 +59,7 @@ const data = reactive({
})
//
const getItems = async (newPage: number = 1) => {
const getItems = async () => {
let search_time = ['', ''] as any
if (data.search_time[0] != undefined) {
search_time = [FormatSearchDate(data.search_time[0], 0), FormatSearchDate(data.search_time[1] ?? data.search_time[0], 1)]
@ -74,12 +74,10 @@ const getItems = async (newPage: number = 1) => {
...data.search,
assign_start_time: assign_time[0],
assign_end_time: assign_time[1],
reg_start_time: search_time[0],
reg_end_time: search_time[1],
page: newPage,
size: pageLimit,
search_start_time: search_time[0],
search_end_time: search_time[1],
}
const response = (await instance.post('/assignment/phoneList', params)).data
const response: CustomResponse = (await instance.post('/assignment/intention/list', params)).data
if (!response.code) {
data.snackbar.show = true
@ -96,7 +94,7 @@ const getGroups = async () => {
const params = {
group_name: '',
}
const response = (await instance.post('/system/groupList', params)).data
const response: CustomResponse = (await instance.post('/system/groupList', params)).data
if (!response.code) {
data.snackbar.show = true
@ -122,20 +120,6 @@ const showBox = (item: any, method: number) => {
if (data.dialog.method == 2) data.dialog.text = item.explain
}
//
const onSubmit = async () => {
const params = { id: data.dialog.id, intention: data.dialog.intention, explain: data.dialog.text }
const response = (await instance.post('/assignment/phoneUpdate', params)).data
data.snackbar.show = true
data.snackbar.msg = response.msg
if (!response.code) return
data.dialog.show = false
getItems()
}
onMounted(() => {
getItems()
getGroups()
@ -186,18 +170,8 @@ onMounted(() => {
:size="data.paginate.last_page"
@get-items="getItems"
>
<template #item.final_intention="{ value, item }">
<span v-for="v in data.options.intention">
<v-btn v-if="v.value == value" size="small" variant="tonal" color="pink" @click="showBox(item, 1)">{{
v.title
}}</v-btn>
</span>
</template>
<template #item.explain="{ item }">
<v-btn variant="text" color="warning" @click="showBox(item, 2)" icon="mdi-book-open-outline"></v-btn>
</template>
<template #item.first_intention="{ value }">
<span v-for="v in data.options.intention">
<span v-for="v in data.options.intention" :key="v.value">
<v-btn v-if="v.value == value" size="small" variant="text">{{ v.title }}</v-btn>
</span>
</template>
@ -211,28 +185,5 @@ onMounted(() => {
</v-col>
</v-row>
<v-dialog v-model="data.dialog.show" width="600">
<v-card>
<v-toolbar>
<v-card-title>信息</v-card-title>
</v-toolbar>
<v-card-text>
<v-form ref="form">
<template v-if="data.dialog.method == 1">
<v-select label="跟进效果" v-model="data.dialog.intention" :items="data.options.intention"></v-select>
</template>
<template v-if="data.dialog.method == 2">
<v-textarea label="用户意向备注说明" :rows="5" v-model="data.dialog.text"></v-textarea>
</template>
</v-form>
</v-card-text>
<v-card-actions>
<v-btn color="pink" @click="data.dialog.show = 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" />
</template>

112
src/views/assignment/NumberAssign.vue

@ -5,7 +5,10 @@ import instance from '@/plugins/axios'
import { onMounted, reactive, ref } from 'vue'
import { VDateInput } from 'vuetify/labs/VDateInput'
import Snackbar from '@/components/Snackbar.vue'
import { FormatSearchDate } from '@/common/func'
import { FormatSearchDate, ParseToken } from '@/common/func'
import type { CustomResponse } from '@/common/interface'
import { useManagerStore } from '@/stores/manager'
import CardBox from '@/components/CardBox.vue'
const form = ref()
@ -26,12 +29,13 @@ const data = reactive({
snackbar: { show: false, msg: '' },
assign_members: [] as Array<any>,
searchForm: { start_time: '', end_time: '' },
members: [] as any[],
assign_btn: false,
form: { id: 0, assign_count: 0 },
time_range: [],
assign_detail: { show: false },
})
const store = useManagerStore()
//
const getItems = async (newPage: number = 1) => {
let time_range = ['', ''] as any
@ -45,7 +49,7 @@ const getItems = async (newPage: number = 1) => {
page: newPage,
size: pageLimit,
}
const response = (await instance.post('/assignment/membersAssignLogList', params)).data
const response: CustomResponse = (await instance.post('/assignment/membersAssignLogList', params)).data
if (!response.code) {
data.snackbar.show = true
@ -59,14 +63,15 @@ const getItems = async (newPage: number = 1) => {
//
const getMembers = async (page: number = 1) => {
const manager = ParseToken(store.token)
const params = {
username: '',
role_name: '',
group_name: '',
group_id: manager.group_id,
page: page,
size: 1000,
}
const response = (await instance.post('/manager/list', params)).data
const response: CustomResponse = (await instance.post('/manager/list', params)).data
if (!response.code) {
data.snackbar.show = true
@ -74,10 +79,11 @@ const getMembers = async (page: number = 1) => {
return
}
data.members = []
//
data.assign_members = []
for (const item of response.data.data) {
if (item.group_name == '') continue
data.members.push({ id: item.id, username: item.username })
data.assign_members.push({ member_id: item.id, label: `${item.username}(${item.group_name})`, amount: '' })
}
}
@ -85,17 +91,6 @@ const getMembers = async (page: number = 1) => {
const assignMemberBox = (item: any) => {
data.form.id = item.id
data.form.assign_count = item.success_num
if (item.member_assign_status) {
data.assign_btn = false
} else {
data.assign_btn = true
}
//
data.assign_members = []
for (const item of data.members) {
data.assign_members.push({ member_id: item.id, label: item.username, amount: '' })
}
data.dialog = true
}
@ -110,28 +105,28 @@ const membersAssignDetails = async (item: any) => {
return
}
data.assign_members = []
for (const item of respond.data.data) {
let current = { member_id: item.member_id, label: '', amount: item.amount }
for (const member of data.members) {
if (item.member_id == member.id) {
current.label = member.username
for (const index in data.assign_members) {
if (item.member_id == data.assign_members[index].member_id) {
data.assign_members[index].amount = item.amount
data.assign_members[index].score = item.score
break
}
}
data.assign_members.push(current)
}
data.dialog = true
data.assign_btn = false
data.assign_detail.show = true
}
//
const submitAssignMember = async () => {
if (!(await form.value.validate()).valid) return
let params = { id: data.form.id, assign_info: [] as any }
const params = { id: data.form.id, assign_info: [] as any }
for (const item of data.assign_members) {
if (item.amount == '') {
data.snackbar.show = true
data.snackbar.msg = '请填写分配数量'
return
}
params.assign_info.push({ member_id: item.member_id, amount: parseInt(item.amount) })
}
@ -189,32 +184,39 @@ onMounted(() => {
</v-col>
</v-row>
<!-- 弹框 -->
<v-dialog v-model="data.dialog" width="600">
<v-card>
<v-toolbar>
<v-card-title v-show="data.assign_btn">待分配总数: {{ data.form.assign_count }}</v-card-title>
</v-toolbar>
<CardBox :title="`待分配总数: ${data.form.assign_count}`" @submit="submitAssignMember" @close="data.dialog = false">
<template #text>
<v-row>
<v-col cols="12" v-for="item in data.assign_members" :key="item.member_id">
<v-text-field v-model="item.amount" :label="item.label" type="number"></v-text-field>
</v-col>
</v-row>
</template>
</CardBox>
</v-dialog>
<v-card-text>
<v-form ref="form">
<v-row>
<v-col v-for="item in data.assign_members" cols="12">
<v-text-field
v-model="item.amount"
:label="item.label"
:rules="data.rules.amount"
type="number"
:disabled="!data.assign_btn"
></v-text-field>
</v-col>
</v-row>
</v-form>
</v-card-text>
<v-card-actions v-show="data.assign_btn">
<v-btn color="pink" @click="data.dialog = false">取消</v-btn>
<v-btn color="primary" variant="flat" @click="submitAssignMember">确定</v-btn>
</v-card-actions>
</v-card>
<!-- 分配详情 -->
<v-dialog v-model="data.assign_detail.show" width="80%">
<CardBox title="分配详情" :actions="false" @close="data.assign_detail.show = false">
<template #text>
<v-row justify="center">
<v-col v-for="item in data.assign_members" :key="item.member_id">
<v-alert :title="item.label" border="top" color="primary" variant="tonal" min-height="100">
<p class="pt-4 assign_info">分配数量: {{ item.amount }}</p>
<p class="assign_info">价值积分: {{ item.score }}</p>
</v-alert>
</v-col>
</v-row>
</template>
</CardBox>
</v-dialog>
<Snackbar :show="data.snackbar.show" :msg="data.snackbar.msg" @close="(v: boolean) => (data.snackbar.show = v)" />
</template>
<style scoped>
.assign_info {
font-size: 14px;
}
</style>

98
src/views/assignment/NumberImport.vue

@ -7,6 +7,8 @@ import { VFileUpload } from 'vuetify/labs/VFileUpload'
import Snackbar from '@/components/Snackbar.vue'
import { pageLimit } from '@/common/params'
import { FormatSearchDate } from '@/common/func'
import CardBox from '@/components/CardBox.vue'
import type { CustomResponse } from '@/common/interface'
const data = reactive({
headers: [
@ -26,13 +28,9 @@ const data = reactive({
file: null as any,
dialog: false,
assign_groups: [] as any,
assign_btn: true,
rules: {
phone_num: [(v: any) => !!v || '参数不能为空'],
},
form: { id: 0, assign_count: 0 },
searchForm: { start_time: '', end_time: '' },
groups: [] as any[],
assign_detail: { show: false },
})
const getItems = async (newPage: number = 1) => {
@ -47,7 +45,7 @@ const getItems = async (newPage: number = 1) => {
page: newPage,
size: pageLimit,
}
const response = (await instance.post('/assignment/groupsAssignLogList', params)).data
const response: CustomResponse = (await instance.post('/assignment/groupsAssignLogList', params)).data
if (!response.code) {
data.snackbar.show = true
@ -65,7 +63,7 @@ const getGroups = async (page: number = 1) => {
page: page,
size: 100,
}
const response = (await instance.post('/system/groupList', params)).data
const response: CustomResponse = (await instance.post('/system/groupList', params)).data
if (!response.code) {
data.snackbar.show = true
@ -73,7 +71,11 @@ const getGroups = async (page: number = 1) => {
return
}
data.groups = response.data.data
//
data.assign_groups = []
for (const item of response.data.data) {
data.assign_groups.push({ group_id: item.id, group_name: item.group_name, amount: '', score: 0 })
}
}
//
@ -81,18 +83,6 @@ const assignGroupBox = (item: any) => {
data.form.id = item.id
data.form.assign_count = item.success_num
if (item.group_assign_status) {
data.assign_btn = false
} else {
data.assign_btn = true
}
//
data.assign_groups = []
for (const item of data.groups) {
data.assign_groups.push({ group_id: item.id, group_name: item.group_name, amount: '' })
}
data.dialog = true
}
@ -110,13 +100,13 @@ const groupsAssignDetails = async (item: any) => {
for (const index in data.assign_groups) {
if (item.group_id == data.assign_groups[index].group_id) {
data.assign_groups[index].amount = item.amount
data.assign_groups[index].score = item.score
break
}
}
}
data.dialog = true
data.assign_btn = false
data.assign_detail.show = true
}
const submitUpload = async () => {
@ -137,8 +127,13 @@ const submitUpload = async () => {
}
const submitAssignPhone = async () => {
let params = { id: data.form.id, assign_info: [] as any }
const params = { id: data.form.id, assign_info: [] as any }
for (const item of data.assign_groups) {
if (item.amount == '') {
data.snackbar.show = true
data.snackbar.msg = '请填写分配数量'
return
}
params.assign_info.push({ group_id: item.group_id, amount: parseInt(item.amount) })
}
@ -174,14 +169,14 @@ onMounted(() => {
</v-col>
<v-col cols="12">
<v-card>
<v-card-title>
<v-toolbar class="px-2 pt-3">
<v-row>
<v-col>
<v-col cols="3">
<v-date-input v-model="data.time_range" label="请选择日期" clearable max-width="368" multiple="range" />
</v-col>
<v-btn icon="mdi-magnify" class="mt-4 mr-3" @click="getItems"></v-btn>
<v-btn icon="mdi-magnify" variant="elevated" color="primary" class="mt-5 mr-4" @click="() => getItems()"></v-btn>
</v-row>
</v-card-title>
</v-toolbar>
<v-card-text>
<DataTable
:headers="data.headers"
@ -205,30 +200,39 @@ onMounted(() => {
</v-col>
</v-row>
<!-- 弹框 -->
<v-dialog v-model="data.dialog" width="600">
<v-card>
<v-toolbar>
<v-card-title v-show="data.assign_btn">待分配总数: {{ data.form.assign_count }}</v-card-title>
</v-toolbar>
<v-card-text>
<CardBox :title="`待分配总数: ${data.form.assign_count}`" @submit="submitAssignPhone" @close="data.dialog = false">
<template #text>
<v-row>
<v-col v-for="item in data.assign_groups" cols="12">
<v-text-field
v-model="item.amount"
:label="item.group_name"
:rules="data.rules.phone_num"
:disabled="!data.assign_btn"
type="number"
></v-text-field>
<v-col cols="12" v-for="item in data.assign_groups" :key="item.group_id">
<v-text-field v-model="item.amount" :label="item.group_name" type="number"></v-text-field>
</v-col>
</v-row>
</v-card-text>
<v-card-actions v-show="data.assign_btn">
<v-btn color="pink" @click="data.dialog = false">取消</v-btn>
<v-btn color="primary" variant="flat" @click="submitAssignPhone">确定</v-btn>
</v-card-actions>
</v-card>
</template>
</CardBox>
</v-dialog>
<!-- 分配详情 -->
<v-dialog v-model="data.assign_detail.show" width="80%">
<CardBox title="分配详情" :actions="false" @close="data.assign_detail.show = false">
<template #text>
<v-row justify="center">
<v-col cols="2" v-for="item in data.assign_groups" :key="item.group_id">
<v-alert :title="item.group_name" border="top" color="primary" variant="tonal" min-height="100">
<p class="pt-4 assign_info">分配数量: {{ item.amount }}</p>
<p class="assign_info">价值积分: {{ item.score }}</p>
</v-alert>
</v-col>
</v-row>
</template>
</CardBox>
</v-dialog>
<Snackbar :show="data.snackbar.show" :msg="data.snackbar.msg" @close="(v) => (data.snackbar.show = v)" />
</template>
<style scoped>
.assign_info {
font-size: 14px;
}
</style>

3
src/views/login/LoginView.vue

@ -5,6 +5,7 @@ import Snackbar from '@/components/Snackbar.vue'
import { useManagerStore } from '@/stores/manager'
import { useRouter } from 'vue-router'
import { initNavigation } from '@/router'
import type { CustomResponse } from '@/common/interface'
const title = import.meta.env.VITE_APP_TITLE
const router = useRouter()
@ -16,7 +17,7 @@ const data = reactive({
})
const onSubmit = async () => {
const response = (await instance.post('/login', { username: data.username, password: data.password })).data
const response: CustomResponse = (await instance.post('/login', { username: data.username, password: data.password })).data
if (!response.code) {
data.snackbar.show = true

9
src/views/parameter/PhoneScore.vue

@ -4,6 +4,7 @@ import { onMounted, reactive, ref } from 'vue'
import ConfirmBox from '@/components/ConfirmBox.vue'
import Snackbar from '@/components/Snackbar.vue'
import DataTable from '@/components/DataTable.vue'
import type { CustomResponse } from '@/common/interface'
interface FormData {
id: Number
@ -81,7 +82,7 @@ const data = reactive({
})
const getItems = async () => {
const response = (await instance.post('/parameter/numbervalue/list')).data
const response: CustomResponse = (await instance.post('/parameter/numbervalue/list')).data
if (!response.code) {
data.snackbar.show = true
@ -94,7 +95,7 @@ const getItems = async () => {
}
const getLevel = async () => {
const response = (await instance.post('/parameter/numbervalue/levelList')).data
const response: CustomResponse = (await instance.post('/parameter/numbervalue/levelList')).data
if (!response.code) {
data.snackbar.show = true
@ -149,7 +150,7 @@ const destoryBox = (method: number, item: any) => {
const form = ref()
const confirms = ref()
const onSubmit = async () => {
let params = { id: data.form.id } as FormData
const params = { id: data.form.id } as FormData
if (data.request.method < 2) {
if (!(await form.value.validate()).valid) return
@ -167,7 +168,7 @@ const onSubmit = async () => {
params.score_bonus = parseInt(data.form.score_bonus as string)
}
const response = (await instance.post(data.request.url, params)).data
const response: CustomResponse = (await instance.post(data.request.url, params)).data
data.snackbar.show = true
data.snackbar.msg = response.msg

26
src/views/system/GroupList.vue

@ -5,6 +5,8 @@ import { onMounted, reactive, ref } from 'vue'
import ConfirmBox from '@/components/ConfirmBox.vue'
import Snackbar from '@/components/Snackbar.vue'
import DataTable from '@/components/DataTable.vue'
import CardBox from '@/components/CardBox.vue'
import type { CustomResponse } from '@/common/interface'
interface FormInfo {
id: Number
@ -36,7 +38,7 @@ const getItems = async (newPage: number = 1) => {
page: newPage,
size: pageLimit,
}
const response = (await instance.post('/system/groupList', params)).data
const response: CustomResponse = (await instance.post('/system/groupList', params)).data
if (!response.code) {
data.snackbar.show = true
@ -75,7 +77,7 @@ const destoryBox = (method: number, item: any) => {
const form = ref()
const confirms = ref()
const onSubmit = async () => {
let params = { id: data.form.id } as FormInfo
const params = { id: data.form.id } as FormInfo
if (data.request.method < 2) {
if (!(await form.value.validate()).valid) return
@ -83,7 +85,7 @@ const onSubmit = async () => {
params.group_name = data.form.group_name
}
const response = (await instance.post(data.request.url, params)).data
const response: CustomResponse = (await instance.post(data.request.url, params)).data
data.snackbar.show = true
data.snackbar.msg = response.msg
@ -121,18 +123,18 @@ onMounted(() => {
<!-- 编辑 -->
<v-dialog v-model="data.dialog" width="60%">
<v-card>
<v-card-title>信息</v-card-title>
<v-card-text>
<CardBox
:title="data.request.method == 0 ? '添加' : '编辑'"
:show="data.dialog"
@close="data.dialog = false"
@submit="onSubmit"
>
<template #text>
<v-form ref="form">
<v-text-field label="账号" v-model="data.form.group_name" :rules="data.rules.group_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>
</template>
</CardBox>
</v-dialog>
<!-- 删除 -->

196
src/views/system/ManagerList.vue

@ -5,6 +5,8 @@ import { onMounted, reactive, ref } from 'vue'
import ConfirmBox from '@/components/ConfirmBox.vue'
import Snackbar from '@/components/Snackbar.vue'
import DataTable from '@/components/DataTable.vue'
import CardBox from '@/components/CardBox.vue'
import type { CustomResponse } from '@/common/interface'
interface FormInfo {
id: number
@ -12,6 +14,7 @@ interface FormInfo {
password: string
role_id: number
group_id: number
account_type: number
status: number
}
@ -20,6 +23,7 @@ const data = reactive({
{ title: 'ID', key: 'id' },
{ title: '账号', key: 'username' },
{ title: '角色', key: 'role_name' },
{ title: '类型', key: 'account_type' },
{ title: '所属组', key: 'group_name' },
{ title: '状态', key: 'status' },
{ title: '创建时间', key: 'created_at' },
@ -29,6 +33,12 @@ const data = reactive({
options: {
roles: [] as any[],
groups: [] as any[],
account_type: [
{ title: '请选择', value: 0 },
{ title: '管理员', value: 1 },
{ title: '小组长', value: 2 },
{ title: '组成员', value: 3 },
],
},
form: {} as FormInfo,
rules: {
@ -36,23 +46,24 @@ const data = reactive({
password: [(v: any) => (v.length >= 6 && v.length <= 20) || '密码字符在6-20之间'],
roles: [(v: any) => v > 0 || '请选择角色'],
groups: [(v: any) => v > 0 || '请选择组'],
account_type: [(v: any) => v > 0 || '请选择账号类型'],
},
dialog: false,
confirm: { show: false, msg: '确定要删除此记录吗' },
request: { method: 0, url: '' },
snackbar: { show: false, msg: '' },
page: { current: 1, last_page: 1 },
search: { username: '', group_id: 0 },
})
const getItems = async (page: number = 1) => {
const params = {
username: '',
role_name: '',
group_name: '',
username: data.search.username,
group_id: data.search.group_id,
page: page,
size: pageLimit,
}
const response = (await instance.post('/manager/list', params)).data
const response: CustomResponse = (await instance.post('/manager/list', params)).data
if (!response.code) {
data.snackbar.show = true
@ -71,7 +82,7 @@ const getRoles = async () => {
page: 1,
size: 100,
}
const response = (await instance.post('/role/list', params)).data
const response: CustomResponse = (await instance.post('/role/list', params)).data
if (!response.code) {
data.snackbar.show = true
@ -79,7 +90,7 @@ const getRoles = async () => {
return
}
data.options.roles = [{ title: '请选择', value: 0 }]
data.options.roles = [{ title: '全部', value: 0 }]
for (const item of response.data.data) {
data.options.roles.push({
title: item.name,
@ -95,7 +106,7 @@ const getGroups = async () => {
page: 1,
size: 100,
}
const response = (await instance.post('/system/groupList', params)).data
const response: CustomResponse = (await instance.post('/system/groupList', params)).data
if (!response.code) {
data.snackbar.show = true
@ -103,7 +114,7 @@ const getGroups = async () => {
return
}
data.options.groups = [{ title: '请选择', value: 0 }]
data.options.groups = [{ title: '全部', value: 0 }]
for (const item of response.data.data) {
data.options.groups.push({
title: item.group_name,
@ -114,7 +125,7 @@ const getGroups = async () => {
const insertBox = (method: number) => {
data.dialog = true
data.form = { status: 1, role_id: 0, group_id: 0 } as FormInfo
data.form = { status: 1, role_id: 0, group_id: 0, account_type: 0 } as FormInfo
data.request.method = method
data.request.url = '/manager/add'
@ -140,7 +151,7 @@ const destoryBox = (method: number, item: any) => {
const form = ref()
const confirms = ref()
const onSubmit = async () => {
let params = { id: data.form.id } as FormInfo
const params = { id: data.form.id } as FormInfo
if (data.request.method < 2) {
if (!(await form.value.validate()).valid) return
@ -149,9 +160,10 @@ const onSubmit = async () => {
params.role_id = data.form.role_id
params.group_id = data.form.group_id
params.status = data.form.status
params.account_type = data.form.account_type
}
const response = (await instance.post(data.request.url, params)).data
const response: CustomResponse = (await instance.post(data.request.url, params)).data
data.snackbar.show = true
data.snackbar.msg = response.msg
@ -171,71 +183,109 @@ onMounted(() => {
</script>
<template>
<v-card>
<v-card-title>
<v-row>
<v-col cols="12" class="pb-0">
<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="getItems"
>
<template #item.status="{ value }">
<v-btn v-if="value === 1" color="green" variant="tonal" size="small">在职</v-btn>
<v-btn v-if="value === 2" color="pink" variant="tonal" size="small">离职</v-btn>
</template>
<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-col>
<v-col cols="12">
<v-card>
<v-card-title>信息</v-card-title>
<v-toolbar class="px-2 pt-3">
<v-row>
<v-col cols="3">
<v-text-field v-model="data.search.username" label="账号"></v-text-field>
</v-col>
<v-col cols="3">
<v-select label="所属组" v-model="data.search.group_id" :items="data.options.groups"></v-select>
</v-col>
<v-btn icon="mdi-magnify" variant="elevated" color="primary" class="mt-5 mr-4" @click="() => getItems()"></v-btn>
</v-row>
</v-toolbar>
<v-card-text>
<v-form ref="form">
<v-text-field
label="账号"
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-select v-model="data.form.role_id" :items="data.options.roles" :rules="data.rules.roles"></v-select>
<v-select v-model="data.form.group_id" :items="data.options.groups" :rules="data.rules.groups"></v-select>
<v-switch
:label="data.form.status == 1 ? '在职' : '离职'"
v-model="data.form.status"
:true-value="1"
:false-value="2"
inset
color="primary"
></v-switch>
</v-form>
<DataTable
:headers="data.headers"
:items="data.desserts"
:page="data.page.current"
:size="data.page.last_page"
@get-items="getItems"
>
<template #item.account_type="{ value }">
<span v-for="v in data.options.account_type" :key="v.value">
<v-btn v-if="v.value == value" color="warning" variant="tonal" size="small">{{ v.title }}</v-btn>
</span>
</template>
<template #item.group_name="{ value }">
<span v-if="value == ''"> - </span>
<v-btn v-else color="pink" variant="tonal" size="small">{{ value }}</v-btn>
</template>
<template #item.status="{ value }">
<v-btn v-if="value === 1" color="green" variant="tonal" size="small">在职</v-btn>
<v-btn v-if="value === 2" color="pink" variant="tonal" size="small">离职</v-btn>
</template>
<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-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-dialog v-model="data.dialog" width="800">
<CardBox :title="data.request.method == 0 ? '添加' : '编辑'" @submit="onSubmit" @close="(v) => (data.dialog = v)">
<template #text>
<v-form ref="form">
<v-text-field
label="账号"
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-select
label="角色"
v-model="data.form.role_id"
:items="data.options.roles"
:rules="data.rules.roles"
></v-select>
<v-select
label="账号类型"
v-model="data.form.account_type"
:items="data.options.account_type"
:rules="data.rules.account_type"
></v-select>
<v-select
label="所属组"
v-model="data.form.group_id"
:items="data.options.groups"
:rules="data.rules.groups"
></v-select>
<v-switch
:label="data.form.status == 1 ? '在职' : '离职'"
v-model="data.form.status"
:true-value="1"
:false-value="2"
inset
color="primary"
></v-switch>
</v-form>
</template>
</CardBox>
</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" @close="(v) => (data.snackbar.show = v)" />
</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" @close="(v) => (data.snackbar.show = v)" />
</v-card>
</v-col>
</v-row>
</template>

28
src/views/system/RoleList.vue

@ -6,6 +6,8 @@ import ConfirmBox from '@/components/ConfirmBox.vue'
import Snackbar from '@/components/Snackbar.vue'
import DataTable from '@/components/DataTable.vue'
import PermissionSelect from '@/components/PermissionSelect.vue'
import CardBox from '@/components/CardBox.vue'
import type { CustomResponse } from '@/common/interface'
interface FormInfo {
id: Number
@ -47,7 +49,7 @@ const getItems = async (newPage: number = 1) => {
page: newPage,
size: pageLimit,
}
const response = (await instance.post('/role/list', params)).data
const response: CustomResponse = (await instance.post('/role/list', params)).data
if (!response.code) {
data.snackbar.show = true
@ -61,7 +63,7 @@ const getItems = async (newPage: number = 1) => {
//
const getPermissionsTree = async () => {
const response = (await instance.post('/permissionsTree')).data
const response: CustomResponse = (await instance.post('/permissionsTree')).data
if (!response.code) {
data.snackbar.show = true
@ -74,7 +76,7 @@ const getPermissionsTree = async () => {
//
const getPermissionsList = async () => {
const response = (await instance.post('/permissionsList')).data
const response: CustomResponse = (await instance.post('/permissionsList')).data
if (!response.code) {
data.snackbar.show = true
@ -117,7 +119,7 @@ const destoryBox = (method: number, item: any) => {
const form = ref()
const confirms = ref()
const onSubmit = async () => {
let params = { id: data.form.id } as FormInfo
const params = { id: data.form.id } as FormInfo
if (data.request.method < 2) {
if (!(await form.value.validate()).valid) return
@ -126,7 +128,7 @@ const onSubmit = async () => {
params.permissions = data.selectAuth.join(',')
}
const response = (await instance.post(data.request.url, params)).data
const response: CustomResponse = (await instance.post(data.request.url, params)).data
data.snackbar.show = true
data.snackbar.msg = response.msg
@ -170,14 +172,12 @@ onMounted(() => {
<!-- 编辑 -->
<v-dialog v-model="data.dialog" width="90%">
<v-card>
<v-card-title>信息</v-card-title>
<v-card-text>
<CardBox :title="data.request.method == 0 ? '添加' : '编辑'" @submit="onSubmit" @close="data.dialog = false">
<template #text>
<v-form ref="form">
<v-text-field label="账号" v-model="data.form.name" :rules="data.rules.name"></v-text-field>
<v-row>
<v-col cols="12" v-for="(item, index) in data.permissionTree">
<v-col cols="12" v-for="(item, index) in data.permissionTree" :key="index">
<v-alert>
<v-checkbox
v-model="data.selectAuth"
@ -203,12 +203,8 @@ onMounted(() => {
</v-col>
</v-row>
</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>
</template>
</CardBox>
</v-dialog>
<!-- 删除 -->

21
tsconfig.app.json

@ -1,15 +1,12 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"module": "es2020",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"strictNullChecks": true
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"paths": {
"@/*": ["./src/*"]
}
}
}

1
tsconfig.node.json

@ -8,7 +8,6 @@
"playwright.config.*"
],
"compilerOptions": {
"composite": true,
"noEmit": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",

Loading…
Cancel
Save