首页的统计图和票据的状态分标签展示

This commit is contained in:
2024-04-17 11:23:49 +08:00
parent b840ca4974
commit f070b02da9
46 changed files with 7371 additions and 6675 deletions

View File

@ -13,8 +13,8 @@ export default mergeConfig(
},
proxy: {
'/api': {
target: 'http://59.110.238.182:8081',
// target: 'http://192.168.3.158:8081',
target: 'http://106.53.179.133:8081',
// target: 'http://192.168.243.246:8081',
// target: 'http://localhost:5173',
changeOrigin: true,
},

View File

@ -3,10 +3,9 @@
<head>
<meta charset="UTF-8" />
<link rel="shortcut icon" type="image/x-icon"
href="https://unpkg.byted-static.com/latest/byted/arco-config/assets/favicon.ico">
<link rel="icon" href="./src/image/票据服务.png" type="image/x-icon">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>中山学院学习平台</title>
<title>票据管理系统</title>
</head>
<body>

View File

@ -13,10 +13,7 @@
"type:check": "vue-tsc --noEmit --skipLibCheck",
"lint-staged": "npx lint-staged",
"prepare": "husky install",
"start": "npm run dev"
},
"lint-staged": {
"*.{js,ts,jsx,tsx}": [
@ -40,6 +37,8 @@
"axios": "^0.24.0",
"dayjs": "^1.11.5",
"echarts": "^5.4.0",
"exceljs": "^4.4.0",
"file-saver": "^2.0.5",
"lodash": "^4.17.21",
"mitt": "^3.0.0",
"nprogress": "^0.2.0",
@ -50,12 +49,14 @@
"vue": "^3.2.40",
"vue-echarts": "^6.2.3",
"vue-i18n": "^9.2.2",
"vue-router": "^4.0.14"
"vue-router": "^4.0.14",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@arco-plugins/vite-vue": "^1.4.5",
"@commitlint/cli": "^17.1.2",
"@commitlint/config-conventional": "^17.1.0",
"@types/file-saver": "^2.0.7",
"@types/lodash": "^4.14.186",
"@types/mockjs": "^1.0.7",
"@types/nprogress": "^0.2.0",

View File

@ -75,10 +75,12 @@ axios.interceptors.response.use(
const { response } = error;
console.log('error', error);
if (response.status === '401') {
if (response.status === 401) {
router.push({
name: 'login',
});
}
else {
Message.error({

View File

@ -18,8 +18,13 @@ export interface RoleListRecord extends RoleRecord {
}
// 查询所有的角色列表、
export function queryRoleList() {
return axios.get('/api/rest/role');
export function queryRoleList(data: any) {
// return axios.get('/api/rest/role',data);
return axios({
url: '/api/rest/role', // 路径
method: 'get',
params: data, // 参数
})
}
// 切换启用状态

View File

@ -7,7 +7,7 @@ export interface TicketCreateRecord {
type: string;
contactEmail: string;
companyName: string;
attachId: string;
attachId: [];
auditorId: string;
submit: boolean;
userId: undefined;
@ -15,6 +15,9 @@ export interface TicketCreateRecord {
}
export interface TicketRecord extends TicketCreateRecord {
auditor: any;
createTime(createTime: any): unknown;
deptName: any;
value: any;
id: undefined;
status: string
@ -83,3 +86,8 @@ export function home(data: any){
params: data, // 参数
});
}
// 首页统计图
export function chart(){
return axios.get('/api/rest/bill/trend')
}

View File

@ -29,6 +29,8 @@ export interface PasswordReSetModel {
// 添加用户数据
export interface CreateRecord {
value: any;
code: any;
username: string;
nickName: string;
@ -107,10 +109,15 @@ export function resetPassword(data: PasswordReSetModel) {
}
// 注册用户
export function create(data: CreateRecord) {
export function register(data: CreateRecord) {
return axios.post('/api/rest/user/register', data);
}
// 新建用户
export function create(data: CreateRecord) {
return axios.post('/api/rest/user', data);
}
// 模糊查询用户列表
export function queryUserList(params: any) {
return axios({
@ -158,6 +165,11 @@ export function deptAudit(id: string,roleId:string){
});
}
// 获取验证码
export function code(data: string){
return axios.get(`/api/rest/user/send-email?email=${data}`);
}
export function switchRole(roleId: number) {
return axios.patch<UserState>(`/api/user/self/switch-role/${roleId}`);
}

View File

@ -1,16 +1,16 @@
<template>
<a-layout-footer class="footer">Arco Pro</a-layout-footer>
<a-layout-footer class="footer"></a-layout-footer>
</template>
<script lang="ts" setup></script>
<style lang="less" scoped>
.footer {
display: flex;
align-items: center;
justify-content: center;
height: 40px;
color: var(--color-text-2);
text-align: center;
}
.footer {
display: flex;
align-items: center;
justify-content: center;
height: 40px;
color: var(--color-text-2);
text-align: center;
}
</style>

View File

@ -2,10 +2,7 @@
<div class="navbar">
<div class="left-side">
<a-space>
<img
alt="logo"
src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/dfdba5317c0c20ce20e64fac803d52bc.svg~tplv-49unhts6dw-image.image"
/>
<img style="height: 24px" alt="logo" src="../../image/票据服务.png" />
<a-typography-title
:style="{ margin: 0, fontSize: '18px' }"
:heading="5"
@ -112,6 +109,9 @@
<li>
<a-dropdown trigger="click">
<a-button style="margin-right: 20px" status="normal">{{
userStore.nickName
}}</a-button>
<a-avatar
:size="32"
:style="{ marginRight: '8px', cursor: 'pointer' }"

View File

@ -8,7 +8,7 @@
"menuCollapse": false,
"footer": true,
"themeColor": "#165DFF",
"menuWidth": 220,
"menuWidth": 200,
"globalSettings": false,
"device": "desktop",
"tabBar": false,

BIN
src/image/票据服务.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -9,7 +9,7 @@ const SYSTEM: AppRouteRecordRaw = {
locale: 'menu.system',
icon: 'icon-computer',
requiresAuth: true,
order: 1,
order: 2,
},
children: [
// {

View File

@ -9,7 +9,7 @@ const TICKET: AppRouteRecordRaw = {
locale: 'menu.ticket',
icon: 'icon-folder-add',
requiresAuth: true,
order: 4,
order: 1,
},
children: [
{

View File

@ -9,7 +9,7 @@ const USER: AppRouteRecordRaw = {
locale: 'menu.user',
icon: 'icon-user',
requiresAuth: true,
order: 7,
order: 3,
},
children: [
// {

View File

@ -27,8 +27,8 @@ const useRoleStore = defineStore('role', {
},
actions: {
// 获取所有的角色列表
async getRoleList() {
return queryRoleList();
async getRoleList(params: any) {
return queryRoleList(params);
},
async enabledRole(id: string) {

View File

@ -11,7 +11,8 @@ import {
audit,
uploadFile,
attachment,
home
home,
chart,
} from '@/api/ticket';
import { ticketStore } from './type';
@ -61,7 +62,7 @@ const useTicketStore = defineStore('ticket', {
return update(data);
},
async uploadFileTicket(file: any) {
async uploadFile(file: any) {
return uploadFile(file);
},
@ -75,7 +76,11 @@ const useTicketStore = defineStore('ticket', {
async getHome(data: any){
return home(data);
}
},
async getChart(){
return chart();
},
},
});

View File

@ -10,10 +10,12 @@ import {
enabled,
remove,
create,
register,
UserRecord,
update,
userDetail,
deptAudit
deptAudit,
code
} from '@/api/user';
import { setToken, clearToken } from '@/utils/auth';
import { removeRouteListener } from '@/utils/route-listener';
@ -92,11 +94,16 @@ const useUserStore = defineStore('user', {
return remove(id);
},
// Register user
// Create user
async createUser(data: UserRecord) {
return create(data);
},
// register user
async registerUser(data: UserRecord) {
return register(data);
},
// Update user
async updateUser(data: UserRecord) {
return update(data);
@ -110,6 +117,10 @@ const useUserStore = defineStore('user', {
return deptAudit(deptId,roleId)
},
async getCode(params: string){
return code(params)
},
logoutCallBack() {
const appStore = useAppStore();
this.resetInfo();

View File

@ -26,6 +26,8 @@ export interface PostData {
export interface Pagination {
page: number;
size: number;
current: number;
total: null | number;
}
export type TimeRanger = [string, string];

69
src/utils/excel.ts Normal file
View File

@ -0,0 +1,69 @@
// 新建 @/utils/excel.ts
import saveAs from 'file-saver'; // https://www.npmjs.com/package/file-saver
import ExcelJS from 'exceljs'; // https://github.com/exceljs/exceljs/blob/master/README_zh.md
import dayjs from 'dayjs'; // https://dayjs.fenxianglu.cn/
import * as XLSX from 'xlsx'; // https://www.npmjs.com/package/xlsx
import { Message } from '@arco-design/web-vue'; // https://arco.design/vue/component/message
import { FileItem } from '@arco-design/web-vue/es/upload/interfaces'; // arco类型
export interface DownloadExcelPrams {
columns: { title: string, key: string }[];
rows: object[];
name: string
}
// 导出下载文件
export function downloadExcel({ columns, rows, name = '未命名文件' }: DownloadExcelPrams) {
const workbook = new ExcelJS.Workbook();
workbook.creator = 'Start-front';
workbook.lastModifiedBy = 'Start-front';
workbook.created = new Date(1985, 8, 30);
workbook.modified = new Date();
workbook.lastPrinted = new Date(2016, 9, 27);
// 将工作簿添加一个sheet页sheet1
const sheet1 = workbook.addWorksheet(name);
// 表头数据添加
sheet1.columns = columns.map(item => ({
header: item.title,
key: item.key,
width: 20
}));
// 表格内容添加
rows.map(item => sheet1.addRow(item));
workbook.xlsx.writeBuffer().then(buffer => {
saveAs(
new Blob([buffer], { type: 'application/octet-stream' }),
`${name}.xlsx`
);
});
};
// 读取文件为json格式
export function readExcle(fileItem:FileItem) {
console.log('读取文件...',fileItem);
return new Promise((resove,reject)=>{
try {
let workbook:XLSX.Sheet;
const reader = new FileReader();
reader.readAsBinaryString(fileItem.file as File); // 发起异步请求
reader.onload = function(ev){
const data = ev.target?.result;
workbook = XLSX.read(data, {type: 'binary'});
const sheetNames = workbook.SheetNames; // 工作表名称集合
sheetNames.forEach((name:string) => {
const worksheet = workbook.Sheets[name]; // 只能通过工作表名称来获取指定工作表
const jsonres = XLSX.utils.sheet_to_json(worksheet);
resove(jsonres)
});
} // onload
} catch (error) {
Message.error('读取失败,请选择正确文件');
reject(error);
}
})
}

View File

@ -1,114 +1,122 @@
<template>
<a-grid :cols="24" :row-gap="16" class="panel">
<a-grid-item
class="panel-col"
:span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 12, xxl: 6 }"
>
<a-space>
<a-avatar :size="54" class="col-avatar">
<img
alt="avatar"
src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/288b89194e657603ff40db39e8072640.svg~tplv-49unhts6dw-image.image"
/>
</a-avatar>
<a-statistic
:title="$t('workplace.pass')"
:value="formData.pass || 0"
:value-from="0"
animation
show-group-separator
>
<template #suffix>
<span class="unit">{{ $t('workplace.pecs') }}</span>
</template>
</a-statistic>
</a-space>
</a-grid-item>
<a-grid-item
class="panel-col"
:span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 12, xxl: 6 }"
>
<a-space>
<a-avatar :size="54" class="col-avatar">
<img
alt="avatar"
src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/fdc66b07224cdf18843c6076c2587eb5.svg~tplv-49unhts6dw-image.image"
/>
</a-avatar>
<a-statistic
:title="$t('workplace.notPass')"
:value="formData.notPass || 0"
:value-from="0"
animation
show-group-separator
>
<template #suffix>
<span class="unit">{{ $t('workplace.pecs') }}</span>
</template>
</a-statistic>
</a-space>
</a-grid-item>
<a-grid-item
class="panel-col"
:span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 12, xxl: 6 }"
>
<a-space>
<a-avatar :size="54" class="col-avatar">
<img
alt="avatar"
src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/77d74c9a245adeae1ec7fb5d4539738d.svg~tplv-49unhts6dw-image.image"
/>
</a-avatar>
<a-statistic
:title="$t('workplace.notAudit')"
:value="formData.notAudit || 0"
:value-from="0"
animation
show-group-separator
>
<template #suffix>
<span class="unit">{{ $t('workplace.pecs') }}</span>
</template>
</a-statistic>
</a-space>
</a-grid-item>
<a-grid-item
v-if="userStore.permissions !== 'auditor'"
class="panel-col"
:span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 12, xxl: 6 }"
style="border-right: none"
>
<a-space>
<a-avatar :size="54" class="col-avatar">
<img
alt="avatar"
src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/c8b36e26d2b9bb5dbf9b74dd6d7345af.svg~tplv-49unhts6dw-image.image"
/>
</a-avatar>
<a-statistic
v-model="formData.notFiled"
:title="$t('workplace.notFiled')"
:value="formData.notFiled || 0"
:value-from="0"
animation
show-group-separator
>
<template #suffix>
<span class="unit">{{ $t('workplace.pecs') }}</span>
</template>
</a-statistic>
</a-space>
</a-grid-item>
<a-grid-item :span="24">
<a-divider class="panel-border" />
</a-grid-item>
</a-grid>
<div style="width: 100%">
<a-grid :cols="24" :row-gap="16" class="panel">
<a-grid-item
class="panel-col"
:span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 12, xxl: 6 }"
>
<a-space>
<a-avatar :size="54" class="col-avatar">
<img
alt="avatar"
src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/288b89194e657603ff40db39e8072640.svg~tplv-49unhts6dw-image.image"
/>
</a-avatar>
<a-statistic
:title="$t('workplace.pass')"
:value="formData.pass || 0"
:value-from="0"
animation
show-group-separator
>
<template #suffix>
<span class="unit">{{ $t('workplace.pecs') }}</span>
</template>
</a-statistic>
</a-space>
</a-grid-item>
<a-grid-item
class="panel-col"
:span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 12, xxl: 6 }"
>
<a-space>
<a-avatar :size="54" class="col-avatar">
<img
alt="avatar"
src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/fdc66b07224cdf18843c6076c2587eb5.svg~tplv-49unhts6dw-image.image"
/>
</a-avatar>
<a-statistic
:title="$t('workplace.notPass')"
:value="formData.notPass || 0"
:value-from="0"
animation
show-group-separator
>
<template #suffix>
<span class="unit">{{ $t('workplace.pecs') }}</span>
</template>
</a-statistic>
</a-space>
</a-grid-item>
<a-grid-item
class="panel-col"
:span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 12, xxl: 6 }"
>
<a-space>
<a-avatar :size="54" class="col-avatar">
<img
alt="avatar"
src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/77d74c9a245adeae1ec7fb5d4539738d.svg~tplv-49unhts6dw-image.image"
/>
</a-avatar>
<a-statistic
:title="$t('workplace.notAudit')"
:value="formData.notAudit || 0"
:value-from="0"
animation
show-group-separator
>
<template #suffix>
<span class="unit">{{ $t('workplace.pecs') }}</span>
</template>
</a-statistic>
</a-space>
</a-grid-item>
<a-grid-item
v-if="userStore.permissions !== 'auditor'"
class="panel-col"
:span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 12, xxl: 6 }"
style="border-right: none"
>
<a-space>
<a-avatar :size="54" class="col-avatar">
<img
alt="avatar"
src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/c8b36e26d2b9bb5dbf9b74dd6d7345af.svg~tplv-49unhts6dw-image.image"
/>
</a-avatar>
<a-statistic
v-model="formData.notFiled"
:title="$t('workplace.notFiled')"
:value="formData.notFiled || 0"
:value-from="0"
animation
show-group-separator
>
<template #suffix>
<span class="unit">{{ $t('workplace.pecs') }}</span>
</template>
</a-statistic>
</a-space>
</a-grid-item>
<a-grid-item :span="24">
<a-divider class="panel-border" />
</a-grid-item>
</a-grid>
<a-card :bordered="false" v-if="userStore.permissions === 'admin'">
<a-space id="TicketEcharts" style="width: 98%; height: 400px"></a-space>
</a-card>
</div>
</template>
<script lang="ts" setup>
import { useI18n } from 'vue-i18n';
import { useTicketStore, useUserStore } from '@/store';
import { ref } from 'vue';
import { onMounted, ref } from 'vue';
import * as echarts from 'echarts';
const { t } = useI18n();
const ticketStore = useTicketStore();
const userStore = useUserStore();
const formData = ref({
@ -118,6 +126,72 @@ const formData = ref({
pass: undefined,
});
const getEchartData = () => {
const res = ticketStore.getChart();
console.log('chart', res);
};
onMounted(async () => {
const res = await ticketStore.getChart();
console.log('chart', res.data);
type EChartsOption = echarts.EChartsOption;
const chartDom = document.getElementById('TicketEcharts');
const myChart = echarts.init(chartDom);
const option = {
title: {
text: t('workplace.chart'),
},
tooltip: {
trigger: 'axis',
},
legend: {
data: [
t('workplace.pass'),
t('workplace.notPass'),
t('workplace.notAudit'),
],
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
toolbox: {
feature: {
saveAsImage: {},
},
},
xAxis: {
type: 'category',
boundaryGap: false,
data: res.data.dates,
},
yAxis: {
type: 'value',
},
series: [
{
name: t('workplace.pass'),
type: 'line',
data: res.data.status2Counts,
},
{
name: t('workplace.notPass'),
type: 'line',
data: res.data.status3Counts,
},
{
name: t('workplace.notAudit'),
type: 'line',
data: res.data.status1Counts,
},
],
};
myChart.setOption(option);
});
const getHomeData = async (params: {
auditorId: number | undefined;
userId: number | string | undefined;

View File

@ -40,4 +40,6 @@ export default {
'workplace.notPass': 'notPass',
'workplace.notAudit': 'notAudit',
'workplace.notFiled': 'notFiled',
'workplace.total': 'total',
'workplace.chart':' Ticket Chart'
};

View File

@ -38,4 +38,6 @@ export default {
'workplace.notPass': '审核不通过',
'workplace.notAudit': '待审核',
'workplace.notFiled': '待提交',
'workplace.total': '票据总数',
'workplace.chart':'票据分析图'
};

View File

@ -94,6 +94,40 @@
:placeholder="$t('user.info.username.placeholder')"
/>
</a-form-item>
<a-form-item
field="email"
:label="$t('user.info.email')"
:rules="[
{
required: true,
type: 'email',
message: t('user.info.email.required'),
},
]"
:validate-trigger="['change', 'input']"
>
<a-input
v-model="formData.email"
:placeholder="$t('user.info.emailCode.placeholder')"
/>
</a-form-item>
<a-form-item
field="phone"
:label="$t('user.info.phone')"
:rules="[
{ required: true, message: t('user.info.phone.required') },
{ match: /^1[3-9]\d{9}$/, message: t('user.info.phone.format') },
]"
:validate-trigger="['change', 'input']"
>
<a-input
v-model="formData.phone"
:placeholder="$t('user.info.phone.placeholder')"
/>
</a-form-item>
<a-form-item
field="password"
:label="$t('user.info.password')"
@ -111,43 +145,37 @@
:placeholder="$t('user.info.nickName.placeholder')"
/>
</a-form-item>
<a-form-item
field="phone"
:label="$t('user.info.phone')"
:rules="[
{ required: true, message: t('user.info.phone.required') },
{ match: /^1[3-9]\d{9}$/, message: t('user.info.phone.format') },
]"
:validate-trigger="['change', 'input']"
>
<a-input
v-model="formData.phone"
:placeholder="$t('user.info.phone.placeholder')"
/>
</a-form-item>
<a-form-item
field="email"
:label="$t('user.info.email')"
:rules="[
{
required: true,
type: 'email',
message: t('user.info.email.required'),
},
]"
:validate-trigger="['change', 'input']"
>
<a-input
v-model="formData.email"
:placeholder="$t('user.info.email.placeholder')"
/>
</a-form-item>
<a-form-item field="address" :label="$t('user.info.address')">
<a-input
v-model="formData.address"
:placeholder="$t('user.info.address.placeholder')"
/>
</a-form-item>
<a-form-item
field="code"
:label="$t('user.info.code')"
:rules="[
{
required: true,
message: t('user.info.code.required'),
},
]"
:validate-trigger="['change', 'input']"
>
<a-input
v-model="formData.code"
:placeholder="$t('user.info.code.placeholder')"
/>
<a-button
type="primary"
style="margin-left: 50px; margin-right: 50px"
@click="getCodeData"
:disabled="flag"
>发送验证码
<div v-if="flag">({{ totalCount }}s)</div>
</a-button>
</a-form-item>
</a-form>
</a-modal>
</template>
@ -179,19 +207,22 @@ const formData = ref<CreateRecord>({
nickName: '',
password: '',
phone: '',
email: '',
email: '@qq.com',
enabled: 'true',
address: '',
deptId: 235,
roleId: undefined,
permissionIds: [],
authorities: [],
code: '',
});
const flag = ref(false);
const totalCount = ref(60);
const deptOptions = ref();
const getDeptData = async () => {
const res = await deptList();
deptOptions.value = res.data;
deptOptions.value = res.data.records;
};
const loginConfig = useStorage('login-config', {
@ -207,6 +238,7 @@ const userInfo = reactive({
password: loginConfig.value.password,
});
//
const handleSubmit = async ({
//
errors,
@ -253,23 +285,53 @@ const handleSubmit = async ({
}
}
};
//
const setRememberPassword = (value: boolean) => {
loginConfig.value.rememberPassword = value;
};
//
const handleClick = () => {
setVisible(true);
};
//
const handleCancel = async () => {
userCreateRef.value?.resetFields();
setVisible(false);
};
//
const getCodeData = async () => {
if (formData.value.email === '') {
Message.error({
content: t('user.info.email.required'),
duration: 5 * 1000,
});
} else {
const res = await userStore.getCode(formData.value.email);
if (res.status === 200) {
flag.value = true;
setInterval(() => {
if (totalCount.value !== 0) {
totalCount.value = totalCount.value * 1 - 1;
}
}, 1000);
setTimeout(() => {
flag.value = false;
totalCount.value = 60;
}, 60000);
}
}
};
//
const handleOk = async () => {
const valid = await userCreateRef.value?.validate();
if (!valid) {
const res = await userStore.createUser(formData.value);
// formData.value.username = formData.value.email;
const res = await userStore.registerUser(formData.value);
if (res.status === 200) {
Message.success({
content: t('create.sucess'),

View File

@ -1,81 +1,73 @@
<template>
<div class="container">
<div class="logo">
<img
alt="logo"
src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/dfdba5317c0c20ce20e64fac803d52bc.svg~tplv-49unhts6dw-image.image"
/>
<div class="logo-text">Hello World!</div>
<div class="logo-text">Hello Ticket!</div>
</div>
<LoginBanner />
<div class="content">
<div class="content-inner">
<LoginForm />
</div>
<div class="footer">
<Footer />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import Footer from '@/components/footer/index.vue';
import LoginBanner from './components/banner.vue';
import LoginForm from './components/login-form.vue';
import LoginBanner from './components/banner.vue';
import LoginForm from './components/login-form.vue';
</script>
<style lang="less" scoped>
.container {
.container {
display: flex;
height: 100vh;
.banner {
width: 550px;
background: linear-gradient(163.85deg, #1d2129 0%, #00308f 100%);
}
.content {
position: relative;
display: flex;
height: 100vh;
.banner {
width: 550px;
background: linear-gradient(163.85deg, #1d2129 0%, #00308f 100%);
}
.content {
position: relative;
display: flex;
flex: 1;
align-items: center;
justify-content: center;
padding-bottom: 40px;
}
.footer {
position: absolute;
right: 0;
bottom: 0;
width: 100%;
}
}
.logo {
position: fixed;
top: 24px;
left: 22px;
z-index: 1;
display: inline-flex;
flex: 1;
align-items: center;
&-text {
margin-right: 4px;
margin-left: 4px;
color: var(--color-fill-1);
font-size: 20px;
}
justify-content: center;
padding-bottom: 40px;
}
.footer {
position: absolute;
right: 0;
bottom: 0;
width: 100%;
}
}
.logo {
position: fixed;
top: 24px;
left: 22px;
z-index: 1;
display: inline-flex;
align-items: center;
&-text {
margin-right: 4px;
margin-left: 4px;
color: var(--color-fill-1);
font-size: 20px;
}
}
</style>
<style lang="less" scoped>
// responsive
@media (max-width: @screen-lg) {
.container {
.banner {
width: 25%;
}
// responsive
@media (max-width: @screen-lg) {
.container {
.banner {
width: 25%;
}
}
}
</style>

View File

@ -1,11 +1,11 @@
export default {
'login.form.title': 'ZSC Learning Platform',
'login.form.userName.errMsg': 'Username cannot be empty',
'login.form.userName.errMsg': 'Username cannot be empty(Email)',
'login.form.password.errMsg': 'Password cannot be empty',
'login.form.login.errMsg': 'Login error, refresh and try again',
'login.form.login.success': 'welcome to use',
'login.form.userName.placeholder': 'Username: admin',
'login.form.password.placeholder': 'Password: admin',
'login.form.userName.placeholder': 'Username(Email)',
'login.form.password.placeholder': 'Password',
'login.form.rememberPassword': 'Remember password',
'login.form.forgetPassword': 'Forgot password',
'login.form.login': 'login',

View File

@ -1,6 +1,6 @@
export default {
'login.form.title': '中山学院学习平台',
'login.form.userName.errMsg': '用户名不能为空',
'login.form.title': '票据管理系统',
'login.form.userName.errMsg': '账号不能为空',
'login.form.password.errMsg': '密码不能为空',
'login.form.login.errMsg': '登录出错,请刷新重试',
'login.form.login.success': '欢迎使用',
@ -10,11 +10,15 @@ export default {
'login.form.forgetPassword': '忘记密码',
'login.form.login': '登录',
'login.form.register': '注册账号',
'login.banner.slogan1': '开箱即用的高质量模板',
'login.banner.subSlogan1': '丰富的的页面模板,覆盖大多数典型业务场景',
'login.banner.slogan2': '内置了常见问题的解决方案',
'login.banner.subSlogan2': '国际化,路由配置,状态管理应有尽有',
'login.banner.slogan3': '接入可视化增强工具AUX',
'login.banner.subSlogan3': '实现灵活的区块式开发',
'create.user': '注册用户'
'login.banner.slogan1': '数据查询',
'login.banner.subSlogan1': '用户可以通过关键词、条件组合等方式进行快速查询和检索',
'login.banner.slogan2': '数据存储',
'login.banner.subSlogan2': '系统具备强大的数据存储功能,能够将海量数据存储在数据库中',
'login.banner.slogan3': '数据输出',
'login.banner.subSlogan3': '系统可以将查询和分析结果以表格和图表导出',
'create.user': '注册用户',
'user.info.emailCode.placeholder':'请输入Email(用于获取验证码)',
'user.info.email.required':'Email不能为空',
};

View File

@ -1,25 +1,26 @@
<template>
<a-button v-if="props.isCreate" type="primary" @click="handleClick">
<template #icon><icon-plus /></template>
{{ modalTitle }}
{{ t('create') }}
</a-button>
<a-button
v-if="!props.isCreate"
type="outline"
size="small"
:style="{ marginRight: '10px' }"
:style="{ marginRight: '10px', padding: '7px' }"
@click="handleClick"
>
{{ modalTitle }}
<template #icon><icon-edit /></template>
{{ t('edit') }}
</a-button>
<a-modal
width="600px"
width="700px"
:visible="visible"
@ok="handleSubmit"
@cancel="handleCancel"
>
<template #title>{{ modalTitle }}</template>
<a-form ref="createEditRef" :model="formData" :style="{ width: '500px' }">
<a-form ref="createEditRef" :model="formData" :style="{ width: '650px' }">
<a-form-item
field="name"
:label="$t('dept.info.name')"
@ -35,6 +36,7 @@
<a-textarea
v-model="formData.remark"
:placeholder="$t('dept.info.remark.placeholder')"
style="height: 100px"
/>
</a-form-item>
</a-form>
@ -58,7 +60,7 @@ const props = defineProps({
});
const { t } = useI18n();
const modalTitle = computed(() => {
return props.isCreate ? t('create') : t('edit');
return props.isCreate ? t('createDept') : t('editDept');
});
const { visible, setVisible } = useVisible(false);
const deptStore = useDeptStore();

View File

@ -1,13 +1,13 @@
<template>
<div class="container">
<Breadcrumb :items="['menu.system', 'menu.system.dept']" />
<a-card class="general-card" :title="$t('menu.list.searchTable')">
<a-card class="general-card" title=" ">
<a-row>
<a-col :flex="1">
<a-form
:model="formModel"
:label-col-props="{ span: 6 }"
:wrapper-col-props="{ span: 18 }"
:wrapper-col-props="{ span: 12 }"
label-align="right"
>
<a-row :gutter="16">
@ -118,6 +118,8 @@
:data="renderData"
:bordered="false"
:size="size"
:pagination="false"
style="margin-bottom: 40px"
>
<template #index="{ rowIndex }">
{{ rowIndex + 1 }}
@ -146,12 +148,28 @@
type="error"
@ok="handleDelete(record)"
>
<a-button type="primary" size="small" status="danger">
<a-button
type="primary"
size="small"
status="danger"
style="padding: 7px"
>
<template #icon><icon-delete /></template>
{{ $t('delete') }}
</a-button>
</a-popconfirm>
</template>
</a-table>
<a-pagination
style="float: right; position: relative; right: 1px; bottom: 25px"
:total="pagination.total"
@page-size-change="onSizeChange"
:size="size"
@change="onPageChange"
show-total
show-jumper
show-page-size
/>
</a-card>
</div>
</template>
@ -161,6 +179,7 @@ import { computed, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import useLoading from '@/hooks/loading';
import useTableOption from '@/hooks/table-option';
import { Pagination } from '@/types/global';
import type { TableColumnData } from '@arco-design/web-vue/es/table/interface';
import dayjs from 'dayjs';
import { Message } from '@arco-design/web-vue';
@ -187,6 +206,12 @@ const generateFormModel = () => {
code: '',
};
};
const pagination: Pagination = {
page: 1,
size: 10,
current: 1,
total: 10,
};
const { t } = useI18n();
const renderData = ref<DeptRecord[]>([]);
@ -202,10 +227,16 @@ const columns = computed<TableColumnData[]>(() => [
{
title: t('deptTable.columns.name'),
dataIndex: 'name',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: t('deptTable.columns.id'),
dataIndex: 'id',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: t('deptTable.columns.remark'),
@ -215,6 +246,9 @@ const columns = computed<TableColumnData[]>(() => [
title: t('deptTable.columns.createTime'),
dataIndex: 'createTime',
slotName: 'createTime',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: t('deptTable.columns.enabled'),
@ -233,8 +267,11 @@ const fetchData = async (params?: Partial<DeptRecord>) => {
setLoading(true);
try {
const res = await deptStore.getDeptList(params);
const { data } = res;
renderData.value = data;
renderData.value = res.data.records;
pagination.page = res.data.page;
pagination.current = res.data.current;
pagination.total = res.data.total;
} catch (err) {
// you can report use errorHandler or other
} finally {
@ -245,12 +282,26 @@ const fetchData = async (params?: Partial<DeptRecord>) => {
//
const search = () => {
fetchData({
...pagination,
...formModel.value,
});
} as unknown as any);
};
search();
//
const onPageChange = (current: number) => {
pagination.page = current;
pagination.current = current;
search();
};
//
const onSizeChange = (size: number) => {
pagination.size = size;
search();
};
//
const reset = () => {
formModel.value = generateFormModel();

View File

@ -21,5 +21,9 @@ export default {
'dept.info.remark': 'Remark',
'dept.info.remark.placeholder': 'Please enter Remark',
'Confirm the deletion of this department' :'Confirm the deletion of this department?'
'Confirm the deletion of this department' :'Confirm the deletion of this department?',
// modalTitle
'createDept': 'Create Dept',
'editDept': 'Edit Dept Info'
};

View File

@ -5,8 +5,8 @@ export default {
'searchTable.form.deptName.placeholder': '请输入部门名称',
'deptTable.columns.index': '序号',
'deptTable.columns.name': '区域名称',
'deptTable.columns.id': '区域ID',
'deptTable.columns.name': '部门名称',
'deptTable.columns.id': '部门ID',
'deptTable.columns.remark': '备注',
'deptTable.columns.enabled': '是否启用',
'deptTable.columns.createTime': '创建时间',
@ -21,5 +21,10 @@ export default {
'dept.info.remark': '备注',
'dept.info.remark.placeholder': '请输入备注',
'Confirm the deletion of this department' :'确认删除此部门?'
'Confirm the deletion of this department' :'确认删除此部门?',
// modalTitle
'createDept': '新增部门',
'editDept': '修改部门信息'
};

View File

@ -1,25 +1,26 @@
<template>
<a-button v-if="props.isCreate" type="primary" @click="handleClick">
<template #icon><icon-plus /></template>
{{ modalTitle }}
{{ t('create') }}
</a-button>
<a-button
v-if="!props.isCreate"
type="outline"
size="small"
:style="{ marginRight: '10px' }"
:style="{ marginRight: '10px', padding: '7px' }"
@click="handleClick"
>
{{ modalTitle }}
<template #icon><icon-edit /></template>
{{ t('edit') }}
</a-button>
<a-modal
width="600px"
width="700px"
:visible="visible"
@ok="handleSubmit"
@cancel="handleCancel"
>
<template #title>{{ modalTitle }}</template>
<a-form ref="createEditRef" :model="formData" :style="{ width: '500px' }">
<a-form ref="createEditRef" :model="formData" :style="{ width: '650px' }">
<a-form-item
field="name"
:label="$t('role.info.name')"
@ -35,7 +36,9 @@
<a-form-item field="remark" :label="$t('role.info.remark')">
<a-textarea
v-model="formData.remark"
:show-word-limit="true"
:placeholder="$t('role.info.remark.placeholder')"
style="height: 100px"
/>
</a-form-item>
</a-form>
@ -60,7 +63,7 @@ const props = defineProps({
});
const { t } = useI18n();
const modalTitle = computed(() => {
return props.isCreate ? t('create') : t('edit');
return props.isCreate ? t('createRole') : t('editRole');
});
const { visible, setVisible } = useVisible(false);
const createEditRef = ref<FormInstance>();

View File

@ -1,7 +1,46 @@
<template>
<div class="container">
<Breadcrumb :items="['menu.system', 'menu.system.role']" />
<a-card class="general-card" :title="$t('menu.role.list')">
<a-card class="general-card" title=" ">
<a-row>
<a-col :flex="1">
<a-form
:model="formModel"
:label-col-props="{ span: 6 }"
:wrapper-col-props="{ span: 12 }"
label-align="right"
>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item field="title" :label="$t('searchTable.form.name')">
<a-input
v-model="formModel.name"
:placeholder="$t('searchTable.form.name.placeholder')"
/>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-col>
<a-divider style="height: 42px" direction="vertical" />
<a-col :flex="'46px'" style="text-align: right">
<a-space :size="18">
<a-button type="primary" @click="search">
<template #icon>
<icon-search />
</template>
{{ $t('searchTable.form.search') }}
</a-button>
<a-button @click="reset">
<template #icon>
<icon-refresh />
</template>
{{ $t('searchTable.form.reset') }}
</a-button>
</a-space>
</a-col>
</a-row>
<a-divider style="margin-top: 10px" />
<a-row style="margin-bottom: 16px">
<a-col :span="12">
<a-space>
@ -77,6 +116,7 @@
:bordered="false"
:size="size"
@page-change="onPageChange"
style="margin-bottom: 40px"
>
<template #index="{ rowIndex }">
{{ rowIndex + 1 }}
@ -105,12 +145,28 @@
type="error"
@ok="handleDelete(record)"
>
<a-button type="primary" size="small" status="danger">
<a-button
type="primary"
size="small"
status="danger"
style="padding: 7px"
>
<template #icon><icon-delete /></template>
{{ $t('delete') }}
</a-button>
</a-popconfirm>
</template>
</a-table>
<a-pagination
style="float: right; position: relative; right: 1px; bottom: 25px"
:total="pagination.total"
@page-size-change="onSizeChange"
:size="size"
@change="onPageChange"
show-total
show-jumper
show-page-size
/>
</a-card>
</div>
</template>
@ -119,6 +175,7 @@
import { computed, ref, reactive, watch, nextTick } from 'vue';
import { useI18n } from 'vue-i18n';
import useLoading from '@/hooks/loading';
import { Pagination } from '@/types/global';
import type { TableColumnData } from '@arco-design/web-vue/es/table/interface';
import { Message } from '@arco-design/web-vue';
import useTableOption from '@/hooks/table-option';
@ -143,6 +200,19 @@ const {
deepClone,
} = useTableOption();
const roleStore = useRoleStore();
const generateFormModel = () => {
return {
name: undefined,
};
};
const formModel = ref(generateFormModel());
const pagination: Pagination = {
page: 1,
size: 10,
current: 1,
total: 10,
};
//
const columns = computed<TableColumnData[]>(() => [
@ -154,19 +224,21 @@ const columns = computed<TableColumnData[]>(() => [
{
title: t('roleTable.columns.name'),
dataIndex: 'name',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: t('roleTable.columns.remark'),
dataIndex: 'remark',
},
// {
// title: t('roleTable.columns.authorities'),
// dataIndex: 'authorities',
// },
{
title: t('roleTable.columns.createTime'),
dataIndex: 'createTime',
slotName: 'createTime',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: t('roleTable.columns.enabled'),
@ -181,11 +253,15 @@ const columns = computed<TableColumnData[]>(() => [
]);
//
const fetchData = async () => {
const fetchData = async (params: any) => {
setLoading(true);
try {
const res = await roleStore.getRoleList();
renderData.value = res.data;
const res = await roleStore.getRoleList(params);
console.log('info', res.data);
renderData.value = res.data.records;
pagination.page = res.data.page;
pagination.current = res.data.current;
pagination.total = res.data.total;
} catch (err) {
// you can report use errorHandler or other
} finally {
@ -193,7 +269,33 @@ const fetchData = async () => {
}
};
fetchData();
//
const search = () => {
fetchData({
...pagination,
...formModel.value,
} as unknown as any);
};
search();
//
const onPageChange = (current: number) => {
pagination.page = current;
pagination.current = current;
search();
};
//
const onSizeChange = (size: number) => {
pagination.size = size;
search();
};
//
const reset = () => {
formModel.value = generateFormModel();
};
//
const enabledStatus = async (record: string) => {
@ -220,7 +322,7 @@ const handleDelete = async (record: RoleRecord) => {
content: t('delete.role.sucess'),
duration: 5 * 1000,
});
fetchData();
search();
} else {
Message.error({
content: t('delete.role.fail'),

View File

@ -14,6 +14,9 @@ export default {
'delete.role.sucess': 'Delete Role Sucess',
'delete.role.fail': 'Delete Role Fail',
'searchTable.form.name':'Name',
'searchTable.form.name.placeholder':'Please enter Name',
'role.info.name': 'Name',
'role.info.name.placeholder': 'Please enter Name',
'role.info.name.required': 'Name is required',
@ -27,4 +30,9 @@ export default {
'modify.status.sucess':'Modify status sucess',
'modify.status.fail':'Modify status fail',
// modalTitle
'createRole': 'Create Role',
'editRole': 'Edit Role Info',
};

View File

@ -14,6 +14,9 @@ export default {
'delete.role.sucess': '删除角色成功',
'delete.role.fail': '删除角色失败',
'searchTable.form.name':'角色名称',
'searchTable.form.name.placeholder':'请输入角色名称',
'role.info.name': '角色名称',
'role.info.name.placeholder': '请输入角色名称',
'role.info.name.required': '角色不能为空',
@ -27,4 +30,9 @@ export default {
'modify.status.sucess':'修改状态成功',
'modify.status.fail':'修改状态失败',
// modalTitle
'createRole': '新增角色',
'editRole': '修改角色信息'
};

View File

@ -1,26 +1,27 @@
<template>
<a-button v-if="props.isCreate" type="primary" @click="handleClick">
<template #icon><icon-plus /></template>
{{ modalTitle }}
{{ t('create') }}
</a-button>
<a-button
v-if="!props.isCreate"
type="outline"
size="small"
:style="{ marginRight: '10px' }"
:style="{ marginRight: '10px', padding: '7px' }"
@click="handleClick"
>
{{ modalTitle }}
<template #icon><icon-edit /></template>
{{ t('edit') }}
</a-button>
<a-modal
width="600px"
width="700px"
:visible="visible"
@ok="handleSubmit"
@cancel="handleCancel"
>
<template #title>{{ modalTitle }}</template>
<a-form ref="CreateRef" :model="formData" :style="{ width: '500px' }">
<a-form ref="CreateRef" :model="formData" :style="{ width: '650px' }">
<a-form-item
field="username"
:label="$t('user.info.username')"
@ -42,21 +43,20 @@
<div v-else>{{ formData.username }}</div>
</a-form-item>
<a-form-item
field="password"
:label="$t('user.info.password')"
v-if="isCreate"
field="email"
:label="$t('user.info.email')"
:rules="[
{
required: true,
type: 'email',
message: t('user.info.email.required'),
},
]"
:validate-trigger="['change', 'input']"
:rules="[{ required: true, message: t('user.info.password.required') }]"
>
<a-input
v-model="formData.password"
:placeholder="$t('user.info.password.placeholder')"
/>
</a-form-item>
<a-form-item field="nickName" :label="$t('user.info.nickName')">
<a-input
v-model="formData.nickName"
:placeholder="$t('user.info.nickName.placeholder')"
v-model="formData.email"
:placeholder="$t('user.info.email.placeholder')"
/>
</a-form-item>
<a-form-item
@ -74,22 +74,24 @@
/>
</a-form-item>
<a-form-item
field="email"
:label="$t('user.info.email')"
:rules="[
{
required: true,
type: 'email',
message: t('user.info.email.required'),
},
]"
field="password"
:label="$t('user.info.password')"
v-if="isCreate"
:validate-trigger="['change', 'input']"
:rules="[{ required: true, message: t('user.info.password.required') }]"
>
<a-input
v-model="formData.email"
:placeholder="$t('user.info.email.placeholder')"
v-model="formData.password"
:placeholder="$t('user.info.password.placeholder')"
/>
</a-form-item>
<a-form-item field="nickName" :label="$t('user.info.nickName')">
<a-input
v-model="formData.nickName"
:placeholder="$t('user.info.nickName.placeholder')"
/>
</a-form-item>
<a-form-item field="address" :label="$t('user.info.address')">
<a-input
v-model="formData.address"
@ -139,11 +141,10 @@
import { useI18n } from 'vue-i18n';
import useVisible from '@/hooks/visible';
import { computed, PropType, ref } from 'vue';
import { CreateRecord, UserRecord } from '@/api/user';
import { CreateRecord } from '@/api/user';
import { FormInstance } from '@arco-design/web-vue/es/form';
import { queryRoleList } from '@/api/role';
import { deptList } from '@/api/dept';
import { computedAsync } from '@vueuse/core';
import { Message } from '@arco-design/web-vue';
import { useUserStore } from '@/store';
@ -155,7 +156,7 @@ const props = defineProps({
});
const { t } = useI18n();
const modalTitle = computed(() => {
return props.isCreate ? t('create') : t('edit');
return props.isCreate ? t('createUser') : t('editUser');
});
const { visible, setVisible } = useVisible(false);
const checkKeys = ref<number[]>([]);
@ -180,32 +181,16 @@ let formDifer = {};
const userStore = useUserStore();
//
// const deptOptions = computedAsync(async () => {
// const { data } = await deptList();
// const deptData = data.filter((item: any) => {
// return item.enabled !== false;
// });
// return deptData;
// });
const deptOptions = ref();
const getDeptData = async () => {
const res = await deptList();
deptOptions.value = res.data;
deptOptions.value = res.data.records;
};
//
// const roleOptions = computedAsync(async () => {
// const res = await queryRoleList();
// const roleData = res.data.filter((item: any) => {
// return item.enabled !== false;
// });
// return roleData;
// });
const roleOptions = ref();
const getRoleData = async () => {
const res = await queryRoleList();
roleOptions.value = res.data.filter((item: any) => {
const res = await queryRoleList('');
roleOptions.value = res.data.records.filter((item: any) => {
return item.enabled !== false;
});
};
@ -215,7 +200,6 @@ const handleClick = () => {
getDeptData();
getRoleData();
const userId = props.prem?.id;
//
if (!props.isCreate && userId) {
formData.value = props.prem;
@ -242,6 +226,7 @@ const handleSubmit = async () => {
formData.value.permissionIds = checkKeys.value;
//
if (props.isCreate) {
// formData.value.username = formData.value.email;
const res = await userStore.createUser(formData.value);
if (res.status === 200) {
Message.success({

View File

@ -1,7 +1,7 @@
<template>
<div class="container">
<Breadcrumb :items="['menu.system', 'menu.system.user']" />
<a-card class="general-card" :title="$t('menu.list.searchTable')">
<a-card class="general-card" title=" ">
<a-row>
<a-col :flex="1">
<a-form
@ -80,15 +80,26 @@
</a-row>
<a-divider style="margin-top: 0" />
<a-row style="margin-bottom: 16px">
<a-row>
<a-col :span="12">
<a-space>
<UserEdit ref="createUserRef" :is-create="true" @refresh="search" />
</a-space>
<a-button @click="generateExcel" style="margin-left: 20px">
<template #icon>
<icon-download size="18" />
</template>
{{ $t('searchTable.operation.download') }}
</a-button>
</a-col>
<a-col
:span="12"
style="display: flex; align-items: center; justify-content: end"
style="
display: flex;
align-items: center;
justify-content: end;
padding-bottom: 20px;
"
>
<a-tooltip :content="$t('searchTable.actions.refresh')">
<div class="action-icon" @click="search">
@ -152,12 +163,15 @@
<a-table
row-key="id"
:loading="loading"
:pagination="pagination"
:pagination="false"
:columns="(cloneColumns as TableColumnData[])"
:data="renderData"
:bordered="false"
:size="size"
@page-change="onPageChange"
style="margin-bottom: 40px"
:filter-icon-align-left="alignLeft"
@change="handleSortChange"
>
<template #index="{ rowIndex }">
{{ rowIndex + 1 + (pagination.current - 1) * pagination.size }}
@ -177,12 +191,12 @@
:is-create="false"
@refresh="fetchData"
/>
<!-- <Userc
<Userc
ref="editUserRef"
:user="record"
:is-create="false"
@refresh="search"
/> -->
/>
<!-- <a-popconfirm
content="确认删除此用户?"
type="error"
@ -194,22 +208,31 @@
</a-popconfirm> -->
</template>
</a-table>
<a-pagination
style="float: right; position: relative; right: 1px; bottom: 25px"
:total="pagination.total"
@page-size-change="onSizeChange"
:size="size"
@change="onPageChange"
show-total
show-jumper
show-page-size
/>
</a-card>
</div>
</template>
<script lang="ts" setup>
import { computed, ref, reactive, watch, nextTick } from 'vue';
import { computed, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import useLoading from '@/hooks/loading';
import { UserRecord, UserParams } from '@/api/user';
import { Pagination } from '@/types/global';
import type { SelectOptionData } from '@arco-design/web-vue/es/select/interface';
import type { TableColumnData } from '@arco-design/web-vue/es/table/interface';
import cloneDeep from 'lodash/cloneDeep';
import Sortable from 'sortablejs';
import { useUserStore } from '@/store';
import { Message } from '@arco-design/web-vue';
import { downloadExcel, DownloadExcelPrams } from '@/utils/excel';
import useTableOption from '@/hooks/table-option';
import UserEdit from './components/user-edit.vue';
@ -245,13 +268,12 @@ const {
const userStore = useUserStore();
const basePagination: Pagination = {
const pagination: Pagination = {
page: 1,
size: 10,
current: 1,
total: null,
};
const pagination = reactive({
...basePagination,
});
const columns = computed<TableColumnData[]>(() => [
{
@ -263,18 +285,30 @@ const columns = computed<TableColumnData[]>(() => [
{
title: t('userTable.columns.username'),
dataIndex: 'username',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: t('userTable.columns.phone'),
dataIndex: 'phone',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: t('userTable.columns.email'),
dataIndex: 'email',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: t('userTable.columns.nickName'),
dataIndex: 'nickName',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: t('userTable.columns.address'),
@ -311,9 +345,10 @@ const fetchData = async (
try {
const res = await userStore.getUserList(params);
renderData.value = res.data.records;
pagination.page = res.data.page;
pagination.current = res.data.current;
pagination.total = res.data.total;
// pagination.size = data.size;
pagination.size = res.data.size;
} catch (err) {
// you can report use errorHandler or other
} finally {
@ -321,26 +356,58 @@ const fetchData = async (
}
};
//
const generateExcel = () => {
const param: DownloadExcelPrams = {
columns: [
{ title: '用户名', key: 'username' },
{ title: '昵称', key: 'nickName' },
{ title: '电话号码', key: 'phone' },
{ title: '部门Id', key: 'deptId' },
{ title: '角色Id', key: 'roleId' },
{ title: 'email', key: 'email' },
{ title: '启用状态', key: 'enabled' },
],
rows: renderData.value,
name: '用户表格',
};
downloadExcel(param);
};
//
const search = () => {
fetchData({
...basePagination,
...pagination,
...formModel.value,
} as unknown as UserParams);
};
//
//
const onPageChange = (current: number) => {
fetchData({ ...basePagination, current });
pagination.page = current;
pagination.current = current;
search();
};
fetchData();
//
const onSizeChange = (size: number) => {
pagination.size = size;
search();
};
search();
//
const reset = () => {
formModel.value = generateFormModel();
};
//
const alignLeft = ref(false);
const handleSortChange = (data: any, extra: any, currentDataSource: any) => {
console.log('change', data, extra, currentDataSource);
};
//
const enabledStatus = async (record: string) => {
record.enabled = !record.enabled;

View File

@ -65,4 +65,8 @@ export default {
'user.info.role.placeholder': 'Please select Role',
'user.info.role.required': 'Role is required',
// modalTitle
'createUser': 'Create User',
'editUser': 'Edit User Info'
};

View File

@ -16,7 +16,7 @@ export default {
'searchTable.form.selectDefault': '全部',
'searchTable.operation.create': '新建',
'searchTable.operation.import': '批量导入',
'searchTable.operation.download': '下载',
'searchTable.operation.download': '导出',
// columns
'userTable.columns.index': '序号',
'userTable.columns.nickName': '昵称',
@ -64,5 +64,10 @@ export default {
'user.info.role':'角色',
'user.info.role.placeholder': '请选择角色',
'user.info.role.required': '请选择一个',
'user.info.code':'验证码',
'user.info.code.placeholder':'请输入验证码',
// modalTitle
'createUser': '新增用户',
'editUser': '修改用户信息'
}

View File

@ -1,33 +1,36 @@
<template>
<!-- 新增 -->
<a-button
v-permission="['BILL_QUERY']"
v-if="props.isCreate"
type="primary"
size="small"
:style="{ marginRight: '10px' }"
@click="handleClick"
>
<template #icon><icon-plus /></template>
{{ modalTitle }}
</a-button>
<!-- 修改 -->
<a-button
v-permission="['BILL_UPDATE']"
v-if="!props.isCreate"
size="small"
type="primary"
:style="{ marginRight: '10px' }"
:style="{ marginRight: '10px', padding: '7px' }"
@click="handleClick"
>
<template #icon><icon-edit /></template>
{{ modalTitle }}
</a-button>
<a-modal
width="600px"
width="700px"
:visible="visible"
:footer="false"
@cancel="handleCancel"
>
<template #title>{{ modalTitle }}</template>
<a-form ref="createEditRef" :model="formData" :style="{ width: '500px' }">
<a-form ref="createEditRef" :model="formData" :style="{ width: '650px' }">
<a-form-item
field="companyName"
:label="$t('ticket.info.companyName')"
@ -54,18 +57,6 @@
/>
</a-form-item>
<a-form-item
field="body"
:label="$t('ticket.info.body')"
:validate-trigger="['change', 'input']"
:rules="[{ required: true, message: t('ticket.info.body.required') }]"
>
<a-textarea
v-model="formData.body"
:placeholder="$t('ticket.info.body.placeholder')"
/>
</a-form-item>
<a-form-item
field="money"
:label="$t('ticket.info.money')"
@ -105,16 +96,6 @@
/>
</a-form-item>
<a-form-item field="attachment" :label="$t('ticket.info.attachment')">
<a-upload
:file-list="fileList"
:custom-request="Onchange"
:limit="1"
:on-before-remove="removeAttact"
v-model="formData.attachId"
/>
</a-form-item>
<a-form-item
field="deptId"
:label="$t('ticket.info.dept')"
@ -159,6 +140,29 @@
>
{{ formData.comment }}
</a-form-item>
<a-form-item
field="body"
:label="$t('ticket.info.body')"
:validate-trigger="['change', 'input']"
:rules="[{ required: true, message: t('ticket.info.body.required') }]"
>
<a-textarea
v-model="formData.body"
:placeholder="$t('ticket.info.body.placeholder')"
style="height: 100px"
/>
</a-form-item>
<a-form-item field="attachment" :label="$t('ticket.info.attachment')">
<a-upload
:file-list="fileList"
:custom-request="Onchange"
:limit="5"
:on-before-remove="removeAttact"
/>
</a-form-item>
<a-form-item>
<a-button type="dashed" @click="handleCancel">
{{ $t('cancel') }}</a-button
@ -182,10 +186,9 @@
import { useI18n } from 'vue-i18n';
import useVisible from '@/hooks/visible';
import { computed, PropType, ref } from 'vue';
import { computedAsync } from '@vueuse/core';
import { FormInstance } from '@arco-design/web-vue/es/form';
import { SelectOptionData } from '@arco-design/web-vue/es/select/interface';
import { Message } from '@arco-design/web-vue';
import { FileItem, Message } from '@arco-design/web-vue';
import { useTicketStore, useUserStore, useRoleStore } from '@/store';
import { TicketRecord } from '@/api/ticket';
import { deptList } from '@/api/dept';
@ -216,7 +219,7 @@ const formData = ref<TicketRecord>({
type: '',
contactEmail: '',
companyName: '',
attachId: '',
attachId: [],
auditorId: '',
userId: undefined,
submit: '',
@ -228,7 +231,7 @@ const emit = defineEmits(['refresh']);
const deptOptions = ref();
const getDeptData = async () => {
const res = await deptList();
deptOptions.value = res.data;
deptOptions.value = res.data.records;
};
const typesOptions = computed<SelectOptionData[]>(() => [
@ -250,8 +253,8 @@ let auiditRoleId = '';
// ID
const getRoleId = async () => {
const res = await roleStore.getRoleList();
res.data.forEach((item: any) => {
const res = await roleStore.getRoleList('');
res.data.records.forEach((item: any) => {
if (item.name === 'auditor') {
auiditRoleId = item.id;
}
@ -277,12 +280,13 @@ const optionDept = async (flag: boolean) => {
let formDifer = {};
const fileList = ref([]);
const attachList = ref([]);
//
const Onchange = async (option: any) => {
const FormDatas = new FormData();
FormDatas.append('file', option.fileItem.file);
const res = await ticketStore.uploadFileTicket(FormDatas);
const res = await ticketStore.uploadFile(FormDatas);
if (res.status === 200) {
Message.success({
content: t('upload.sucess'),
@ -290,7 +294,10 @@ const Onchange = async (option: any) => {
});
res.data.name = res.data.fileName;
fileList.value.push(res.data);
formData.value.attachId = res.data.id;
console.log('res', res.data.id);
attachList.value.push(res.data.id);
formData.value.attachId = attachList;
console.log('poti', formData.value.attachId);
} else {
Message.error({
content: t('upload.fail'),
@ -300,8 +307,13 @@ const Onchange = async (option: any) => {
};
//
const removeAttact = () => {
fileList.value.pop();
const removeAttact = (fileItem: FileItem) => {
console.log('2', fileItem.id, fileList.value, attachList.value);
fileList.value = fileList.value.filter((item) => item.id !== fileItem.id);
attachList.value = attachList.value.filter((item) => item !== fileItem.id);
console.log('3', fileList.value, attachList.value);
formData.value.attachId = attachList.value;
console.log('3', formData.value.attachId);
};
//
@ -317,12 +329,13 @@ const handleClick = () => {
//
formData.value = res.data;
//
if (formData.value.attachId) {
const data = await ticketStore.getAttachment(formData.value.attachId);
formData.value.attachId.forEach(async (item: any) => {
console.log('item,', item);
const data = await ticketStore.getAttachment(item);
data.data.name = data.data.fileName;
fileList.value.push(data.data);
}
});
attachList.value = formData.value.attachId;
//
const auditInfo = await userStore.getUserDetail(
formData.value.auditorId
@ -389,7 +402,7 @@ const handleOk = async () => {
}
}
createEditRef.value?.resetFields();
removeAttact();
attachList.value = [];
emit('refresh');
setVisible(false);
}
@ -430,7 +443,8 @@ const handleStorage = async () => {
}
}
createEditRef.value?.resetFields();
removeAttact();
attachList.value = [];
fileList.value = [];
emit('refresh');
setVisible(false);
}
@ -438,7 +452,8 @@ const handleStorage = async () => {
//
const handleCancel = async () => {
removeAttact();
attachList.value = [];
fileList.value = [];
createEditRef.value?.resetFields();
setVisible(false);
};

View File

@ -1,33 +1,37 @@
<template>
<!-- 详情 -->
<a-button
v-permission="['BILL_QUERY']"
v-if="props.isDetail"
type="outline"
size="small"
:style="{ marginRight: '10px' }"
:style="{ marginRight: '10px', padding: '7px' }"
@click="handleClick"
>
<template #icon><icon-eye /></template>
{{ modalTitle }}
</a-button>
<!-- 审核 -->
<a-button
v-permission="['BILL_AUDIT']"
v-if="!props.isDetail"
size="small"
type="primary"
:style="{ marginRight: '10px' }"
:style="{ marginRight: '10px', padding: '7px' }"
@click="handleClick"
>
<template #icon><icon-search /></template>
{{ modalTitle }}
</a-button>
<a-modal
width="600px"
width="700px"
:visible="visible"
@ok="handleSubmit"
@cancel="handleCancel"
>
<template #title>{{ modalTitle }}</template>
<a-form ref="createEditRef" :model="formData" :style="{ width: '500px' }">
<a-form ref="createEditRef" :model="formData" :style="{ width: '650px' }">
<a-form-item field="companyName" :label="$t('ticket.info.companyName')">
<div>
{{ formData.companyName }}
@ -70,39 +74,16 @@
</div>
</a-form-item>
<a-form-item field="attachment" :label="$t('ticket.info.attachment')">
<a-upload
:file-list="fileList"
:custom-request="Onchange"
:limit="1"
:on-before-remove="removeAttact"
:show-remove-button="false"
v-model="formData.attachId"
/>
</a-form-item>
<a-form-item field="deptId" :label="$t('ticket.info.dept')">
<a-tree-select
v-model="formData.deptId"
:field-names="{
key: 'id',
title: 'name',
children: 'children',
}"
:data="deptOptions"
disabled
style="color: black"
/>
{{ formData.deptName }}
</a-form-item>
<!-- 审核员 -->
<a-form-item field="auditorId" :label="$t('ticket.info.auditor')">
<a-select v-model="formData.auditorId" disabled style="color: black">
<div v-for="(item, id) in auditorOptions" :key="id">
<a-option :value="item.id">{{ item.username }} </a-option>
</div>
</a-select>
{{ formData.auditor }}
</a-form-item>
<!-- 审核状态 -->
<a-form-item
field="status"
:label="$t('ticket.info.status')"
@ -112,7 +93,6 @@
{{ formData.status }}
</div>
</a-form-item>
<a-form-item
field="status"
v-else
@ -121,18 +101,37 @@
:validate-trigger="['change', 'input']"
:rules="[{ required: true, message: t('ticket.info.status.required') }]"
>
<a-radio-group
v-for="i in statusOptions"
:key="i.value"
v-model="formData.submit"
>
<a-radio :value="i.value">{{ i.label }}</a-radio>
<a-radio-group v-model="formData.submit" default-value="true">
<a-radio value="true">{{ t('pass') }}</a-radio>
<a-radio value="false">{{ t('failed') }}</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item field="comment" :label="$t('ticket.info.comment')">
<a-textarea v-model="formData.comment" v-if="!props.isDetail" />
<div v-else>{{ formData.comment }} </div>
<!-- 审核意见 -->
<a-form-item
field="comment"
:label="$t('ticket.info.comment')"
v-if="!props.isDetail"
>
<a-textarea v-model="formData.comment" style="height: 200px" />
</a-form-item>
<a-form-item field="comment" :label="$t('ticket.info.comment')" v-else>
<div>{{ formData.comment || '无' }}</div>
</a-form-item>
<!-- 附件 -->
<a-form-item field="attachment" :label="$t('ticket.info.attachment')">
<a-upload
:file-list="fileList"
:custom-request="Onchange"
:limit="5"
:disabled="true"
:on-before-remove="removeAttact"
:show-remove-button="false"
v-model="formData.attachId"
v-if="fileList.length !== 0"
/>
<div v-else></div>
</a-form-item>
</a-form>
</a-modal>
@ -142,7 +141,6 @@
import { useI18n } from 'vue-i18n';
import useVisible from '@/hooks/visible';
import { computed, PropType, ref } from 'vue';
import { computedAsync } from '@vueuse/core';
import { FormInstance } from '@arco-design/web-vue/es/form';
import { SelectOptionData } from '@arco-design/web-vue/es/select/interface';
import { Message } from '@arco-design/web-vue';
@ -178,8 +176,10 @@ const formData = ref<TicketRecord>({
companyName: '',
attachId: '',
auditorId: '',
submit: undefined,
submit: true,
comment: '',
deptName: '',
auditor: '',
});
const emit = defineEmits(['refresh']);
@ -187,15 +187,15 @@ const emit = defineEmits(['refresh']);
const deptOptions = ref();
const getDeptData = async () => {
const res = await deptList();
deptOptions.value = res.data;
deptOptions.value = res.data.records;
};
let auiditRoleId = '';
// ID
const getRoleId = async () => {
const res = await roleStore.getRoleList();
res.data.forEach((item: any) => {
const res = await roleStore.getRoleList('');
res.data.records.forEach((item: any) => {
if (item.name === 'auditor') {
auiditRoleId = item.id;
}
@ -216,17 +216,6 @@ const optionDept = async () => {
auditorOptions.value = res.data.records;
};
const statusOptions = computed<SelectOptionData[]>(() => [
{
label: t('pass'),
value: true,
},
{
label: t('failed'),
value: false,
},
]);
const fileList = ref([]);
//
const handleClick = async () => {
@ -237,18 +226,29 @@ const handleClick = async () => {
const res = await ticketStore.getDetail(ticketId);
//
formData.value = res.data;
formData.value.submit = 'true';
//
if (formData.value.attachId) {
const data = await ticketStore.getAttachment(formData.value.attachId);
formData.value.attachId.forEach(async (item: any) => {
// console.log('item,', item);
const data = await ticketStore.getAttachment(item);
data.data.name = data.data.fileName;
fileList.value.push(data.data);
}
});
//
const auditInfo = await userStore.getUserDetail(formData.value.auditorId);
formData.value.deptId = auditInfo.data.deptId;
optionDept().then(() => {
deptOptions.value.forEach((item: any) => {
if (item.id === formData.value.deptId) {
formData.value.deptName = item.name;
}
});
auditorOptions.value.forEach((item: any) => {
if (item.id === formData.value.auditorId) {
formData.value.auditor = item.username;
}
});
setVisible(true);
});
};
@ -271,7 +271,7 @@ const handleSubmit = async () => {
});
}
}
fileList.value.pop();
fileList.value = [];
emit('refresh');
setVisible(false);
}
@ -279,7 +279,7 @@ const handleSubmit = async () => {
//
const handleCancel = async () => {
fileList.value.pop();
fileList.value = [];
createEditRef.value?.resetFields();
setVisible(false);
};

View File

@ -1,7 +1,18 @@
<template>
<div class="container">
<Breadcrumb :items="['menu.ticket', 'menu.ticket.manage']" />
<a-card class="general-card" :title="$t('menu.list.searchTable')">
<a-card class="general-card">
<a-tabs default-active-key="PASS" @change="changeTop">
<a-tab-pane key="PASS" :title="t('pass')"> </a-tab-pane>
<a-tab-pane key="FAILED" :title="t('failed')"> </a-tab-pane>
<a-tab-pane key="EXAMINE" :title="t('unreviewed')"> </a-tab-pane>
<a-tab-pane
key="SUBMIT"
:title="t('drafts')"
v-if="userStore.permissions !== 'auditor'"
>
</a-tab-pane>
</a-tabs>
<a-row>
<a-col :flex="1">
<a-form
@ -11,6 +22,7 @@
label-align="right"
>
<a-row :gutter="16">
<!-- 公司名称 -->
<a-col :span="8">
<a-form-item
field="companyName"
@ -25,6 +37,7 @@
</a-form-item>
</a-col>
<!-- 标题 -->
<a-col :span="8">
<a-form-item
field="title"
@ -36,9 +49,42 @@
/>
</a-form-item>
</a-col>
<!-- 用户 -->
<a-col :span="8">
<a-form-item
field="userName"
:label="$t('searchTable.form.userName')"
>
<a-input
v-model="formModel.userName"
:placeholder="$t('searchTable.form.committer.placeholder')"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<!-- 金额 -->
<a-col :span="8">
<a-form-item
field="money"
:label="$t('searchTable.form.money')"
>
<a-input-group>
<a-input
v-model="formModel.minMoney"
:placeholder="$t('searchTable.form.min')"
/>
<a-input
v-model="formModel.maxMoney"
:placeholder="$t('searchTable.form.max')"
/>
</a-input-group>
</a-form-item>
</a-col>
<!-- 票据类型 -->
<a-col :span="8">
<a-form-item field="type" :label="$t('searchTable.form.type')">
<a-select
@ -49,15 +95,16 @@
</a-form-item>
</a-col>
<!-- 时间 -->
<a-col :span="8">
<a-form-item
field="status"
:label="$t('searchTable.form.status')"
>
<a-select
v-model="formModel.status"
:options="statusOptions"
:placeholder="$t('searchTable.form.status.placeholder')"
<a-form-item field="time" :label="$t('searchTable.form.time')">
<a-range-picker
v-model="formModel.time"
style="width: 360px"
show-time
@select="onSelect"
value-format="YYYY-MM-DD"
:allow-clear="false"
/>
</a-form-item>
</a-col>
@ -88,12 +135,19 @@
<a-col :span="12">
<a-space>
<TicketForm
v-if="formModel.status === 'SUBMIT'"
ref="createEditRef"
:prem="ticketItem"
:prem="formModel"
:is-create="true"
@refresh="search"
/>
</a-space>
<a-button @click="generateExcel" style="margin-left: 5px">
<template #icon>
<icon-download size="16" />
</template>
{{ $t('searchTable.operation.download') }}
</a-button>
</a-col>
<a-col
:span="12"
@ -162,10 +216,23 @@
:columns="(cloneColumns as TableColumnData[])"
:data="renderData"
:bordered="false"
:pagination="pagination"
:pagination="false"
:size="size"
:expandable="expandable"
@page-change="onPageChange"
style="margin-bottom: 40px"
>
<template #expand-row="{ record }">
<!-- 下面展示子表格,根据需求对子table进行属性配置 -->
<a-descriptions layout="inline-horizontal" :column="1" bordered>
<descriptions-item :label="t('ticket.info.contactEmail')">
{{ record.contactEmail }}
</descriptions-item>
<descriptions-item :label="t('ticket.info.body')">
{{ record.body }}
</descriptions-item>
</a-descriptions>
</template>
<template #index="{ rowIndex }">
{{ rowIndex + 1 + (pagination.current - 1) * pagination.size }}
</template>
@ -173,6 +240,21 @@
{{ dayjs(record.createTime).format('YYYY-MM-DD') }}
</template>
<template #status="{ record }">
<a-button status="success" v-if="record.status === '审核通过'">{{
record.status
}}</a-button>
<a-button status="danger" v-if="record.status === '审核未通过'">{{
record.status
}}</a-button>
<a-button status="normal" v-if="record.status === '待审核'">{{
record.status
}}</a-button>
<a-button status="warning" v-if="record.status === '待提交'">{{
record.status
}}</a-button>
</template>
<template #operations="{ record }">
<!-- 详情 -->
<TicketEdit
@ -210,18 +292,33 @@
size="small"
status="danger"
v-permission="['BILL_DELETE']"
v-if="
record.status === '待提交' || record.status === '审核未通过'
"
style="padding: 7px"
>
<template #icon><icon-delete /></template>
{{ $t('delete') }}
</a-button>
</a-popconfirm>
</template>
</a-table>
<a-pagination
style="float: right; position: relative; right: 1px; bottom: 25px"
:total="pagination.total"
@page-size-change="onSizeChange"
:size="size"
@change="onPageChange"
show-total
show-jumper
show-page-size
/>
</a-card>
</div>
</template>
<script lang="ts" setup>
import { computed, ref, watch, reactive } from 'vue';
import { computed, reactive, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import useLoading from '@/hooks/loading';
import useTableOption from '@/hooks/table-option';
@ -232,6 +329,7 @@ import dayjs from 'dayjs';
import { Message } from '@arco-design/web-vue';
import { useTicketStore, useUserStore } from '@/store';
import { TicketRecord } from '@/api/ticket';
import { downloadExcel, DownloadExcelPrams } from '@/utils/excel';
import TicketEdit from './components/ticket-edit.vue';
import TicketForm from './components/form-edit.vue';
@ -255,13 +353,27 @@ const generateFormModel = () => {
companyName: '',
title: '',
type: '',
status: '',
//
status: 'PASS',
time: [],
userName: '',
minMoney: '',
maxMoney: '',
startTime: '',
endTime: '',
};
};
const { t } = useI18n();
const renderData = ref<TicketRecord[]>([]);
const formModel = ref(generateFormModel());
const currentStatus = ref('PASS');
//
const expandable = reactive({
title: ' ',
expandedRowRender: (record: any) => {},
});
//
const columns = computed<TableColumnData[]>(() => [
@ -273,6 +385,9 @@ const columns = computed<TableColumnData[]>(() => [
{
title: t('ticketTable.columns.companyName'),
dataIndex: 'companyName',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: t('ticketTable.columns.title'),
@ -281,14 +396,36 @@ const columns = computed<TableColumnData[]>(() => [
{
title: t('ticketTable.columns.money'),
dataIndex: 'money',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: t('ticketTable.columns.userName'),
dataIndex: 'userName',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: t('ticketTable.columns.createTime'),
dataIndex: 'createTime',
sortable: {
sortDirections: ['ascend', 'descend'],
},
slotName: 'createTime',
},
{
title: t('ticketTable.columns.type'),
dataIndex: 'type',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: t('ticketTable.columns.status'),
dataIndex: 'status',
slotName: 'status',
},
{
title: t('searchTable.columns.operations'),
@ -312,39 +449,56 @@ const typesOptions = computed<SelectOptionData[]>(() => [
},
]);
const statusOptions = computed<SelectOptionData[]>(() => [
{
label: t('unsubmitted'),
value: 'SUBMIT',
},
{
label: t('unreviewed'),
value: 'EXAMINE',
},
{
label: t('pass'),
value: 'PASS',
},
{
label: t('failed'),
value: 'FAILED',
},
]);
//
const basePagination: Pagination = {
const pagination: Pagination = {
page: 1,
size: 10,
current: 1,
total: 1,
};
//
const pagination = reactive({
...basePagination,
});
//
const generateExcel = () => {
const param: DownloadExcelPrams = {
columns: [
{ title: '公司', key: 'companyName' },
{ title: '标题', key: 'title' },
{ title: '金额', key: 'money' },
{ title: '内容', key: 'body' },
{ title: '联系邮箱', key: 'contactEmail' },
{ title: '创建时间', key: 'createTime' },
{ title: '类型', key: 'type' },
{ title: '审核状态', key: 'status' },
],
rows: renderData.value,
name: '票据表格',
};
downloadExcel(param);
};
//
const onSelect = (dateString: any, date: any) => {
formModel.value.startTime = String(dateString[0]);
formModel.value.endTime = String(dateString[1]);
};
//
const onChange = (dateString: any, date: any) => {
formModel.value.startTime = String(dateString[0]);
formModel.value.endTime = String(dateString[1]);
};
//
const onPageChange = (current: number) => {
fetchData({ ...basePagination, ...formModel.value, current });
pagination.page = current;
pagination.current = current;
search();
};
//
const onSizeChange = (size: number) => {
pagination.size = size;
search();
};
//
@ -383,22 +537,32 @@ const fetchData = async (params: {
//
const search = () => {
// time
const { time, ...auditDate } = formModel.value;
fetchData({
...basePagination,
...formModel.value,
current: '1',
...pagination,
...auditDate,
} as unknown as any);
};
//
const changeTop = (key: any) => {
formModel.value.status = key;
currentStatus.value = key;
search();
};
search();
//
const reset = () => {
formModel.value = generateFormModel();
formModel.value.status = currentStatus.value;
};
//
const handleDelete = async (record) => {
const handleDelete = async (record: any) => {
const res = await ticketStore.removeTicket(record.id);
if (res.status === 200) {
Message.success({

View File

@ -16,10 +16,19 @@ export default {
'searchTable.form.companyName.placeholder': 'Please enter Company',
'searchTable.form.title':'Title',
'searchTable.form.title.placeholder': 'Please enter Title',
'searchTable.form.money':'Money',
'searchTable.form.money.placeholder': 'Please enter Money',
'searchTable.form.userName':'UserName',
'searchTable.form.userName.placeholder': 'Please enter UserName',
'searchTable.form.type':'Type',
'searchTable.form.type.placeholder': 'Please select Type',
'searchTable.form.status': 'Status',
'searchTable.form.status.placeholder': 'Please select Status',
'searchTable.form.time':'Time',
'searchTable.form.time.placeholder': 'Please select Time',
'searchTable.form.min':'Min money',
'searchTable.form.max':'Max money',
'Confirm the deletion of this ticket': 'Confirm the deletion of this ticket?',
@ -44,37 +53,39 @@ export default {
'unreviewed': 'Unreviewed',
'pass': 'Pass',
'failed': 'Failed',
'drafts': 'Drafts',
"ticket.info.companyName": 'Company',
"ticket.info.companyName": 'Company:',
"ticket.info.companyName.required": 'CompanyName is required',
"ticket.info.companyName.placeholder": 'Please enter CompanyName',
"ticket.info.title": 'Title',
"ticket.info.title": 'Title:',
"ticket.info.title.required": 'Title is required',
"ticket.info.title.placeholder": 'Please enter Title',
"ticket.info.body": 'Content',
"ticket.info.body": 'Content:',
"ticket.info.body.required": 'Content is required',
"ticket.info.body.placeholder": 'Please enter Content',
"ticket.info.money": 'Money',
"ticket.info.money": 'Money:',
"ticket.info.money.required": 'Money is required',
"ticket.info.money.placeholder": 'Please enter Money',
"ticket.info.contactEmail": 'Email',
"ticket.info.createTime": 'CreateTime',
"ticket.info.contactEmail": 'Email:',
"ticket.info.createTime": 'CreateTime:',
"ticket.info.contactEmail.required": 'contactEmail is required',
"ticket.info.contactEmail.placeholder": 'Please enter contactEmail',
"ticket.info.type": 'Type',
"ticket.info.type": 'Type:',
"ticket.info.type.required": 'Type is required',
"ticket.info.type.placeholder": 'Please select Type',
"ticket.info.attachment": 'Attachment',
"ticket.info.dept": 'Dept',
"ticket.info.attachment": 'Attachment:',
"ticket.info.dept": 'Dept:',
"ticket.info.dept.required": 'Dept is required',
"ticket.info.dept.placeholder": 'Please select Dept',
"ticket.info.auditor": 'Auditor',
"ticket.info.auditor": 'Auditor:',
"ticket.info.auditor.required": 'Auditor is required',
"ticket.info.auditor.placeholder": 'Please select Auditor',
"ticket.info.status": 'Status',
"ticket.info.status": 'Status:',
"ticket.info.status.required": 'Status is required',
"ticket.info.status.placeholder": 'Please select Status',
"ticket.info.comment": 'Comment',
"ticket.info.comment": 'Comment:',
"ticketTable.columns.userName":'userName',
'upload.sucess': 'Upload Sucess',
'upload.fail': 'Upload Fail',

View File

@ -15,10 +15,19 @@ export default {
'searchTable.form.companyName.placeholder': '请输入公司名称',
'searchTable.form.title':'标题',
'searchTable.form.title.placeholder': '请输入标题',
'searchTable.form.money':'金额',
'searchTable.form.money.placeholder': '请输入金额',
'searchTable.form.userName':'用户',
'searchTable.form.committer.placeholder': '请输入票据提交的用户',
'searchTable.form.type':'类型',
'searchTable.form.type.placeholder': '请选择票据类型',
'searchTable.form.status': '状态',
'searchTable.form.status.placeholder': '请选择状态',
'searchTable.form.time':'时间',
'searchTable.form.time.placeholder': '请选择时间段',
'searchTable.form.min':'最小金额',
'searchTable.form.max':'最大金额',
'Confirm the deletion of this ticket': '确定删除此票据?',
@ -43,37 +52,40 @@ export default {
'unreviewed': '待审核',
'pass': '审核通过',
'failed': '审核未通过',
'drafts':'草稿箱',
"ticket.info.companyName": '公司',
"ticket.info.companyName": '公司:',
"ticket.info.companyName.required": '公司名称不能为空',
"ticket.info.companyName.placeholder": '请输入公司名称',
"ticket.info.title": '标题',
"ticket.info.title": '标题:',
"ticket.info.title.required": '标题不能为空',
"ticket.info.title.placeholder": '请输入标题',
"ticket.info.body": '内容',
"ticket.info.body": '内容:',
"ticket.info.body.required": '内容不能为空',
"ticket.info.body.placeholder": '请输入内容',
"ticket.info.money": '金额',
"ticket.info.money": '金额:',
"ticket.info.money.required": '金额不能为空',
"ticket.info.money.placeholder": '请输入金额',
"ticket.info.contactEmail": '联系邮箱',
"ticket.info.createTime": '创建时间',
"ticket.info.contactEmail": '联系邮箱:',
"ticket.info.createTime": '创建时间:',
"ticket.info.contactEmail.required": '联系邮箱不能为空',
"ticket.info.contactEmail.placeholder": '请输入联系邮箱',
"ticket.info.type": '票据类型',
"ticket.info.type": '票据类型:',
"ticket.info.type.required": '票据类型不能为空',
"ticket.info.type.placeholder": '请选择票据类型',
"ticket.info.attachment": '附件',
"ticket.info.dept": '部门',
"ticket.info.attachment": '附件:',
"ticket.info.dept": '部门:',
"ticket.info.dept.required": '部门不能为空',
"ticket.info.dept.placeholder": '请选择提交的部门',
"ticket.info.auditor": '审核员',
"ticket.info.auditor": '审核员:',
"ticket.info.auditor.required": '审核员不能为空',
"ticket.info.auditor.placeholder": '请选择审核员',
"ticket.info.status": '审核状态',
"ticket.info.status": '审核状态:',
"ticket.info.status.required": '审核状态不能为空',
"ticket.info.status.placeholder": '请选择审核状态',
"ticket.info.comment": '审核意见',
"ticket.info.comment": '审核意见:',
"ticketTable.columns.userName":'用户',
'upload.sucess': '上传成功',
'upload.fail': '上传失败',

View File

@ -6,7 +6,7 @@
:label-col-props="{ span: 8 }"
:wrapper-col-props="{ span: 16 }"
>
<a-form-item
<!-- <a-form-item
field="oldPassword"
:label="$t('userSetting.passwordReset.form.label.oldPassword')"
:rules="[{ required: true, message: $t('login.form.password.errMsg') }]"
@ -19,7 +19,7 @@
allow-clear
>
</a-input-password>
</a-form-item>
</a-form-item> -->
<a-form-item
field="password"

View File

@ -93,7 +93,7 @@ const fileList = ref<FileItem[]>([file]);
const Onchange = async (option: any) => {
const FormDatas = new FormData();
FormDatas.append('file', option.fileItem.file);
const res = await ticketStore.uploadFileTicket(FormDatas);
const res = await ticketStore.uploadFile(FormDatas);
if (res.status === 200) {
Message.success({
content: t('upload.sucess'),

12304
yarn.lock

File diff suppressed because it is too large Load Diff