Compare commits

...

43 Commits
feat ... song

Author SHA1 Message Date
song db889ca3f9 还element-plus 4 years ago
song 0bd7dc8f21 合并代码 4 years ago
song 6b819e6e34 添加清除浮点方法 4 years ago
Min5203 3ecb206451 表格和按钮样式调整 4 years ago
Min5203 d1a77ea0dc 表格和按钮样式调整 4 years ago
aBin 6c64449f41 时间财务统计图 4 years ago
Min5203 c51474ac2f 资源管理界面tab栏固定和发起申请按钮样式 4 years ago
song fabb3662de 上传票据 4 years ago
song 4485da585c Merge branch 'apply' of ssh://101.201.226.163:50022/DIGITAL_MANAGEMENT/finance into apply 4 years ago
song 6a05e2f8b5 上传票据 4 years ago
aBin b2373db78b 财务统计追加时id为0限制添加 4 years ago
aBin da5dccc24c 财务统计图表数据对接及表格修改 4 years ago
aBin 1255bd667f 财务统计图表数据对接及表格修改 4 years ago
Min5203 5b4043ba09 财务统计柱状图 4 years ago
Min5203 b3a6c5cb40 Merge branch 'apply' of http://101.201.226.163:3000/DIGITAL_MANAGEMENT/finance into apply 4 years ago
Min5203 081f0729f4 完成统计页面柱状图 4 years ago
song 0cf0257cb9 Merge branch 'apply' of ssh://101.201.226.163:50022/DIGITAL_MANAGEMENT/finance into apply 4 years ago
song b7c71b087e 申请+申请详情 4 years ago
Min5203 68ab2c908b 完成统计页面的饼图 4 years ago
Min5203 63935d892e 完成统计页面的饼图 4 years ago
song 3428a00d9e 申请详情 4 years ago
aBin b6f2d4006e 我的历史申请 4 years ago
aBin 3869ff242e 我的历史申请 4 years ago
song 7bc5def9b3 发起申请 4 years ago
song baede98c02 发起申请 4 years ago
song dfb457d11d 发起申请 4 years ago
Min5203 984b0bc112 Merge branch 'apply' of http://101.201.226.163:3000/DIGITAL_MANAGEMENT/finance into apply 4 years ago
Min5203 9a3097ba22 财务人员的财务审批界面 4 years ago
song ea7b92e5d7 Merge branch 'apply' of ssh://101.201.226.163:50022/DIGITAL_MANAGEMENT/finance into apply 4 years ago
song f27dd3c0cb 财务审批接口 4 years ago
wally 1ec9334d34 style: 细节调整 4 years ago
wally 804ca96528 refact: 资源管理界面根据hash滚动到目标位置 4 years ago
Min5203 ea051d3577 资源管理界面css的更改 4 years ago
Min5203 35c73910f1 Merge branch 'apply' of http://101.201.226.163:3000/DIGITAL_MANAGEMENT/finance into apply 4 years ago
Min5203 7f5c5ba8d7 更改为tailwind css 样式 4 years ago
song 7a0113348d Merge branch 'apply' of ssh://101.201.226.163:50022/DIGITAL_MANAGEMENT/finance into apply 4 years ago
song d05d4d95d1 添加锚点 4 years ago
Min5203 0434a23750 我的奖金页面 4 years ago
Min5203 7d03221bfa 我的奖金页面 4 years ago
song 095c043407 财务审批 4 years ago
Min5203 8f0bc2dca9 提出table做成组件 4 years ago
Min5203 6ef8384880 发起申请界面信息选择和个人申请完善 4 years ago
Min5203 0cdb35e9d0 财务管理和我的申请 4 years ago
  1. 1
      .env.development
  2. 1
      .env.production
  3. 1
      .env.test
  4. 42
      .eslintrc.js
  5. 56
      apis/axios.js
  6. 36
      apis/finance.js
  7. 11
      apis/index.js
  8. 8
      apis/member.js
  9. 8
      apis/ocr.js
  10. 33
      apis/projectFinance.js
  11. 26
      app.vue
  12. 0
      assets/.gitkeep
  13. 144
      components/BarEcharts.vue
  14. 122
      components/BonusCollection.vue
  15. 13
      components/Common/Hello.vue
  16. 170
      components/Expenditure.vue
  17. 151
      components/FinanceExamine.vue
  18. 151
      components/FinanceManage.vue
  19. 10
      components/Hello.vue
  20. 159
      components/HistoricalApplication.vue
  21. 198
      components/RingEcharts.vue
  22. 36
      components/Search.vue
  23. 29
      composables/state.ts
  24. 11
      layouts/default.vue
  25. 12
      nuxt.config.ts
  26. 306
      package-lock.json
  27. 5
      package.json
  28. 585
      pages/Initiate-application.vue
  29. 76
      pages/applicant.vue
  30. 224
      pages/application-details.vue
  31. 269
      pages/financial-approval-details.vue
  32. 124
      pages/financial-approval.vue
  33. 237
      pages/index.vue
  34. 15
      plugins/vant.js
  35. BIN
      public/statistics.png
  36. 15
      rest/http-client.env.json
  37. 109
      rest/财务条.http
  38. 36
      utils/num.js

1
.env.development

@ -0,0 +1 @@
VITE_API_URL=https://test.tall.wiki

1
.env.production

@ -0,0 +1 @@
VITE_API_URL=http://www.tall.wiki

1
.env.test

@ -0,0 +1 @@
VITE_API_URL=https://test.tall.wiki

42
.eslintrc.js

@ -17,5 +17,47 @@ module.exports = {
'@typescript-eslint',
],
rules: {
'import/no-unresolved': 0,
'import/extensions': 0,
'import/no-extraneous-dependencies': 0,
'no-plusplus': 0,
'no-use-before-define': [
'error',
{
functions: false,
classes: true,
variables: true,
},
],
'consistent-return': 0,
'vue/html-self-closing': 'off',
'no-unused-expressions': 'off',
'vue/no-mutating-props': 'off',
'vue/no-multiple-template-root': 'off',
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-param-reassign': 'off',
'max-len': [
'error',
{
code: 140,
tabWidth: 2,
},
],
'object-curly-newline': ['error', { multiline: true }],
'arrow-parens': ['error', 'as-needed'],
'linebreak-style': 'off',
'vue/attributes-order': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/max-attributes-per-line': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/html-indent': 'off',
'vue/html-closing-bracket-newline': [
'error',
{
singleline: 'never',
multiline: 'always',
},
],
},
};

56
apis/axios.js

@ -0,0 +1,56 @@
import Axios from 'axios';
import { ElMessage } from 'element-plus'
import { useToken } from '../composables/state'
const baseUrl = '/gateway';
const instance = Axios.create({
baseUrl,
timeout: 20000,
});
// request
instance.interceptors.request.use(
config => {
const token = useToken()
if (token.value) {
config.headers.Authorization = `Bearer ${token.value}`;
}
// config.headers.deviceId = store.state.systemInfo.deviceId;
config.headers.deviceId = '1';
config.headers.appType = 0;
return config;
},
error => {
return Promise.reject(error);
},
);
// response
instance.interceptors.response.use(
response => {
if (response.status !== 200 || !response.data) {
return Promise.reject(response.statusText);
}
const { code, data, msg } = response.data;
if (code === 200) {
return data;
}
return Promise.reject(msg);
},
error => {
if (error.response && error.response.data) {
const code = error.response.status;
const msg = error.response.data.message;
ElMessage.error(`Code: ${code}, Message: ${msg}`);
console.error(`[Axios Error]`, error.response);
} else {
// ElMessage.error(`${error}`);
}
return Promise.reject(error);
},
);
export default instance;

36
apis/finance.js

@ -0,0 +1,36 @@
import http from 'apis/axios';
const apiUrl = import.meta.env.VITE_API_URL;
const ptccsens = `${apiUrl}/ptccsens/v1.0`;
const finance = `${ptccsens}/finance`;
// 发起申请
export const apply = params => http.post(`${finance}/apply`, params);
// 审批
export const audit = params => http.post(`${finance}/audit`, params);
// 查询申请详情
export const getApplyDetail = params =>
http.post(`${finance}/getApplyDetail`, params);
// 通过任务id查看任务关联的财务信息
export const getByTask = params => http.post(`${finance}/getByTask`, params);
// 查看当前用户的费用申请历史信息(奖金)
export const personalHistory = params =>
http.post(`${finance}/personalHistory`, params);
// 查询费用申请类型
export const queryType = params => http.post(`${finance}/queryType`, params);
// 任务支出统计
export const taskExpense = params =>
http.post(`${finance}/taskExpense`, params);
// 名目支出统计
export const rowExpense = params => http.post(`${finance}/rowExpense`, params);
// 成员财务统计
export const memberFinance = params =>
http.post(`${finance}/memberFinance`, params);

11
apis/index.js

@ -0,0 +1,11 @@
import http from 'apis/axios';
const apiUrl = import.meta.env.VITE_API_URL;
const ptccsens = `${apiUrl}/gateway/tall3/v3.0`;
const users = `${ptccsens}/users`;
// 根据userId 获取token
export const getToken = userId => http.get(`${users}/userId`, { params: { userId } });
// 登录
export const signIn = params => http.post(`${users}/signin`, params);

8
apis/member.js

@ -0,0 +1,8 @@
import http from 'apis/axios';
const apiUrl = import.meta.env.VITE_API_URL;
const ptccsens = `${apiUrl}/ptccsens/v1.0`;
const member = `${ptccsens}/member`;
// 查询所有成员
export const queryChecker = params => http.post(`${member}/queryChecker`, params);

8
apis/ocr.js

@ -0,0 +1,8 @@
import http from 'apis/axios';
const apiUrl = import.meta.env.VITE_API_URL;
const ptccsens = `${apiUrl}/ptccsens/v1.0`;
const ocr = `${ptccsens}/ocr`;
// 发起申请
export const bill = `${ocr}/bill`;

33
apis/projectFinance.js

@ -0,0 +1,33 @@
import http from 'apis/axios';
const apiUrl = import.meta.env.VITE_API_URL;
const ptccsens = `${apiUrl}/ptccsens/v1.0`;
const projectFinance = `${ptccsens}/projectFinance`;
// 追加预算
export const addBudget = params =>
http.post(`${projectFinance}/addBudget`, params);
// 查看所有的费用申请
export const queryAllMoneyApply = params =>
http.post(`${projectFinance}/queryAllMoneyApply`, params);
// 查看项目下的财务信息
export const queryFinanceOfProject = params =>
http.post(`${projectFinance}/queryFinanceOfProject`, params);
// 查看自己需要审批的申请
export const queryNeedCheckByMe = params =>
http.post(`${projectFinance}/queryNeedCheckByMe`, params);
// 查看项目下的所有任务对应的财务信息
export const queryProjectFinance = params =>
http.post(`${projectFinance}/queryProjectFinance`, params);
// 修改任务或项目的预算和奖金信息
export const updateFinance = params =>
http.post(`${projectFinance}/updateFinance`, params);
// 时间财务图统计
export const timeFinancialChart = params =>
http.post(`${projectFinance}/timeFinancialChart`, params);

26
app.vue

@ -1,3 +1,29 @@
<template>
<NuxtPage />
</template>
<script setup>
import { reactive } from 'vue';
import { useRouter } from 'vue-router';
const router = useRouter();
const token = useToken()
const user = useUser()
const userId = useUserId()
const projectId = useProjectId()
const taskId = useTaskId()
const projectName = useProjectName()
const taskName = useTaskName()
//
const routeValue = router.currentRoute.value;
console.log('routeValue: ', routeValue);
token.value = routeValue && routeValue.query && routeValue.query.token ? routeValue.query.token : '';
userId.value = routeValue && routeValue.query && routeValue.query.userId ? routeValue.query.userId : '';
projectId.value = routeValue && routeValue.query && routeValue.query.projectId ? routeValue.query.projectId : '';
taskId.value = routeValue && routeValue.query && routeValue.query.id ? routeValue.query.id : '';
projectName.value = routeValue && routeValue.query && routeValue.query.pn ? routeValue.query.pn : '';
taskName.value = routeValue && routeValue.query && routeValue.query.tn ? routeValue.query.tn : '';
</script>

0
assets/.gitkeep

144
components/BarEcharts.vue

@ -0,0 +1,144 @@
<template>
<div id="barEcharts" style="width: 380px; height: 350px"></div>
</template>
<script setup>
import { onMounted } from 'vue';
import { timeFinancialChart } from 'apis/projectFinance';
const projectId = useProjectId();
const data = reactive({
timeList: [],
taskNameList: [],
});
async function getChartData() {
try {
const params = { param: { projectId: projectId.value } };
const res = await timeFinancialChart(params);
console.log('res:', res);
return processing(res);
} catch (error) {
console.error(error);
}
}
function processing(list) {
let timeList = [
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
];
for (let i = 0; i < list.length; i++) {
for (let j = 0; j < list[i].data.length; j++) {
const m = list[i].data[j].time - 0;
timeList[m - 1] = m + '月';
}
}
for (let i = 0; i < timeList.length; i++) {
if (!timeList[i]) {
timeList.splice(i, 1);
i -= 1;
}
}
let series = [];
let taskNameList = [];
for (let i = 0; i < list.length; i++) {
let data = [];
taskNameList.push(list[i].name);
for (let k = 0; k < timeList.length; k++) {
data.push(null);
}
for (let k = 0; k < list[i].data.length; k++) {
for (let m = 0; m < timeList.length; m++) {
if (list[i].data[k].time - 0 + '月' === timeList[m]) {
data[m] = list[i].data[k].expend - 0;
}
}
}
let obj = {
name: list[i].name,
type: 'bar',
stack: 'total',
label: {
show: false,
color: '#FFFFFF',
},
barWidth: 12,
data: data,
};
series.push(obj);
}
data.timeList = timeList;
data.taskNameList = taskNameList;
console.log('series: ', series);
return series;
}
onMounted(async () => {
const myChart = echarts.init(document.getElementById('barEcharts'));
const series = await getChartData();
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
},
legend: {
top: 10,
right: 0,
icon: 'circle',
orient: 'horizontal',
itemGap: 10,
itemWidth: 10,
itemHeight: 14,
textStyle: {
fontSize: 14,
color: '#858585',
fontWeight: 400,
},
},
data: data.taskNameList,
color: ['#7E84A3', '#FF914C', '#5189F8', '#3FC7BB'],
grid: {
left: '15%',
right: '0',
bottom: '5%',
containLabel: false,
},
xAxis: {
type: 'category',
data: data.timeList,
axisTick: {
show: false,
},
axisLabel: {
show: true,
},
axisLine: {
show: false,
},
},
yAxis: {
type: 'value',
splitLine: {
lineStyle: {
color: '#F3F4F5',
},
},
},
series: series,
};
myChart.setOption(option);
});
</script>

122
components/BonusCollection.vue

@ -0,0 +1,122 @@
<template>
<div>
<div
v-if="data.info.list && data.info.list.length"
class="w-full overflow-x-scroll"
>
<table class="text-gray-500 mt-4 text-ms">
<tr class="bg-gray-100 text-gray-400">
<td class="name">申请人</td>
<td class="money">金额()</td>
<td class="time">时间</td>
<td class="remark">备注</td>
</tr>
<tr v-for="item in data.info.list" class="text-gray-500">
<td>{{ item.submitName }}</td>
<td>
{{ (+item.money / 100).toFixed(2) }}
</td>
<td>
{{ dayjs(item.submitTime - 0).format('YYYY/MM/DD') }}
</td>
<td>
{{ item.remark }}
</td>
</tr>
</table>
<div class="w-1/2 mt-4 ml-48">
<el-pagination
v-model="data.pageNum"
:items-per-page="data.pageSize"
:page-count="data.pages"
mode="simple"
/>
</div>
</div>
<el-empty v-else description="暂无数据" />
</div>
</template>
<script setup>
import dayjs from 'dayjs';
import { personalHistory } from 'apis/finance';
import { ref, reactive, onMounted, nextTick } from 'vue';
const data = reactive({
info: {},
pageNum: 1,
pageSize: 10,
pages: 0,
});
const projectId = useProjectId();
const taskDetailId = useTaskId();
const taskName = useTaskName();
/**
* 查看当前用户的费用申请历史信息奖金
* @param { Number } pageNum
* @param { Number } pageSize
* @param { String } projectId
* @param { String } name
*/
async function handlePersonalHistory() {
try {
const params = {
param: {
pageNum: data.pageNum,
pageSize: data.pageSize,
projectId: projectId.value,
taskDetailId: taskDetailId.value,
taskName: taskName.value,
type: 1,
},
};
const res = await personalHistory(params);
data.info = res;
data.pageNum = res.pageNum ? +res.pageNum : 1;
data.pageSize = res.pageSize ? +res.pageSize : 10;
data.pages = res.pages ? +res.pages : 0;
} catch (error) {
console.error('error: ', error);
}
}
function confirm() {
alert('确认放款');
}
onMounted(() => {
nextTick(() => {
handlePersonalHistory();
});
});
</script>
<style scoped lang="less">
table {
width: 500px;
td {
border: 0.5px solid #ccc;
padding: 0.85rem;
width: 5.9375rem;
}
.name {
width: 100px;
}
.money {
width: 100px;
}
.time {
width: 170px;
}
.remark {
width: 120px;
}
}
.input-box {
padding: 0 !important;
border-bottom: 1px solid #ccc;
}
</style>

13
components/Common/Hello.vue

@ -1,13 +0,0 @@
<template>
<h1>
Common Hello.vue <br /><small>{{ modalDisplay }}</small>
<van-button type="primary" @click="modalDisplay = !modalDisplay">
主要按钮
</van-button>
</h1>
</template>
<script setup>
const modalDisplay = useModal();
console.log(modalDisplay.value);
</script>

170
components/Expenditure.vue

@ -0,0 +1,170 @@
<template>
<div v-if="data.info.length">
<div class="w-full overflow-x-scroll">
<table class="text-gray-500 mt-4 text-xs" v-if="props.id === 'taskTable'">
<tr class="bg-gray-100">
<td width="20%">任务名称</td>
<td width="16%">支出</td>
<td width="16%">占比</td>
<td width="16%">追加</td>
<td width="16%">操作</td>
</tr>
<tr v-for="item in data.info">
<td>{{ item.taskName }}</td>
<td>{{ item.money }}</td>
<td>{{ item.percentage }}%</td>
<td>
<div v-if="!item.showField" @click="item.showField = true">
{{ item.budget - 0 }}
</div>
<el-field
v-else-if="financeId - 0 !== 0 && item.showField"
v-model="item.budget"
type="number"
class="input-box"
@change="handleUpdateBudget(item)"
@blur="item.showField = false"
/>
</td>
<td>
<el-icon name="plus" @click="toApplication(item)" />
</td>
</tr>
</table>
<table class="text-gray-500 mt-4 text-xs" v-if="props.id === 'nameTable'">
<tr class="bg-gray-100">
<td width="15%">任务名称</td>
<td width="16%">预算()</td>
<td width="16%">占比()</td>
</tr>
<tr v-for="item in data.info">
<td>{{ item.rowName }}</td>
<td>{{ item.percentage }}</td>
<td>{{ item.money }}</td>
</tr>
</table>
</div>
<div class="w-1/2 mt-4 ml-48">
<el-pagination
v-model="data.pageNum"
:items-per-page="data.pageSize"
:page-count="data.pages"
mode="simple"
/>
</div>
</div>
<el-empty v-else description="暂无数据" />
<el-dialog
v-model:show="data.show"
title="追加预算"
show-cancel-button
@confirm="handleAdd"
>
<el-field
:border="data.border"
v-model="data.appendBudget"
type="textarea"
class="appendBudget"
placeholder="追加预算"
/>
</el-dialog>
</template>
<script setup>
import { ref, reactive, onMounted, nextTick } from 'vue';
import { taskExpense, rowExpense } from 'apis/finance';
import { addBudget } from 'apis/projectFinance';
const projectId = useProjectId();
const router = useRouter();
const props = defineProps({
id: { type: Object, default: () => {} },
});
console.log('props.id: ', props.id);
const data = reactive({
info: [],
pageNum: 1,
pageSize: 10,
pages: 0,
show: false,
appendBudget: '',
auditInfo: {},
border: true,
});
//
async function handleUpdateBudget(item) {
try {
const params = {
param: {
appendBudget: item.budget - 0,
financeId: item.financeId,
projectId: projectId.value,
},
};
await addBudget(params);
} catch (error) {
console.error('error: ', error);
}
}
// ,
function toApplication(item) {
const routeValue = router.currentRoute.value;
const query = routeValue.query;
query.tn = item.taskName;
// console.log('query: ', query);
router.push({ path: '/Initiate-application', query });
}
// echarts()
async function getTaskExpense() {
const params = { param: { projectId: projectId.value } };
const res = await taskExpense(params);
for (let i = 0; i < res.length; i++) {
res.showField = false;
}
data.info = res;
}
// echarts()
async function getRowExpense() {
const params = { param: { projectId: projectId.value } };
const res = await rowExpense(params);
data.info = res;
}
function setData() {
if (props.id === 'taskTable') {
getTaskExpense();
} else if (props.id === 'nameTable') {
getRowExpense();
}
}
onMounted(() => {
nextTick(() => {
// handleFinanceOfProject();
setData();
});
});
</script>
<style scoped lang="less">
table {
width: 120%;
td {
border: 0.5px solid #ccc;
padding: 0.5rem;
}
}
.input-box {
padding: 0 !important;
border-bottom: 1px solid #ccc;
}
.appendBudget {
border: 1px solid #ccc;
border-radius: 4px;
margin: 5%;
width: 90%;
}
</style>

151
components/FinanceExamine.vue

@ -0,0 +1,151 @@
<template>
<div v-if="data.info.list && data.info.list.length">
<div class="w-full overflow-x-scroll">
<table class="text-gray-500 mt-4">
<tr class="bg-gray-100">
<td width="20%">申请人</td>
<td width="25%">金额()</td>
<td width="30%">时间</td>
<td width="25%">操作</td>
</tr>
<tr v-for="item in data.info.list">
<td @click="openDetails(item.applyId)">{{item.submitName}}</td>
<td @click="openDetails(item.applyId)">{{item.money}}</td>
<td @click="openDetails(item.applyId)">{{dayjs(item.submitTime - 0).format('YYYY-MM-DD')}}</td>
<td>
<div v-if="!item.applyType" class="flex flex-row justify-around">
<el-button type="success" size="mini" class="rounded" @click="showRemark(item.financeCheckId, 1)">通过</el-button>
<el-button type="danger" size="mini" class="rounded" @click="showRemark(item.financeCheckId, 2)">驳回</el-button>
</div>
<div class="text-center" v-else :class="item.applyType === 1 ? 'text-blue-500' : 'text-red-500'">{{ item.applyType === 1 ? '已完成' : '已驳回' }}</div>
</td>
</tr>
</table>
</div>
<div class="w-1/2 mt-4 ml-48">
<el-pagination v-model="data.pageNum" :items-per-page="data.pageSize" :page-count="data.pages" mode="simple" />
</div>
</div>
<el-empty v-else description="暂无数据" />
<el-dialog v-model:show="data.show" title="备注" show-cancel-button @confirm="handleAudit">
<!-- <el-field :border="data.border" v-model="data.remark" type="textarea" class="remark" placeholder="请输入备注" /> -->
</el-dialog>
</template>
<script setup>
import dayjs from "dayjs";
import {ref, reactive, onMounted, nextTick} from'vue';
import { audit } from 'apis/finance';
import { queryNeedCheckByMe } from 'apis/projectFinance';
import { ElMessage } from 'element-plus'
import { useRouter } from 'vue-router';
const router = useRouter()
const data = reactive({
info: {},
pageNum: 1,
pageSize: 10,
pages: 0,
show: false,
remark: '',
auditInfo: {},
border: true
})
const taskId = useTaskId()
/**
* 查看项目下的所有任务对应的财务信息
* @param { Number } pageNum
* @param { Number } pageSize
* @param { String } taskDetailId 任务详情id
*/
async function handleFinanceOfProject(){
try {
const params = {
param:{
pageNum: data.pageNum,
pageSize: data.pageSize,
taskDetailId: taskId.value
}
}
const res = await queryNeedCheckByMe(params)
data.info = res
data.pageNum = res && res.pageNum ? +res.pageNum : 1
data.pageSize = res && res.pageSize ? +res.pageSize : 10
data.pages = res && res.pages ? +res.pages : 0
} catch (error) {
console.error('error: ', error);
}
}
onMounted(() => {
nextTick(() => {
handleFinanceOfProject()
})
})
//
function showRemark(financeCheckId, checkStatus){
data.show = true
data.auditInfo = { financeCheckId, checkStatus }
}
/**
* 审批
* @param { Number } checkStatus 审核状态 1已通过 2驳回
* @param { String } financeCheckId 审核id
* @param { String } remark 备注
*/
async function handleAudit(financeCheckId, checkStatus){
try {
const { auditInfo, remark } = data
const { financeCheckId, checkStatus } = data.auditInfo
const params = {
param:{
financeCheckId,
checkStatus,
remark: data.remark
}
}
await audit(params)
ElMessage.success('审批成功')
handleFinanceOfProject()
data.remark = ''
} catch (error) {
console.error('error: ', error);
}
}
//
function openDetails(applyId){
const routeValue = router.currentRoute.value;
const query = routeValue.query;
query.applyId = applyId;
router.push({ path: '/financial-approval-details', query });
}
</script>
<style scoped lang="less">
table{
width: 120%;
td{
border: 0.5px solid #ccc;
padding: 0.5rem;
}
}
.input-box{
padding: 0!important;
border-bottom: 1px solid #ccc
}
.remark{
border: 1px solid #ccc;
border-radius: 4px;
margin: 5%;
width: 90%;
}
</style>

151
components/FinanceManage.vue

@ -0,0 +1,151 @@
<template>
<div>
<div v-if="data.info.taskFinanceList && data.info.taskFinanceList.list && data.info.taskFinanceList.list.length">
<table class="w-full text-gray-500 mt-4 text-ms">
<tr class="bg-gray-100">
<td class="name">任务名称</td>
<td>预算()</td>
<td>奖金()</td>
</tr>
<tr v-for="item in data.info.taskFinanceList.list">
<td>{{item.name}}</td>
<td>
<div v-if="!item.showBudgetEdit" @click="editBudge(item)">{{ item.budget }}</div>
<el-field v-else v-model="item.budget" type="number" class="input-box" @change="handleUpdateFinance(item, 'budget')" @blur="item.showBudgetEdit = false" />
</td>
<td>
<div v-if="!item.showBonusEdit" @click="editBonus(item)">{{ item.bonus }}</div>
<el-field v-else v-model="item.bonus" type="number" class="input-box" @change="handleUpdateFinance(item, 'bonus')" @blur="item.showBonusEdit = false" />
</td>
</tr>
<tr>
<td>合计</td>
<td>{{ num.except(+data.info.budget,100) || 0 }}</td>
<td>{{ num.except(+data.info.bonus,100) || 0 }}</td>
</tr>
</table>
<div class="w-1/2 mt-4 ml-48">
<el-pagination v-model="data.pageNum" :items-per-page="data.pageSize" :page-count="data.pages" mode="simple" />
</div>
</div>
<el-empty v-else description="暂无数据" />
</div>
</template>
<script setup>
import { queryProjectFinance, updateFinance } from 'apis/projectFinance'
import {ref, reactive, onMounted, nextTick} from'vue'
import num from 'utils/num';
const data = reactive({
info: {},
pageNum: 1,
pageSize: 10,
pages: 0,
budget: 0,
bonus: 0,
})
const projectId = useProjectId()
//
function editBudge(item){
data.budget = num.except(+item.budget,100)
data.info.taskFinanceList.list.forEach((list) => {
if(item.taskFinanceId === list.taskFinanceId){
list.showBudgetEdit = true
}else{
list.showBudgetEdit = false
}
})
}
//
function editBonus(item){
data.bonus = num.except(+item.bonus,100)
data.info.taskFinanceList.list.forEach((list) => {
if(item.taskFinanceId === list.taskFinanceId){
list.showBonusEdit = true
}else{
list.showBonusEdit = false
}
})
}
/**
* 查看项目下的所有任务对应的财务信息
* @param { Number } pageNum
* @param { Number } pageSize
* @param { String } projectId
* @param { String } name
*/
async function handleProjectFinance(){
try {
const params = {
param:{
pageNum: data.pageNum,
pageSize: data.pageSize,
projectId: projectId.value
}
}
const res = await queryProjectFinance(params)
data.info = res
data.pageNum = res.taskFinanceList && res.taskFinanceList.pageNum ? +res.taskFinanceList.pageNum : 1
data.pageSize = res.taskFinanceList && res.taskFinanceList.pageSize ? +res.taskFinanceList.pageSize : 10
data.pages = res.taskFinanceList && res.taskFinanceList.pages ? +res.taskFinanceList.pages : 0
} catch (error) {
console.error('error: ', error);
}
}
/**
* 修改任务或项目的预算和奖金信息
* @param { Number } bonus 奖金
* @param { Number } budget 预算
* @param { String } taskDetailId 任务详情id
* @param { String } taskFinanceId 任务财务信息id
*/
async function handleUpdateFinance(item, type){
try {
console.log('item, type: ', item, type, data[type], item[type]);
item[type] = data[type]
console.log('item: ', item.bonus);
const params = {
param:{
// bonus: num.ride(+item.bonus,100),
// budget: num.ride(+item.budget,100),
taskDetailId: item.taskDetailId,
taskFinanceId: item.taskFinanceId,
}
}
const res = await updateFinance(params)
handleProjectFinance()
} catch (error) {
console.error('error: ', error);
}
}
onMounted(() => {
nextTick(() => {
handleProjectFinance()
})
})
</script>
<style scoped lang="less">
table {
td {
border: 0.5px solid #ccc;
padding: 0.85rem;
width: 5.9375rem;
}
.name{
width:50%
}
}
.input-box{
padding: 0!important;
border-bottom: 1px solid #ccc
}
</style>

10
components/Hello.vue

@ -1,10 +0,0 @@
<template>
<div>
Hello
<h2>{{ modalDisplay }}</h2>
</div>
</template>
<script setup>
const modalDisplay = useModal();
</script>

159
components/HistoricalApplication.vue

@ -0,0 +1,159 @@
<template>
<div>
<div
v-if="data.info.list && data.info.list.length"
class="w-full overflow-x-scroll"
>
<table class="text-gray-500 mt-4 text-ms">
<tr class="bg-gray-100 text-gray-400">
<td class="name">申请人</td>
<td class="money">金额()</td>
<td class="time">时间</td>
<td class="status">状态</td>
</tr>
<tr v-for="item in data.info.list" class="text-gray-500">
<td @click="openDetails(item.applyId)">{{ item.submitName }}</td>
<td @click="openDetails(item.applyId)">
{{ num.except(+item.money, 100) }}
</td>
<td @click="openDetails(item.applyId)">
{{ dayjs(item.submitTime - 0).format('YYYY/MM/DD') }}
</td>
<td>
<!-- <div v-if="!item.showBonusEdit" @click="item.showBonusEdit = true"> -->
<span v-if="item.applyType - 0 === 0" class="text-gray-500">
待审核
</span>
<span v-else-if="item.applyType - 0 === 1" class="text-gray-500">
已通过
</span>
<span v-else-if="item.applyType - 0 === 2" class="text-red-500">
已驳回
</span>
<span v-else-if="item.applyType - 0 === 3" class="text-gray-500">
待放款
</span>
<el-button
v-else-if="item.applyType - 0 === 4"
type="success"
size="mini"
class="rounded"
@click="confirm"
>
确认
</el-button>
<span v-else-if="item.applyType - 0 === 5" class="text-green-500">
已确认
</span>
</td>
</tr>
</table>
<div class="w-1/2 mt-4 ml-48">
<el-pagination
v-model="data.pageNum"
:items-per-page="data.pageSize"
:page-count="data.pages"
mode="simple"
/>
</div>
</div>
<el-empty v-else description="暂无数据" />
</div>
</template>
<script setup>
import dayjs from 'dayjs';
import { personalHistory } from 'apis/finance';
import { ref, reactive, onMounted, nextTick } from 'vue';
import { useRouter } from 'vue-router';
import num from 'utils/num';
const router = useRouter();
const data = reactive({
info: {},
pageNum: 1,
pageSize: 10,
pages: 0,
});
const projectId = useProjectId();
const taskDetailId = useTaskId();
const taskName = useTaskName();
/**
* 查看当前用户的费用申请历史信息奖金
* @param { Number } pageNum
* @param { Number } pageSize
* @param { String } projectId
* @param { String } name
*/
async function handlePersonalHistory() {
try {
const params = {
param: {
pageNum: data.pageNum,
pageSize: data.pageSize,
projectId: projectId.value,
taskDetailId: taskDetailId.value,
taskName: taskName.value,
type: 0,
},
};
const res = await personalHistory(params);
data.info = res;
data.pageNum = res.pageNum ? +res.pageNum : 1;
data.pageSize = res.pageSize ? +res.pageSize : 10;
data.pages = res.pages ? +res.pages : 0;
} catch (error) {
console.error('error: ', error);
}
}
function confirm() {
alert('确认放款');
}
onMounted(() => {
nextTick(() => {
handlePersonalHistory();
});
});
//
function openDetails(applyId){
const routeValue = router.currentRoute.value;
const query = routeValue.query;
query.applyId = applyId;
router.push({ path: '/application-details', query });
}
</script>
<style scoped lang="less">
table {
width: 500px;
td {
border: 0.5px solid #ccc;
padding: 0.85rem;
width: 5.9375rem;
}
.name {
width: 100px;
}
.money {
width: 100px;
}
.time {
width: 120px;
}
.status {
width: 120px;
}
}
.input-box {
padding: 0 !important;
border-bottom: 1px solid #ccc;
}
</style>

198
components/RingEcharts.vue

@ -0,0 +1,198 @@
<template>
<!-- <div id="yield-chart"></div> -->
<div :id="props.id" style="width: 400px; height: 260px"></div>
</template>
<script setup>
import { onMounted } from 'vue';
import { taskExpense, rowExpense, memberFinance } from 'apis/finance';
const projectId = useProjectId();
const props = defineProps({ id: { type: String, default: () => {} } });
let oData = [
{
name: '办公费',
value: 36,
rate: 12,
},
{
name: '车辆费用',
value: 20,
rate: 20,
},
{
name: '差旅费33',
value: 15,
rate: -40,
},
{
name: '租赁费',
value: 10,
rate: -15,
},
{
name: '其他',
value: 9,
rate: 12,
},
];
// echarts()
async function getTaskExpense() {
const params = { param: { projectId: projectId.value } };
const res = await taskExpense(params);
return changeData(res, 'taskName');
}
// echarts()
async function getRowExpense() {
const params = { param: { projectId: projectId.value } };
const res = await rowExpense(params);
return changeData(res, 'rowName');
}
// echarts()
async function getMemberFinance() {
const params = { param: { projectId: projectId.value } };
const res = await memberFinance(params);
return changeData(res, 'memberName');
}
// optionData
function changeData(list, name) {
let optionsData = [];
for (let i = 0; i < list.length; i++) {
const data = {
name: list[i][name],
value: list[i].money - 0,
percentage: list[i].percentage,
};
optionsData.push(data);
}
return optionsData;
}
function setData() {
if (props.id === 'taskEcharts') {
return getTaskExpense();
} else if (props.id === 'nameEcharts') {
return getRowExpense();
} else if (props.id === 'memberEcharts') {
return getMemberFinance();
} else {
return oData;
}
}
function getTitleNum(list) {
let num = 0;
for (let i = 0; i < list.length; i++) {
num += list[i].value;
}
return num;
}
onMounted(async () => {
if (props.id) {
var myChart = echarts.init(document.getElementById(props.id));
}
const data = await setData();
const title = getTitleNum(data);
const option = {
title: {
text: title,
textStyle: {
fontSize: 17,
color: 'black',
},
textAlign: 'center',
x: '24%',
y: '35%',
},
legend: {
type: 'plain',
icon: 'circle',
orient: 'vertical',
left: '55%',
top: '15%',
align: 'left',
itemGap: 15,
itemWidth: 10, //
itemHeight: 10, //
symbolKeepAspect: false,
textStyle: {
color: '#000',
rich: {
name: {
verticalAlign: 'right',
align: 'left',
width: 50,
fontSize: 12,
},
value: {
align: 'left',
width: 40,
fontSize: 12,
},
count: {
align: 'left',
width: 80,
fontSize: 12,
},
},
},
data: data.map(item => item.name),
formatter: function (name) {
if (data && data.length) {
for (var i = 0; i < data.length; i++) {
if (name === data[i].name) {
return (
'{name| ' +
name +
'} | ' +
'{value| ' +
data[i].percentage +
'%}' +
'{count| ' +
data[i].value +
'} '
);
}
}
}
},
},
series: [
{
name: '数量',
type: 'pie',
radius: ['40%', '55%'],
center: ['25%', '40%'],
data: data,
label: {
normal: {
show: false,
position: 'center',
formatter: '{text|{c}}\n{b}',
rich: {
text: {
align: 'center',
verticalAlign: 'middle',
padding: 8,
fontSize: 30,
},
value: {
align: 'center',
verticalAlign: 'middle',
fontSize: 20,
},
},
},
},
labelLine: {
normal: {
show: true,
},
},
},
],
};
myChart.setOption(option);
});
</script>
<style scoped></style>

36
components/Search.vue

@ -0,0 +1,36 @@
<template>
<div class="pt-4">
<el-input
v-model="searchRef"
placeholder="搜索"
:prefix-icon="Search"
></el-input>
</div>
</template>
<script setup>
import {ref} from 'vue'
const searchRef = ref('')
</script>
<style lang="less">
.el-search{
padding: 0;
}
.el-search__content{
border: 1px solid #ccc;
background-color: #fff;
height:1.85rem;
border-radius:0.2rem
}
.el-field__left-icon{
margin-right: var(--el-padding-base);
width: 45%;
padding-left: 40%;
}
</style>

29
composables/state.ts

@ -0,0 +1,29 @@
import { useState } from '#app';
export const useToken = () => {
return useState('token', () => '');
};
export const useUserId = () => {
return useState('userId', () => '');
};
export const useProjectId = () => {
return useState('projectId', () => '');
};
export const useUser = () => {
return useState('user', () => null);
};
export const useTaskId = () => {
return useState('taskId', () => null);
};
export const useProjectName = () => {
return useState('projectName', () => null);
};
export const useTaskName = () => {
return useState('taskName', () => null);
};

11
layouts/default.vue

@ -1,5 +1,14 @@
<template>
<div>
<div class="container">
<slot />
</div>
</template>
<style lang="less">
.container{
background:#eee;
height:100%;
width:100%;
overflow:hidden;
}
</style>

12
nuxt.config.ts

@ -10,6 +10,9 @@ export default defineNuxtConfig({
components: resolve(__dirname, './components'),
pages: resolve(__dirname, './pages'),
hooks: resolve(__dirname, './hooks'),
apis: resolve(__dirname, './apis'),
public: resolve(__dirname, './public'),
utils: resolve(__dirname, './utils'),
},
meta: {
meta: [
@ -20,7 +23,14 @@ export default defineNuxtConfig({
rel: 'stylesheet',
href: 'https://cdn.bootcdn.net/ajax/libs/normalize/8.0.1/normalize.min.css',
},
{
rel:"stylesheet",
href:"https://cdn.bootcdn.net/ajax/libs/tailwindcss/2.2.19/tailwind.min.css",
},
],
script: [
{ src: 'https://cdn.bootcdn.net/ajax/libs/dayjs/1.10.7/dayjs.min.js' },
{src:'https://cdn.bootcdn.net/ajax/libs/echarts/5.2.2/echarts.common.js'}
],
script: [{ src: '' }],
},
});

306
package-lock.json

@ -1,5 +1,5 @@
{
"name": "plugin-finance",
"name": "finance",
"lockfileVersion": 2,
"requires": true,
"packages": {
@ -7,18 +7,21 @@
"dependencies": {
"axios": "^0.25.0",
"dayjs": "^1.10.7",
"element-plus": "^1.3.0-beta.9",
"lodash": "^4.17.21",
"vant": "^3.4.2"
"vuex": "^4.0.2"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^5.10.0",
"autoprefixer": "^10.4.2",
"eslint": "^8.7.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-vue": "^8.3.0",
"less": "^4.1.2",
"nuxt3": "latest",
"postcss": "^8.4.5",
"prettier": "^2.5.1"
}
},
@ -541,6 +544,22 @@
"mime": "^3.0.0"
}
},
"node_modules/@ctrl/tinycolor": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz",
"integrity": "sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ==",
"engines": {
"node": ">=10"
}
},
"node_modules/@element-plus/icons-vue": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-0.2.6.tgz",
"integrity": "sha512-2gg7VCq4d2firgl7/aVym4Cx/wqKFwKybEQGJiiWJN4urW36+QdAEG1knqSD9qidbjhVp0Jnc9XdSTR1/4Whzw==",
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/@eslint/eslintrc": {
"version": "1.0.5",
"resolved": "https://registry.npmmirror.com/@eslint/eslintrc/download/@eslint/eslintrc-1.0.5.tgz",
@ -1020,8 +1039,12 @@
},
"node_modules/@popperjs/core": {
"version": "2.11.2",
"resolved": "https://registry.npmmirror.com/@popperjs/core/download/@popperjs/core-2.11.2.tgz",
"integrity": "sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA=="
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz",
"integrity": "sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@rollup/plugin-alias": {
"version": "3.1.9",
@ -1567,24 +1590,6 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@vant/icons": {
"version": "1.7.3",
"resolved": "https://registry.npmmirror.com/@vant/icons/download/@vant/icons-1.7.3.tgz",
"integrity": "sha512-tW4EqzxN4kXw1rnlnQJQHofEifPbt/gECOWiibomht8QLyvoGuE4iUmDFS288dJ07ZjuTy0bhdABj0SENo2fmQ=="
},
"node_modules/@vant/popperjs": {
"version": "1.1.0",
"resolved": "https://registry.npm.taobao.org/@vant/popperjs/download/@vant/popperjs-1.1.0.tgz",
"integrity": "sha1-tO3uW7+m+xhwWYbjE9T9XxeUKg8=",
"dependencies": {
"@popperjs/core": "^2.9.2"
}
},
"node_modules/@vant/use": {
"version": "1.3.4",
"resolved": "https://registry.npmmirror.com/@vant/use/download/@vant/use-1.3.4.tgz?cache=0&sync_timestamp=1637544063803&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40vant%2Fuse%2Fdownload%2F%40vant%2Fuse-1.3.4.tgz",
"integrity": "sha512-XvZkPCjcmEBhD+T3vB68thOG6P9jazld6aBTMenhbAQd4FT/x9AiKIWPJx4MvhYoSIWt7fju6K01XTJldWs1hw=="
},
"node_modules/@vercel/nft": {
"version": "0.17.3",
"resolved": "https://registry.npmmirror.com/@vercel/nft/download/@vercel/nft-0.17.3.tgz",
@ -1883,8 +1888,7 @@
"node_modules/@vue/devtools-api": {
"version": "6.0.0-beta.21.1",
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/download/@vue/devtools-api-6.0.0-beta.21.1.tgz",
"integrity": "sha512-FqC4s3pm35qGVeXRGOjTsRzlkJjrBLriDS9YXbflHLsfA9FrcKzIyWnLXoNm+/7930E8rRakXuAc2QkC50swAw==",
"dev": true
"integrity": "sha512-FqC4s3pm35qGVeXRGOjTsRzlkJjrBLriDS9YXbflHLsfA9FrcKzIyWnLXoNm+/7930E8rRakXuAc2QkC50swAw=="
},
"node_modules/@vue/reactivity": {
"version": "3.2.27",
@ -1942,6 +1946,78 @@
"resolved": "https://registry.npmmirror.com/@vue/shared/download/@vue/shared-3.2.27.tgz",
"integrity": "sha512-rpAn9k6O08Lvo7ekBIAnkOukX/4EsEQLPrRJBKhIEasMsOI5eX0f6mq1sDUSY7cgAqWw2d7QtP74CWxdXoyKxA=="
},
"node_modules/@vueuse/core": {
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-7.5.5.tgz",
"integrity": "sha512-RBDqmIoGfak4h3xdXa/Av+ibkb8NY044wEy6+PG2FAWNaID8/FkqmSFjbxogrbmpSX1yZ1PBHrM8DUp/FrIpbg==",
"dependencies": {
"@vueuse/shared": "7.5.5",
"vue-demi": "*"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.1.0",
"vue": "^2.6.0 || ^3.2.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
},
"vue": {
"optional": true
}
}
},
"node_modules/@vueuse/core/node_modules/@vueuse/shared": {
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-7.5.5.tgz",
"integrity": "sha512-mzzTsotHQRPnPAChy8iCv6ek/90CKYhAFyMRgNsMxpT0afZJkbMO/X0OaOu/1NuGbgb8UVjlsWKmCUgKTOF5hA==",
"dependencies": {
"vue-demi": "*"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.1.0",
"vue": "^2.6.0 || ^3.2.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
},
"vue": {
"optional": true
}
}
},
"node_modules/@vueuse/core/node_modules/vue-demi": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.12.1.tgz",
"integrity": "sha512-QL3ny+wX8c6Xm1/EZylbgzdoDolye+VpCXRhI2hug9dJTP3OUJ3lmiKN3CsVV3mOJKwFi0nsstbgob0vG7aoIw==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/@vueuse/head": {
"version": "0.7.5",
"resolved": "https://registry.npmmirror.com/@vueuse/head/download/@vueuse/head-0.7.5.tgz",
@ -2437,9 +2513,14 @@
"integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==",
"dev": true
},
"node_modules/async-validator": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.0.7.tgz",
"integrity": "sha512-Pj2IR7u8hmUEDOwB++su6baaRi+QvsgajuFB9j95foM1N2gy5HM4z60hfusIO0fBPG5uLAEl6yCJr1jNSVugEQ=="
},
"node_modules/autoprefixer": {
"version": "10.4.2",
"resolved": "https://registry.npmmirror.com/autoprefixer/download/autoprefixer-10.4.2.tgz",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.2.tgz",
"integrity": "sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ==",
"dev": true,
"dependencies": {
@ -2456,6 +2537,10 @@
"engines": {
"node": "^10 || ^12 || >=14"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
"peerDependencies": {
"postcss": "^8.1.0"
}
@ -3586,6 +3671,25 @@
"integrity": "sha512-k/0t1TRfonHIp8TJKfjBu2cKj8MqYTiEpOhci+q7CVEE5xnCQnx1pTa+V8b/sdhe4S3PR4p4iceEQWhGrKQORQ==",
"dev": true
},
"node_modules/element-plus": {
"version": "1.3.0-beta.9",
"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-1.3.0-beta.9.tgz",
"integrity": "sha512-h+I6VVtJNwjVkNhfhXp2oCYjgBN7kWQ2Y6nWzwc0wiphAlQDhGuBXAGj9eYpf1uOrMgIpFFwbRhvK7C6sOJsWg==",
"dependencies": {
"@ctrl/tinycolor": "^3.4.0",
"@element-plus/icons-vue": "^0.2.6",
"@popperjs/core": "^2.11.2",
"@vueuse/core": "^7.5.4",
"async-validator": "^4.0.7",
"dayjs": "^1.10.7",
"lodash": "^4.17.21",
"memoize-one": "^6.0.0",
"normalize-wheel-es": "^1.1.1"
},
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmmirror.com/emoji-regex/download/emoji-regex-8.0.0.tgz?cache=0&sync_timestamp=1632751408145&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Femoji-regex%2Fdownload%2Femoji-regex-8.0.0.tgz",
@ -6316,6 +6420,11 @@
"node": ">= 4.0.0"
}
},
"node_modules/memoize-one": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
},
"node_modules/memory-fs": {
"version": "0.5.0",
"resolved": "https://registry.nlark.com/memory-fs/download/memory-fs-0.5.0.tgz",
@ -6957,6 +7066,11 @@
"node": ">=10"
}
},
"node_modules/normalize-wheel-es": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.1.1.tgz",
"integrity": "sha512-157VNH4CngrcsvF8xOVOe22cwniIR3nxSltdctvQeHZj8JttEeOXffK28jucWfWBXs0QNetAumjc1GiInnwX4w=="
},
"node_modules/npm-bundled": {
"version": "1.1.2",
"resolved": "https://registry.nlark.com/npm-bundled/download/npm-bundled-1.1.2.tgz",
@ -7537,7 +7651,7 @@
},
"node_modules/postcss": {
"version": "8.4.5",
"resolved": "https://registry.npmmirror.com/postcss/download/postcss-8.4.5.tgz",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz",
"integrity": "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==",
"dependencies": {
"nanoid": "^3.1.30",
@ -7546,6 +7660,10 @@
},
"engines": {
"node": "^10 || ^12 || >=14"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
}
},
"node_modules/postcss-calc": {
@ -9689,19 +9807,6 @@
"integrity": "sha1-LeGWGMZtwkfc+2+ZM4A12CRaLO4=",
"dev": true
},
"node_modules/vant": {
"version": "3.4.2",
"resolved": "https://registry.npmmirror.com/vant/download/vant-3.4.2.tgz",
"integrity": "sha512-Mupj1FDW19QJpKUhu+gZgcUt3FhKly2AuPASTLmiuby0JtxV5P/XaTvMxwNCEz0oH2Q8QShppTaxoSPaz2z0yg==",
"dependencies": {
"@vant/icons": "^1.7.1",
"@vant/popperjs": "^1.1.0",
"@vant/use": "^1.3.4"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/vite": {
"version": "2.7.13",
"resolved": "https://registry.npmmirror.com/vite/download/vite-2.7.13.tgz",
@ -10231,6 +10336,17 @@
"integrity": "sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=",
"dev": true
},
"node_modules/vuex": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.2.tgz",
"integrity": "sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==",
"dependencies": {
"@vue/devtools-api": "^6.0.0-beta.11"
},
"peerDependencies": {
"vue": "^3.0.2"
}
},
"node_modules/watchpack": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/watchpack/download/watchpack-2.3.1.tgz",
@ -11158,6 +11274,17 @@
"mime": "^3.0.0"
}
},
"@ctrl/tinycolor": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz",
"integrity": "sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ=="
},
"@element-plus/icons-vue": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-0.2.6.tgz",
"integrity": "sha512-2gg7VCq4d2firgl7/aVym4Cx/wqKFwKybEQGJiiWJN4urW36+QdAEG1knqSD9qidbjhVp0Jnc9XdSTR1/4Whzw==",
"requires": {}
},
"@eslint/eslintrc": {
"version": "1.0.5",
"resolved": "https://registry.npmmirror.com/@eslint/eslintrc/download/@eslint/eslintrc-1.0.5.tgz",
@ -11546,7 +11673,7 @@
},
"@popperjs/core": {
"version": "2.11.2",
"resolved": "https://registry.npmmirror.com/@popperjs/core/download/@popperjs/core-2.11.2.tgz",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz",
"integrity": "sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA=="
},
"@rollup/plugin-alias": {
@ -11975,24 +12102,6 @@
"eslint-visitor-keys": "^3.0.0"
}
},
"@vant/icons": {
"version": "1.7.3",
"resolved": "https://registry.npmmirror.com/@vant/icons/download/@vant/icons-1.7.3.tgz",
"integrity": "sha512-tW4EqzxN4kXw1rnlnQJQHofEifPbt/gECOWiibomht8QLyvoGuE4iUmDFS288dJ07ZjuTy0bhdABj0SENo2fmQ=="
},
"@vant/popperjs": {
"version": "1.1.0",
"resolved": "https://registry.npm.taobao.org/@vant/popperjs/download/@vant/popperjs-1.1.0.tgz",
"integrity": "sha1-tO3uW7+m+xhwWYbjE9T9XxeUKg8=",
"requires": {
"@popperjs/core": "^2.9.2"
}
},
"@vant/use": {
"version": "1.3.4",
"resolved": "https://registry.npmmirror.com/@vant/use/download/@vant/use-1.3.4.tgz?cache=0&sync_timestamp=1637544063803&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2F%40vant%2Fuse%2Fdownload%2F%40vant%2Fuse-1.3.4.tgz",
"integrity": "sha512-XvZkPCjcmEBhD+T3vB68thOG6P9jazld6aBTMenhbAQd4FT/x9AiKIWPJx4MvhYoSIWt7fju6K01XTJldWs1hw=="
},
"@vercel/nft": {
"version": "0.17.3",
"resolved": "https://registry.npmmirror.com/@vercel/nft/download/@vercel/nft-0.17.3.tgz",
@ -12246,8 +12355,7 @@
"@vue/devtools-api": {
"version": "6.0.0-beta.21.1",
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/download/@vue/devtools-api-6.0.0-beta.21.1.tgz",
"integrity": "sha512-FqC4s3pm35qGVeXRGOjTsRzlkJjrBLriDS9YXbflHLsfA9FrcKzIyWnLXoNm+/7930E8rRakXuAc2QkC50swAw==",
"dev": true
"integrity": "sha512-FqC4s3pm35qGVeXRGOjTsRzlkJjrBLriDS9YXbflHLsfA9FrcKzIyWnLXoNm+/7930E8rRakXuAc2QkC50swAw=="
},
"@vue/reactivity": {
"version": "3.2.27",
@ -12302,6 +12410,31 @@
"resolved": "https://registry.npmmirror.com/@vue/shared/download/@vue/shared-3.2.27.tgz",
"integrity": "sha512-rpAn9k6O08Lvo7ekBIAnkOukX/4EsEQLPrRJBKhIEasMsOI5eX0f6mq1sDUSY7cgAqWw2d7QtP74CWxdXoyKxA=="
},
"@vueuse/core": {
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-7.5.5.tgz",
"integrity": "sha512-RBDqmIoGfak4h3xdXa/Av+ibkb8NY044wEy6+PG2FAWNaID8/FkqmSFjbxogrbmpSX1yZ1PBHrM8DUp/FrIpbg==",
"requires": {
"@vueuse/shared": "7.5.5",
"vue-demi": "*"
},
"dependencies": {
"@vueuse/shared": {
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-7.5.5.tgz",
"integrity": "sha512-mzzTsotHQRPnPAChy8iCv6ek/90CKYhAFyMRgNsMxpT0afZJkbMO/X0OaOu/1NuGbgb8UVjlsWKmCUgKTOF5hA==",
"requires": {
"vue-demi": "*"
}
},
"vue-demi": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.12.1.tgz",
"integrity": "sha512-QL3ny+wX8c6Xm1/EZylbgzdoDolye+VpCXRhI2hug9dJTP3OUJ3lmiKN3CsVV3mOJKwFi0nsstbgob0vG7aoIw==",
"requires": {}
}
}
},
"@vueuse/head": {
"version": "0.7.5",
"resolved": "https://registry.npmmirror.com/@vueuse/head/download/@vueuse/head-0.7.5.tgz",
@ -12728,9 +12861,14 @@
"integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==",
"dev": true
},
"async-validator": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.0.7.tgz",
"integrity": "sha512-Pj2IR7u8hmUEDOwB++su6baaRi+QvsgajuFB9j95foM1N2gy5HM4z60hfusIO0fBPG5uLAEl6yCJr1jNSVugEQ=="
},
"autoprefixer": {
"version": "10.4.2",
"resolved": "https://registry.npmmirror.com/autoprefixer/download/autoprefixer-10.4.2.tgz",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.2.tgz",
"integrity": "sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ==",
"dev": true,
"requires": {
@ -13610,6 +13748,22 @@
"integrity": "sha512-k/0t1TRfonHIp8TJKfjBu2cKj8MqYTiEpOhci+q7CVEE5xnCQnx1pTa+V8b/sdhe4S3PR4p4iceEQWhGrKQORQ==",
"dev": true
},
"element-plus": {
"version": "1.3.0-beta.9",
"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-1.3.0-beta.9.tgz",
"integrity": "sha512-h+I6VVtJNwjVkNhfhXp2oCYjgBN7kWQ2Y6nWzwc0wiphAlQDhGuBXAGj9eYpf1uOrMgIpFFwbRhvK7C6sOJsWg==",
"requires": {
"@ctrl/tinycolor": "^3.4.0",
"@element-plus/icons-vue": "^0.2.6",
"@popperjs/core": "^2.11.2",
"@vueuse/core": "^7.5.4",
"async-validator": "^4.0.7",
"dayjs": "^1.10.7",
"lodash": "^4.17.21",
"memoize-one": "^6.0.0",
"normalize-wheel-es": "^1.1.1"
}
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmmirror.com/emoji-regex/download/emoji-regex-8.0.0.tgz?cache=0&sync_timestamp=1632751408145&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Femoji-regex%2Fdownload%2Femoji-regex-8.0.0.tgz",
@ -15730,6 +15884,11 @@
"fs-monkey": "1.0.3"
}
},
"memoize-one": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
},
"memory-fs": {
"version": "0.5.0",
"resolved": "https://registry.nlark.com/memory-fs/download/memory-fs-0.5.0.tgz",
@ -16250,6 +16409,11 @@
"integrity": "sha1-QNCIW1Nd7/4/MUe+yHfQX+TFZoo=",
"dev": true
},
"normalize-wheel-es": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.1.1.tgz",
"integrity": "sha512-157VNH4CngrcsvF8xOVOe22cwniIR3nxSltdctvQeHZj8JttEeOXffK28jucWfWBXs0QNetAumjc1GiInnwX4w=="
},
"npm-bundled": {
"version": "1.1.2",
"resolved": "https://registry.nlark.com/npm-bundled/download/npm-bundled-1.1.2.tgz",
@ -16699,7 +16863,7 @@
},
"postcss": {
"version": "8.4.5",
"resolved": "https://registry.npmmirror.com/postcss/download/postcss-8.4.5.tgz",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz",
"integrity": "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==",
"requires": {
"nanoid": "^3.1.30",
@ -18304,16 +18468,6 @@
"integrity": "sha1-LeGWGMZtwkfc+2+ZM4A12CRaLO4=",
"dev": true
},
"vant": {
"version": "3.4.2",
"resolved": "https://registry.npmmirror.com/vant/download/vant-3.4.2.tgz",
"integrity": "sha512-Mupj1FDW19QJpKUhu+gZgcUt3FhKly2AuPASTLmiuby0JtxV5P/XaTvMxwNCEz0oH2Q8QShppTaxoSPaz2z0yg==",
"requires": {
"@vant/icons": "^1.7.1",
"@vant/popperjs": "^1.1.0",
"@vant/use": "^1.3.4"
}
},
"vite": {
"version": "2.7.13",
"resolved": "https://registry.npmmirror.com/vite/download/vite-2.7.13.tgz",
@ -18676,6 +18830,14 @@
}
}
},
"vuex": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.2.tgz",
"integrity": "sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==",
"requires": {
"@vue/devtools-api": "^6.0.0-beta.11"
}
},
"watchpack": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/watchpack/download/watchpack-2.3.1.tgz",

5
package.json

@ -8,18 +8,21 @@
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^5.10.0",
"autoprefixer": "^10.4.2",
"eslint": "^8.7.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-vue": "^8.3.0",
"less": "^4.1.2",
"nuxt3": "latest",
"postcss": "^8.4.5",
"prettier": "^2.5.1"
},
"dependencies": {
"axios": "^0.25.0",
"dayjs": "^1.10.7",
"element-plus": "^1.3.0-beta.9",
"lodash": "^4.17.21",
"vant": "^3.4.2"
"vuex": "^4.0.2"
}
}

585
pages/Initiate-application.vue

@ -0,0 +1,585 @@
<template>
<div>
<!-- 导航返回上一页 -->
<div title="发起申请" left-arrow @click="router.go(-1)" ></div>
<!-- 申请发票需要输入的数据 -->
<div class="bg-white pb-3">
<div class="text-gray-500 px-4 py-3 font-semibold">发票信息</div>
<!-- 是否上传票据 -->
<div v-show="data.isInvoice">
<div class="mx-4 pb-3 border-b">
<div class="flex pt-3 pb-2 justify-between" v-show="data.titleHidden">
<div>
<span class="text-red-500">*</span>
<span class="text-gray-500">上传票据凭证 </span>
<span class="text-gray-400 text-xs">(仅支持ipg格式)</span>
</div>
<el-button
plain
type="primary"
size="mini"
@click="data.isInvoice = false"
>手动输入</el-button
>
</div>
<!-- <el-overlay :show="data.showUploading"> -->
<div
class="text-center border-b w-52 h-24 border-dashed border-2 upload-box"
v-show="!data.isSuccess"
>
<el-uploader v-model="data.fileList" multiple :max-count="1" :after-read="afterRead" class="z-50 opacity-0" />
<div class="upload-txt">
<p class="text-gray-400 text-xl pt-3">+</p>
<p class="text-gray-400 text-xs">上传并识别凭证</p>
</div>
<!-- <el-loading type="spinner" v-if="data.showUploading" class="upload-txt z-50" /> -->
</div>
<!-- </el-overlay> -->
<!-- 上传票据成功后显示发票信息 -->
<div v-show="data.isSuccess">
<el-field
v-for="item in data.invoiceInfo"
required
v-model="item.value"
:label="item.name"
@change="cahngeInvoiceList($event, item)"
input-align="right"
/>
</div>
</div>
</div>
<!-- 手动输入信息 -->
<div v-show="!data.isInvoice">
<el-field
required
v-model="data.money"
label="申请金额"
placeholder="输入申请金额"
input-align="right"
/>
</div>
<!-- 备注信息 -->
<div class="text-gray-500 py-4 pl-5">备注</div>
<el-cell-group>
<el-field
v-model="data.remark"
rows="2"
autosize
type="textarea"
maxlength="40"
placeholder="请输入备注"
show-word-limit
class="border rounded"
/>
</el-cell-group>
</div>
<!-- 选择审核人 -->
<div class="mt-3">
<div class="flex bg-white p-4">
<div class="text-red-500">*</div>
<div class="text-gray-500 pl-1 font-semibold">审核人</div>
</div>
<div class="px-3 bg-white">
<div>
<el-button
class="button"
size="mini"
v-for="item in data.reviewerList"
:key="item.memberId"
:type="
data.checkerList.find(checker => checker === item.memberId)
? 'primary'
: 'default'
"
@click="handleSelectChecker(item.memberId)"
>
{{ item.name }}
</el-button>
</div>
</div>
</div>
<!-- 其他信息 -->
<div class="bg-white mt-3">
<div class="text-gray-500 p-4 font-semibold">其他信息</div>
<!-- 申请类型 -->
<!-- 普通票据申请 -->
<div>
<el-field
v-model="data.applyType"
is-link
readonly
label="申请类型"
placeholder="请选择申请类型"
@click="data.showType = true"
required
input-align="right"
/>
<!-- <el-popup v-model:show="data.showType" round position="bottom">
<el-picker
title="申请类型"
v-if="data.applyTypeOptions && data.applyTypeOptions.length"
:columns="data.applyTypeOptions"
@confirm="finishApplyType"
/>
<el-loading v-else class="my-20 text-center" />
</el-popup> -->
<!-- 所属项目 -->
<el-field
v-model="projectName"
is-link
readonly
label="所属项目"
placeholder="请选择所属项目"
required
input-align="right"
disabled
/>
<!-- <el-popup v-model:show="projectName" round position="bottom">
<el-cascader active-color="#1989fa" class="p-0" />
</el-popup> -->
<!-- 所属任务的 -->
<el-field
v-model="taskName"
v-show="data.isInvoice"
is-link
readonly
label="所属任务"
placeholder="请选择所属任务"
required
input-align="right"
disabled
/>
<!-- <el-popup v-model:show="taskName" round position="bottom">
<el-cascader active-color="#1989fa" class="p-0" />
</el-popup> -->
<!-- 类目选择 -->
<el-field
v-model="data.applyCategory"
is-link
readonly
label="类目"
placeholder="请选择类目"
@click="data.showCategory = true"
input-align="right"
/>
<!-- <el-popup v-model:show="data.showCategory" round position="bottom">
<el-picker
title="请选择类目"
v-if="data.applyCategoryOptions && data.applyCategoryOptions.length"
:columns="data.applyCategoryOptions"
@confirm="finishApplyCategory"
/>
<el-loading v-else class="my-20 text-center" />
</el-popup> -->
<!-- 名目选择 -->
<el-field
v-model="data.applyName"
v-show="data.isInvoice"
is-link
readonly
label="名目"
placeholder="请选择名目"
@click="data.showName = true"
input-align="right"
/>
<!-- <el-popup v-model:show="data.showName" round position="bottom">
<el-picker
title="请选择名目"
v-if="data.applyNameOptions && data.applyNameOptions.length"
:columns="data.applyNameOptions"
@confirm="finishApplyName"
/>
<el-loading v-else class="my-20 text-center" />
</el-popup> -->
</div>
</div>
<!-- 提交人信息 -->
<div class="bg-white mt-3">
<div class="text-gray-500 p-4 font-semibold">提交人信息</div>
<el-field
required
v-model="data.submitName"
label="姓名"
placeholder="请输入姓名"
input-align="right"
/>
<!-- 选择所属部门 -->
<el-field
required
v-model="data.department"
label="所属部门"
placeholder="请输入所属部门"
input-align="right"
/>
</div>
<!-- 历史申请 -->
<div class="bg-white mt-3 p-4">
<div class="text-gray-500 font-semibold">历史申请</div>
<div>
<Search />
<HistoricalApplication />
</div>
</div>
<!-- 底部立即提交按钮 -->
<div class="mx-6 mt-10">
<el-button @click="submit" type="primary" size="small" block>立即提交</el-button>
</div>
</div>
</template>
<script setup>
import axios from 'axios';
import { ref, reactive } from 'vue';
import { queryChecker } from 'apis/member';
import { queryType, apply } from 'apis/finance';
import { bill } from 'apis/ocr';
import { ElMessage } from 'element-plus'
import { useRouter } from 'vue-router';
import num from 'utils/num';
const router = useRouter();
const token = useToken()
const projectName = useProjectName();
console.log('projectName: ', projectName);
const taskName = useTaskName();
const projectId = useProjectId();
const taskDetailId = useTaskId();
const data = reactive({
isInvoice: true,
fileList: [],
showUploading: false,
remark: '',
invoiceList: [],
invoiceInfo: [
{
name: '发票代码',
value: 0,
label: 'invoiceCode',
},
{
name: '发票号码',
value: 0,
label: 'invoiceNumber',
},
{
name: '合计金额(元)',
value: 0,
label: 'money',
},
{
name: '税额(元)',
value: 0,
label: 'taxMoney',
},
{
name: '开票日期',
value: 0,
label: 'invoiceTime',
},
{
name: '备注信息',
value: '无',
label: 'remark',
},
],
reviewerList: [], //
checkerList: [], //
isSuccess: false, //
submitName: '', //
department: '', //
pplyMoney: '5000', //
titleHidden: true, //
currentPage: 0, //
personalType: '个人申请', //
personalCategory: '用款',
money: '',
//
showType: false, //
applyTypes: [],
applyTypeOptions: [],
applyType: '', //
typeId: '',
showCategory: false, //
applyCategories: [],
applyCategoryOptions: [],
applyCategory: '', //
categoryId: '',
showName: false, //
applyNames: [],
applyNameOptions: [],
applyName: '', //
rowId: '',
});
//
function upLoaderImg(file, url) { //file
//new FormData
let params = new FormData()
params.append('part', file)
let config = {
headers: { //
'Content-Type': 'multipart/form-data',
Authorization: 'Bearer ' + token.value
}
}
return new Promise((resolve, reject) => {
// uploadUrl
axios.post(`${bill}`, params, config).then(res => {
resolve(res)
}).catch(err => {
reject(err)
});
})
}
//
async function afterRead(file){
data.showUploading = true
//
upLoaderImg(file.file, 'upload').then(res => {
data.showUploading = false
if (res.data.code == 200) {
for(let key in res.data.data){
data.invoiceInfo.forEach(item => {
if(item.label === key){
if(item.label === 'money' || item.label === 'taxMoney'){
item.value = num.except(+res.data.data[key], 100)
}else{
item.value = res.data.data[key]
}
}
})
}
console.log('data.invoiceInfo: ', data.invoiceInfo);
data.invoiceList.push(res.data.data)
data.titleHidden = false;
data.isSuccess = true;
} else {
ElMessage.error(res.data.msg || '上传失败')
}
})
}
//
function cahngeInvoiceList(e, item) {
data.invoiceList.forEach(invoice => {
for(let key in data.invoiceList){
if(item.label === key){
invoice[key] = item.value
}
}
})
}
//
function finishApplyType(e) {
data.showType = false;
data.applyType = e;
const type = data.applyTypes.find(option => option.name === e);
data.typeId = type.id;
//
handleQueryType(data.typeId, 1);
}
//
function finishApplyCategory(e) {
data.showCategory = false;
data.applyCategory = e;
const category = data.applyCategories.find(option => option.name === e);
data.categoryId = category.id;
//
handleQueryType(data.categoryId, 2);
}
//
function finishApplyName(e) {
data.showName = false;
data.applyName = e;
const row = data.applyNames.find(option => option.name === e);
data.rowId = row.id;
}
//
function handleSelectChecker(id) {
const target = data.checkerList.find(item => item === id);
if (target) {
data.checkerList = data.checkerList.filter(item => item !== id);
} else {
data.checkerList.push(id);
}
}
/**
* 查询所有成员
* @param { String } projectId
*/
async function handleQueryChecker() {
try {
const params = {
param: {
projectId: projectId.value,
},
};
const res = await queryChecker(params);
data.reviewerList = res;
} catch (error) {
console.error('error: ', error);
}
}
/**
* 查询费用申请类型
* @param { String } parentId 上级类型ID默认为0
* @param { Number } type 类型0申请类型 1类目 2名目
*/
async function handleQueryType(parentId, type) {
try {
const params = {
param: {
parentId,
type,
},
};
const res = await queryType(params);
if (type === 0) {
data.applyTypes = res;
res.forEach(item => {
data.applyTypeOptions.push(item.name);
});
}
if (type === 1) {
data.applyCategories = res;
res.forEach(item => {
data.applyCategoryOptions.push(item.name);
});
}
if (type === 2) {
data.applyNames = res;
res.forEach(item => {
data.applyNameOptions.push(item.name);
});
}
} catch (error) {
console.error('error: ', error);
}
}
onMounted(() => {
//
handleQueryChecker();
//
handleQueryType(0, 0);
});
/**
* 发起申请
* @param { Object } params
*/
async function submit() {
try {
if(!verification()) return
const params = {}
params.param = setParams()
await apply(params)
ElMessage.success('审批成功')
} catch (error) {
console.log('error: ', error);
ElMessage.error(error || '申请失败')
}
}
//
function verification(){
const { isSuccess, invoiceInfo, categoryId, checkerList, department, money, rowId, submitName, typeId, isInvoice } = data
//
if(!isSuccess && isInvoice){
ElMessage.error('请上传票据凭证');
return
}
if(!money && !isInvoice){
ElMessage.error('请输入金额');
return
}
if(!checkerList || !checkerList.length){
ElMessage.error('请选择审核人');
return
}
if(!typeId){
ElMessage.error('请选择申请类型');
return
}
if(!categoryId){
ElMessage.error('请选择类目');
return
}
if(!rowId && isInvoice){
ElMessage.error('请选择名目');
return
}
if(!submitName){
ElMessage.error('请输入提交人姓名');
return
}
if(!department){
ElMessage.error('请输入所属部门');
return
}
return true
}
//
function setParams(){
const { remark, money, categoryId, checkerList, department, rowId, submitName, typeId, invoiceInfo, isInvoice, invoiceList } = data
let param = {}
let totleMoney = 0
invoiceInfo.forEach(item => {
if(item.label === 'money'){
totleMoney += item.value
}
})
if(isInvoice){
param = {
money: num.ride(totleMoney,100),invoiceList, remark, checkerList, typeId, projectId: projectId.value,
taskDetailId: taskDetailId.value, categoryId, rowId, submitName, department
}
}else{
param = {
money: num.ride(money,100), remark, checkerList, typeId, projectId: projectId.value, categoryId, submitName, department
}
}
return param
}
</script>
<style lang="less" scoped>
.el-cell-group {
padding: 0 1rem;
}
.el-cell {
font-size: 15px;
}
.bill-name {
font-weight: 600;
color: #6f6f6f;
border-bottom: 1px solid #eee;
padding: 0.75rem 0px;
}
.el-button {
border-radius: 0.2rem;
}
.button {
border-radius: 1rem;
padding: 0 0.75rem;
margin: 0.5rem;
}
.upload-box{
background: #f7f8fa;
position: relative;
}
.upload-txt{
position: absolute;
width: 100%;
bottom: 22px;
}
</style>

76
pages/applicant.vue

@ -0,0 +1,76 @@
<template>
<div>
<!-- 搜索框与表格部分 -->
<el-tabs
v-model:active="active"
shrink
line-width="60px"
color="#59B4FF"
title-active-color="#59B4FF"
>
<el-tab-pane title="我的申请">
<div class=" bg-white px-4">
<div class="mt-4 flex flex-col overflow-hidden h-full">
<div class="py-4 pb-0 text-gray-500 font-semibold">历史申请</div>
<Search />
<HistoricalApplication />
</div>
<!-- 底部提交按钮部分 -->
<div class="fixed w-11/12 box-border bottom-10 " @click="toApplication">
<el-button type="primary" block size="small">发起申请</el-button>
</div>
</div>
</el-tab-pane>
<el-tab-pane title="我的奖金">
<div class="mt-4 bg-white">
<div class="p-4 pb-0 text-gray-500 font-semibold">奖金领取记录</div>
<Search class="px-4 pt-0" />
<BonusCollection class="px-4 mt-0" />
</div>
<div class="mt-4 bg-white">
<div class="text-gray-500 font-semibold m-4 py-3 border-b">
待领取奖金
</div>
<div class="text-ms text-gray-400 pl-4">可领取:</div>
<div
class="w-full h-20 text-gray-400 font-semibold text-center leading-loose"
v-if="!isBonus"
>
暂无可领取的奖金
</div>
<div
class="w-full h-20 text-blue-500 font-semibold text-center leading-loose text-2xl"
v-if="isBonus"
>
500
</div>
<div class="px-8">
<el-button type="primary" size="small" block :disabled="!isBonus"
>立即领取</el-button
>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useRouter } from 'vue-router';
const router = useRouter();
const active = ref(0);
const isBonus = ref(true);
function toApplication() {
const routeValue = router.currentRoute.value;
const query = routeValue.query;
router.push({ path: '/Initiate-application', query });
}
</script>
<style lang="less" scoped></style>

224
pages/application-details.vue

@ -0,0 +1,224 @@
<template>
<div>
<!-- 导航栏 -->
<NuxtLink to="Initiate-application">
<div
title="申请详情"
left-arrow
@click="router.go(-1)"
></div>
</NuxtLink>
<!-- 审核结果 -->
<div class="bg-white px-3">
<div class="text-gray-500 font-semibold px-1 py-3">审核结果</div>
<div v-for="(item, checkerIndex) in checkerList" :key="checkerIndex" class="flex py-3 px-4 text-gray-400 text-base justify-between">
<div>
<div>{{item.checkerName}}</div>
<div class="text-sm pt-1">{{item.remark}}</div>
<div class="text-sm pt-1">{{item.time}}</div>
</div>
<div class="text-center">
<div :class="item.checkStatus == '1' ? 'text-green-500' : item.checkStatus == '2' ? 'text-red-500' : ''">
{{item.checkStatus == '1' ? '已通过' : item.checkStatus == '2' ? '已驳回' : '待审批'}}
</div>
<div v-if="item.checkStatus == '1' || item.checkStatus == '2'" class="mt-1">
<el-progress type="circle" :percentage="100" color="#ff6700" :stroke-width="100" />
<!-- <van-circle
v-model:current-rate="currentRate"
:rate="100"
:speed="100"
:text="100"
class="w-12"
color="#ff6700"
:stroke-width="100"
/> -->
</div>
</div>
</div>
</div>
<!-- 发票信息 -->
<div class="bg-white px-3" v-if="isInvoice">
<div class="text-gray-500 font-semibold px-1 py-3 mt-5">发票信息</div>
<div v-for="(item,invoiceIndex) in invoiceList" :key="invoiceIndex">
<div class="flex py-2 px-4 text-gray-400 text-base justify-between">
<div>发票代码</div>
<div>{{item.invoiceCode}}</div>
</div>
<div class="flex py-2 px-4 text-gray-400 text-base justify-between">
<div>发票号码</div>
<div>{{item.invoiceNumber}}</div>
</div>
<div class="flex py-2 px-4 text-gray-400 text-base justify-between">
<div>合计金额()</div>
<div>{{ num.except(+item.money, 100) }}</div>
</div>
<div class="flex py-2 px-4 text-gray-400 text-base justify-between">
<div>税额()</div>
<div>{{ num.except(+item.taxMoney, 100) }}</div>
</div>
<div class="flex py-2 px-4 text-gray-400 text-base justify-between">
<div>开票日期</div>
<div>{{dayjs(item.invoiceTime - 0).format('YYYY年MM月DD日')}}</div>
</div>
<div class="flex py-2 px-4 text-gray-400 text-base justify-between">
<div>备注</div>
<div>{{item.remark}}</div>
</div>
</div>
</div>
<!-- 金额信息 -->
<div class="bg-white px-3" v-else>
<div class="text-gray-500 font-semibold px-1 py-3 mt-5">发票信息</div>
<div v-for="(item,moneyIndex) in moneyInfo" :key="moneyIndex" class="flex py-2 px-4 text-gray-400 text-base justify-between">
<div>{{item.name}}</div>
<div>{{item.value}}</div>
</div>
</div>
<!-- 其他信息 -->
<div class="bg-white px-3">
<div class="text-gray-500 font-semibold px-1 py-3 mt-5">其他信息</div>
<div v-for="(item,otherIndex) in otherData" :key="otherIndex" class="flex py-2 px-4 text-gray-400 text-base justify-between">
<div>{{item.name}}</div>
<div>{{item.value}}</div>
</div>
</div>
<!-- 提交人信息 -->
<div class="bg-white px-3 mb-5">
<div class="text-gray-500 font-semibold px-1 py-3 mt-5">提交人信息</div>
<div v-for="(item, index) in submitter" :key="index" class="flex py-2 px-4 text-gray-400 text-base justify-between">
<div>{{item.name}}</div>
<div>{{item.value}}</div>
</div>
</div>
</div>
</template>
<script setup>
import dayjs from "dayjs";
import { ref, computed } from 'vue';
import { getApplyDetail } from 'apis/finance';
import { useRouter } from 'vue-router';
import num from 'utils/num';
const router = useRouter();
//
const currentRate = ref(0);
const text = computed(() => currentRate.value.toFixed(0));
const isInvoice = ref(false)
const checkerList = ref([])
//
const invoiceList = ref([])
//
const moneyInfo = ref([
{
name: "申请金额",
value: "",
label: "money",
},
{
name: "备注",
value: "",
label: "remark",
},
])
//
const otherData = ref([
{
name: "申请类型",
value: "",
label: "typeName",
},
{
name: "所属项目",
value: "",
label: "projectName",
},
{
name: "所属任务",
value: "",
label: "taskDetailName",
},
{
name: "类目",
value: "",
label: "categoryName",
},
{
name: "名目",
value: "",
label: "rowName",
},
])
//
const submitter = ref([
{
name:'姓名',
value:'',
label: "submitName",
},
{
name:'部门',
value:'软件部',
label: "department",
},
{
name:'提交时间',
value:'',
label: "applyTime",
},
])
/**
* 查询申请详情
* @param { Number } applyId 申请记录id
*/
async function handleApplyDetail(){
try {
const routeValue = router.currentRoute.value;
const applyId = routeValue.query.applyId;
const params = {param : { applyId }}
const res = await getApplyDetail(params)
//
checkerList.value = res.checkerList
//
isInvoice.value = res.invoiceList && res.invoiceList.length ? true : false;
invoiceList.value = res.invoiceList
for(let key in res){
moneyInfo.value.forEach(item => {
if(item.label === key){
item.value = res[key]
}
})
}
//
for(let key in res){
otherData.value.forEach(item => {
if(item.label === key){
item.value = res[key]
}
})
}
//
for(let key in res){
submitter.value.forEach(item => {
if(item.label === key){
item.value = res[key]
}
})
}
} catch (error) {
console.error('error: ', error);
}
}
handleApplyDetail()
</script>
<style lang="less" scoped>
.el-progress{
width: 2.5rem !important;
height: 2.5rem !important;
}
</style>

269
pages/financial-approval-details.vue

@ -0,0 +1,269 @@
<template>
<div>
<div
title="财务审批详情"
left-arrow
@click="router.go(-1)"
></div>
<!-- 发票信息 -->
<div class="bg-white px-3" v-if="isInvoice">
<div class="text-gray-500 font-semibold px-1 py-3">发票信息</div>
<div v-for="(item,invoiceIndex) in invoiceList" :key="invoiceIndex">
<div class="flex py-2 px-4 text-gray-400 text-base justify-between">
<div>发票代码</div>
<div>{{item.invoiceCode}}</div>
</div>
<div class="flex py-2 px-4 text-gray-400 text-base justify-between">
<div>发票号码</div>
<div>{{item.invoiceNumber}}</div>
</div>
<div class="flex py-2 px-4 text-gray-400 text-base justify-between">
<div>合计金额()</div>
<div>{{ (+item.money / 100).toFixed(2) }}</div>
</div>
<div class="flex py-2 px-4 text-gray-400 text-base justify-between">
<div>税额()</div>
<div>{{ (+item.taxMoney / 100).toFixed(2) }}</div>
</div>
<div class="flex py-2 px-4 text-gray-400 text-base justify-between">
<div>开票日期</div>
<div>{{dayjs(item.invoiceTime - 0).format('YYYY年MM月DD日')}}</div>
</div>
<div class="flex py-2 px-4 text-gray-400 text-base justify-between">
<div>备注</div>
<div>{{item.remark}}</div>
</div>
</div>
</div>
<!-- 金额信息 -->
<div class="bg-white px-3" v-else>
<div class="text-gray-500 font-semibold px-1 py-3">发票信息</div>
<div v-for="(item,moneyIndex) in moneyInfo" :key="moneyIndex" class="flex py-2 px-4 text-gray-400 text-base justify-between">
<div>{{item.name}}</div>
<div>{{item.value}}</div>
</div>
</div>
<!-- 其他信息 -->
<div class="bg-white px-3">
<div class="text-gray-500 font-semibold px-1 py-3 mt-5">其他信息</div>
<div v-for="(item,otherIndex) in otherData" :key="otherIndex" class="flex py-2 px-4 text-gray-400 text-base justify-between">
<div>{{item.name}}</div>
<div>{{item.value}}</div>
</div>
</div>
<!-- 提交人信息 -->
<div class="bg-white px-3 mb-5">
<div class="text-gray-500 font-semibold px-1 py-3 mt-5">提交人信息</div>
<div v-for="(item, index) in submitter" :key="index" class="flex py-2 px-4 text-gray-400 text-base justify-between">
<div>{{item.name}}</div>
<div>{{item.value}}</div>
</div>
</div>
<!-- 审核人信息 -->
<div class="bg-white px-3 mb-5">
<div class="text-gray-500 font-semibold px-1 py-3">审核结果</div>
<div v-for="(item, checkerIndex) in checkerList" :key="checkerIndex" class="flex py-3 px-4 text-gray-400 text-base justify-between">
<div>
<div>{{item.checkerName}}</div>
<div class="text-sm pt-1">{{item.remark}}</div>
<div class="text-sm pt-1">{{item.time}}</div>
</div>
<div class="text-center">
<div v-if="item.checkStatus == '1' || item.checkStatus == '2'" :class="item.checkStatus == '1' ? 'text-green-500' : item.checkStatus == '2' ? 'text-red-500' : ''">
{{item.checkStatus == '1' ? '已通过' : item.checkStatus == '2' ? '已驳回' : '待审批'}}
</div>
<div class="text-center" v-else>
<el-button type="success" round size="mini" class="button ml-5" @click="showRemark(item.financeCheckId, 1)">通过</el-button>
<el-button type="danger" round size="mini" class="button" @click="showRemark(item.financeCheckId, 2)">驳回</el-button>
</div>
<div v-if="item.checkStatus == '1' || item.checkStatus == '2'" class="mt-1">
<el-circle
v-model:current-rate="currentRate"
:rate="100"
:speed="100"
:text="100"
class="w-12"
color="#ff6700"
:stroke-width="100"
/>
</div>
</div>
</div>
</div>
<el-dialog v-model:show="data.show" title="备注" show-cancel-button @confirm="handleAudit">
<el-field :border="data.border" v-model="data.remark" type="textarea" class="remark" placeholder="请输入备注" />
</el-dialog>
</div>
</template>
<script setup>
import dayjs from "dayjs";
import { ref, computed } from 'vue';
import { getApplyDetail, audit } from 'apis/finance';
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus'
const router = useRouter();
const data = reactive({
show: false,
remark: '',
auditInfo: {},
border: true
})
//
const currentRate = ref(0);
const text = computed(() => currentRate.value.toFixed(0));
const isInvoice = ref(false)
const checkerList = ref([])
//
const invoiceList = ref([])
//
const moneyInfo = ref([
{
name: "申请金额",
value: "",
label: "money",
},
{
name: "备注",
value: "",
label: "remark",
},
])
//
const otherData = ref([
{
name: "申请类型",
value: "",
label: "typeName",
},
{
name: "所属项目",
value: "",
label: "projectName",
},
{
name: "所属任务",
value: "",
label: "taskDetailName",
},
{
name: "类目",
value: "",
label: "categoryName",
},
{
name: "名目",
value: "",
label: "rowName",
},
])
//
const submitter = ref([
{
name:'姓名',
value:'',
label: "submitName",
},
{
name:'部门',
value:'软件部',
label: "department",
},
{
name:'提交时间',
value:'',
label: "applyTime",
},
])
/**
* 查询申请详情
* @param { Number } applyId 申请记录id
*/
async function handleApplyDetail(){
try {
const routeValue = router.currentRoute.value;
const applyId = routeValue.query.applyId;
const params = {param : { applyId }}
const res = await getApplyDetail(params)
//
checkerList.value = res.checkerList
//
isInvoice.value = res.invoiceList && res.invoiceList.length ? true : false;
invoiceList.value = res.invoiceList
for(let key in res){
moneyInfo.value.forEach(item => {
if(item.label === key){
item.value = res[key]
}
})
}
//
for(let key in res){
otherData.value.forEach(item => {
if(item.label === key){
item.value = res[key]
}
})
}
//
for(let key in res){
submitter.value.forEach(item => {
if(item.label === key){
item.value = res[key]
}
})
}
} catch (error) {
console.error('error: ', error);
}
}
handleApplyDetail()
//
function showRemark(financeCheckId, checkStatus){
data.show = true
data.auditInfo = { financeCheckId, checkStatus }
}
/**
* 审批
* @param { Number } checkStatus 审核状态 1已通过 2驳回
* @param { String } financeCheckId 审核id
* @param { String } remark 备注
*/
async function handleAudit(financeCheckId, checkStatus){
try {
const { auditInfo, remark } = data
const { financeCheckId, checkStatus } = data.auditInfo
const params = {
param:{
financeCheckId,
checkStatus,
remark: data.remark
}
}
await audit(params)
ElMessage.success('审批成功')
handleFinanceOfProject()
data.remark = ''
} catch (error) {
console.error('error: ', error);
}
}
</script>
<style lang="less" scoped>
.el-circle{
width: 2.5rem !important;
height: 2.5rem !important;
}
.button{
padding: 0 0.75rem;
}
</style>

124
pages/financial-approval.vue

@ -0,0 +1,124 @@
<template>
<div class="mb-60">
<!-- 财务审批页面 -->
<div class="bg-white p-4 flex text-gray-500 flex-col">
<div>
<span class="font-semibold" id="finance-audit">财务审批</span> <span class="ml-2">对员工提交的申请进行审批</span>
</div>
<Search />
<FinanceExamine />
</div>
<!-- 财务统计页面 -->
<div class="bg-white p-4 mt-5 text-gray-500 flex-col">
<div>
<span class="font-semibold" id="finance-statistical">财务统计</span>
<span class="ml-2">财务明细统计查看</span>
</div>
<!-- 任务支出统计 -->
<div class="h-64 overflow-hidden">
<div class="mt-5 flex justify-between">
<div>
<span class="inline-block w-2 h-2 border-2 border-blue-400 rounded-full mr-3"></span>
<span>任务支出统计</span>
</div>
<div>
<el-button :type="taskState ? 'primary' : 'default'" size="mini" @click="taskState = true">图表</el-button>
<el-button :type="!taskState ? 'primary' : 'default'" size="mini" @click="taskState = false">表格</el-button>
</div>
</div>
<!-- 任务支出统计的图表展示 -->
<div v-show="taskState">
<!-- <el-empty description="暂无图表数据信息" /> -->
<RingEcharts class="w-full h-full" id="taskEcharts"/>
</div>
<!-- 任务支出统计的表格展示 -->
<div v-show="!taskState">
<FinanceExamine />
</div>
</div>
<!-- 名目支出统计 -->
<div class="h-64 overflow-hidden">
<div class="mt-5 flex justify-between">
<div>
<span class="inline-block w-2 h-2 border-2 border-blue-400 rounded-full mr-3"></span>
<span>名目支出统计</span>
</div>
<div>
<el-button :type="nameState ? 'primary' : 'default'" size="mini" @click="nameState = true">图表</el-button>
<el-button :type="!nameState ? 'primary' : 'default'" size="mini" @click="nameState = false">表格</el-button>
</div>
</div>
<!-- 名目支出统计的图表展示 -->
<div v-show="nameState">
<!-- <el-empty description="暂无图表数据信息" /> -->
<RingEcharts class="w-full h-full" id="nameEcharts"/>
</div>
<!-- 名目支出统计的表格展示 -->
<div v-show="!nameState">
<FinanceExamine />
</div>
</div>
<!-- 成员财务图 -->
<div class="h-64 overflow-hidden">
<div class="mt-5">
<span class="inline-block w-2 h-2 border-2 border-blue-400 rounded-full mr-3"></span>
<span>成员财务图</span>
</div>
<RingEcharts class="w-full h-full" id="memberEcharts"/>
</div>
<!-- 时间财务图 -->
<div class="overflow-hidden">
<div>
<span class="inline-block w-2 h-2 border-2 border-blue-400 rounded-full mr-3"></span>
<span>时间财务图</span>
</div>
<BarEcharts class="w-full h-full mt-5" />
<!-- 没有数据展示空组件 -->
<!-- <el-empty description="暂无图表数据信息" /> -->
</div>
</div>
<!-- 悬浮跳转 -->
<ul class="menu">
<a href="#finance-audit">财务审批</a>
<a href="#finance-statistical">财务统计</a>
</ul>
</div>
</template>
<script>
export default {
layout: 'default',
};
</script>
<script setup>
import {ref} from 'vue'
const taskState = ref(true);
const nameState = ref(true);
</script>
<style lang="less" scoped>
.menu{
position: fixed;
right: 0;
bottom: 15px;
z-index: 99
}
.menu a {
display: block;
width: 72px;
color: #fff;
background: rgba(25, 137, 250, 0.6);
padding: 8px 20px;
border-radius: 30px 0 0 30px;
margin-bottom: 15px;
}
.el-button--mini{
padding: 0.5rem 1rem;
}
.el-button--mini+.el-button--mini{
margin-left: 0px;
}
</style>

237
pages/index.vue

@ -1,42 +1,144 @@
<template>
<div class="fixed bg-gray-100">
<el-tabs
v-model:active="active"
shrink
line-width="60px"
color="#59B4FF"
title-active-color="#59B4FF"
>
<el-tab-pane title="财务管理">
<div class="h-screen w-full pb-60 overflow-y-scroll">
<!-- 财务管理页面 -->
<div class="bg-white p-4 mt-5 text-gray-500 flex-col">
<div>
<Hello />
<CommonHello />
<span class="font-semibold" id="finance-manage">财务管理</span>
<span class="ml-2">对项目预算奖金进行配置</span>
</div>
<Search class="mr-5"/>
<FinanceManage class="mr-5"/>
</div>
<!-- 财务审批页面 -->
<div class="bg-white p-4 mt-5 text-gray-500 flex-col">
<div>
<span class="font-semibold" id="finance-audit">财务审批</span>
<span class="ml-2">对员工提交的申请进行审批</span>
</div>
<Search class="mr-5"/>
<div class="overflow-x-hidden mr-5 table-box">
<FinanceExamine />
</div>
{{ testString }}
</div>
<!-- 财务统计页面 -->
<div class="bg-white p-4 mt-5 text-gray-500 flex-col">
<div>
<span class="font-semibold" id="finance-statistical">财务统计</span>
<span class="ml-2">财务明细统计查看</span>
</div>
<!-- 任务支出统计 -->
<div class="h-64 overflow-hidden">
<div class="mt-5 flex justify-between">
<div>
<span
class="inline-block w-2 h-2 border-2 border-blue-400 rounded-full mr-3"
></span>
<span>任务支出统计</span>
</div>
<div class="mr-5">
<el-button
:type="taskState ? 'primary' : 'default'"
size="mini"
@click="taskState = true"
>图表</el-button
>
<el-button
:type="!taskState ? 'primary' : 'default'"
size="mini"
@click="taskState = false"
>表格</el-button
>
</div>
</div>
<!-- 任务支出统计的图表展示 -->
<div v-show="taskState">
<!-- <el-empty description="暂无图表数据信息" /> -->
<RingEcharts class="w-full h-full" id="taskEcharts" />
</div>
<!-- 任务支出统计的表格展示 -->
<div v-show="!taskState">
<Expenditure class="w-full h-full" id="taskTable" />
</div>
</div>
<!-- 名目支出统计 -->
<div class="h-64 overflow-hidden">
<div class="mt-5 flex justify-between">
<div>
<span
class="inline-block w-2 h-2 border-2 border-blue-400 rounded-full mr-3"
></span>
<span>名目支出统计</span>
</div>
<div class="mr-5">
<el-button
:type="nameState ? 'primary' : 'default'"
size="mini"
@click="nameState = true"
>图表</el-button
>
<el-button
:type="!nameState ? 'primary' : 'default'"
size="mini"
@click="nameState = false"
>表格</el-button
>
</div>
</div>
<!-- 名目支出统计的图表展示 -->
<div v-show="nameState">
<!-- <el-empty description="暂无图表数据信息" /> -->
<RingEcharts class="w-full h-full" id="nameEcharts" />
</div>
<!-- 名目支出统计的表格展示 -->
<div v-show="!nameState">
<Expenditure class="w-full h-full" id="nameTable" />
</div>
</div>
<!-- 成员财务图 -->
<div class="h-64 overflow-hidden">
<div class="mt-5">
<span
class="inline-block w-2 h-2 border-2 border-blue-400 rounded-full mr-3"
></span>
<span>成员财务图</span>
</div>
<RingEcharts class="w-full h-full" id="memberEcharts" />
</div>
<!-- 时间财务图 -->
<div class="h-96 overflow-hidden">
<div>
<span
class="inline-block w-2 h-2 border-2 border-blue-400 rounded-full mr-3"
></span>
<span>时间财务图</span>
</div>
<BarEcharts class="w-full h-full" />
</div>
</div>
<van-image
class="user-poster"
src="https://img.yzcdn.cn/public_files/2017/10/23/8690bb321356070e0b8c4404d087f8fd.png"
/>
<van-row class="user-links">
<van-col span="6">
<van-icon name="pending-payment" />
待付款
</van-col>
<van-col span="6">
<van-icon name="records" />
待接单
</van-col>
<van-col span="6">
<van-icon name="tosend" />
待发货
</van-col>
<van-col span="6">
<van-icon name="logistics" />
已发货
</van-col>
</van-row>
<van-cell-group class="user-group">
<van-cell icon="records" title="全部订单" is-link />
</van-cell-group>
</div>
</el-tab-pane>
<el-tab-pane title="角色管理"> <div class="h-screen overflow-y-scroll w-screen mt-4">角色管理</div></el-tab-pane>
<el-tab-pane title="任务管理"><div class="h-screen overflow-y-scroll w-screen mt-4">任务管理</div></el-tab-pane>
<el-tab-pane title="成员管理"><div class="h-screen overflow-y-scroll w-screen mt-4">成员管理</div></el-tab-pane>
</el-tabs>
<van-cell-group>
<van-cell icon="points" title="我的积分" is-link />
<van-cell icon="gold-coin-o" title="我的优惠券" is-link />
<van-cell icon="gift-o" title="我收到的礼物" is-link />
</van-cell-group>
<div class="menu">
<a href="#finance-manage">财务管理</a>
<a href="#finance-audit">财务审批</a>
<a href="#finance-statistical">财务统计</a>
</div>
</div>
</template>
@ -47,34 +149,63 @@ export default {
</script>
<script setup>
const testString = ref('Hello world');
import { ref, onMounted, nextTick } from 'vue';
const active = ref(0);
const taskState = ref(true);
const nameState = ref(true);
onMounted(async () => {
scrollToElementByHash();
});
// hash
async function scrollToElementByHash() {
await nextTick();
const hash = window.location.hash;
if (!hash) return;
const scrollDom = document.querySelector(hash);
document.body.scrollTop = scrollDom.offsetTop;
document.documentElement.scrollTop = scrollDom.offsetTop;
window.pageYOffset = scrollDom.offsetTop;
}
</script>
<style lang="less">
body {
margin: 0;
font-size: 16px;
background-color: #f8f8f8;
-webkit-font-smoothing: antialiased;
.el-nav-bar__content {
background-color: #eee;
}
.user {
&-poster {
width: 100%;
height: 53vw;
display: block;
.el-icon-arrow-left:before {
color: #000;
}
&-group {
margin-bottom: 15px;
</style>
<style scoped lang="less">
.menu {
position: fixed;
right: 0;
bottom: 15px;
z-index: 99;
}
&-links {
padding: 15px 0;
font-size: 12px;
text-align: center;
background-color: #fff;
.van-icon {
.menu a {
display: block;
font-size: 24px;
width: 72px;
color: #fff;
background: rgba(25, 137, 250, 0.6);
padding: 8px 20px;
border-radius: 30px 0 0 30px;
margin-bottom: 15px;
}
.button {
padding: 0px;
margin: 0px;
}
.el-button--mini {
padding: 0.5rem 1rem;
}
.el-button--mini + .el-button--mini {
margin-left: 0px;
}
.table-box{
width: 95%;
}
</style>

15
plugins/vant.js

@ -1,17 +1,8 @@
// 目前在 nuxt 中无法按需引入样式,因此采用手动引入的方式
import 'vant/lib/index.css';
import { Button, Cell, CellGroup, Col, Icon, Image, Row } from 'vant';
import 'element-plus/dist/index.css';
import ElementPlus from 'element-plus/lib';
import { defineNuxtPlugin } from '#app';
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.vueApp
.use(Row)
.use(Col)
.use(Image)
.use(Icon)
.use(Cell)
.use(Button)
.use(CellGroup);
  nuxtApp.vueApp.use(ElementPlus);
});

BIN
public/statistics.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

15
rest/http-client.env.json

@ -0,0 +1,15 @@
{
"$shared": {
"version": "v1",
"identifier": "wally",
"credential": "111111"
},
"dev": {
"name": "dev",
"url": "https://test.tall.wiki/gateway"
},
"local": {
"version": "v2",
"url": "https://test.tall.wiki/gateway"
}
}

109
rest/财务条.http

@ -0,0 +1,109 @@
@localhost = http://localhost:7320/v1.0
@localhost_tall = http://localhost:7130/v3.0
@test_tall = http://192.168.0.99:7130/v3.0
@test = https://test.tall.wiki/ptccsens/v1.0
@www_tall = http://www.tall.wiki:7130/v3.0
@www = http://www.tall.wiki/ptccsens/v1.0
@type = content-type: application/json;charset=utf-8
### 登录
# song 1218763410024566784
# @name login
POST {{localhost_tall}}/users/signin
{{type}}
{
"client": 1,
"type": 3,
"data": {
"identifier": "whj",
"credential": "123456"
}
}
### debug
GET {{test}}/debug
{{type}}
Authorization: Bearer {{login.response.body.$.data.token}}
###查询费用申请类型
POST {{localhost}}/finance/queryType
{{type}}
Authorization: Bearer {{login.response.body.$.data.token}}
{
"param":{
"parentId":"4",
"type":2
}
}
###发起申请
POST {{localhost}}/finance/apply
{{type}}
Authorization: Bearer {{login.response.body.$.data.token}}
{
"param":{
"checkerList": [
123
],
"invoiceList": [
{
"invoiceCode": "014002100112",
"invoiceNumber": "86610940",
"invoiceTime": 1643075118954,
"money": 4190,
"remark": "业务招待支出",
"taxMoney": 126,
"url": "https://alifei04.cfp.cn/creative/vcg/800/new/VCG211363439424.jpg"
}
],
"projectId": 2,
"taskDetailId": 3,
"remark": "",
"department": "视觉传达事业部",
"submitName": "黛西",
"money": 4190,
"typeId": 1,
"categoryId": 4,
"rowId": 7
}
}
###查询申请详情
POST {{localhost}}/finance/getApplyDetail
{{type}}
Authorization: Bearer {{login.response.body.$.data.token}}
{
"param":{
"applyId":"1485797754654695424"
}
}
###审批
POST {{localhost}}/finance/audit
{{type}}
Authorization: Bearer {{login.response.body.$.data.token}}
{
"param":{
"checkStatus":"2",
"financeCheckId":"1485797754923130880",
"remark":"测试"
}
}
###通过任务id查看任务关联的财务信息
POST {{localhost}}/finance/getByTask
{{type}}
Authorization: Bearer {{login.response.body.$.data.token}}
{
"param":{
"taskDetailId":"3"
}
}

36
utils/num.js

@ -0,0 +1,36 @@
const num = {
// 清除浮点
// 乘法
ride(a, b) {
var as = Math.pow(10, digitLength(a))
var as2 = Math.pow(10, digitLength(b))
if (digitLength(a) >= digitLength(b)) {
return (as * a * as * b) / as / as//这里要除以最小公倍数的平方
} else {
return (as2 * a * as2 * b) / as2 / as2
}
function digitLength(e) {
var e1 = (e + '').split('');
var e2 = e1.findIndex((item) => item = '.')
return e1.length - 1 - e2;
}
},
// 除法
except(a, b) {
var as = Math.pow(10, digitLength(a))
var as2 = Math.pow(10, digitLength(b))
if (digitLength(a) >= digitLength(b)) {
return ((as * a) / (as * b))
} else {
return ((as2 * a) / (as2 * b))
}
function digitLength(e) {
var e1 = (e + '').split('');
var e2 = e1.findIndex((item) => item = '.')
return e1.length - 1 - e2;
}
},
};
export default num;
Loading…
Cancel
Save