Browse Source

feat:优化

master
wang 2 weeks ago
parent
commit
b512ccdb81
  1. 2
      src/components/support/file-upload/index.vue
  2. 101
      src/components/system/service-count/excel-statistics-detail.vue
  3. 21
      src/views/business/erp/cost/firm-reports-form.vue
  4. 24
      src/views/business/erp/service/service-applications-form.vue
  5. 31
      src/views/business/erp/service/service-applications-list.vue
  6. 2
      src/views/business/erp/service/service-applications-report-list.vue
  7. 32
      src/views/mobile/service/create.vue
  8. 4
      src/views/mobile/service/detail.vue
  9. 2
      src/views/system/account/components/center/index.vue
  10. 59
      src/views/system/home/home-header.vue
  11. 11
      src/views/system/login/login.less
  12. 1
      src/views/system/login/login.vue
  13. 19
      src/views/system/login3/login.less
  14. 69
      src/views/system/login3/login.vue

2
src/components/support/file-upload/index.vue

@ -213,7 +213,7 @@
},
maxSize: {
type: Number,
default: 10,
default: 50,
},
//
accept: {

101
src/components/system/service-count/excel-statistics-detail.vue

@ -63,10 +63,22 @@
<div class="report-cell">序号</div>
<div class="report-cell">律师姓名</div>
<div class="report-cell">执业证号</div>
<div class="report-cell">季度累计公益服务时长</div>
<div class="report-cell">季度累计公益服务成本</div>
<div class="report-cell">年度累计公益服务时长</div>
<div class="report-cell">年度累计公益服务成本</div>
<div class="report-cell sortable" @click="handleSort('quarterlyServiceDuration')">
季度累计公益服务时长
<span class="sort-icon">{{ getSortIcon('quarterlyServiceDuration') }}</span>
</div>
<div class="report-cell sortable" @click="handleSort('quarterlyServiceCost')">
季度累计公益服务成本
<span class="sort-icon">{{ getSortIcon('quarterlyServiceCost') }}</span>
</div>
<div class="report-cell sortable" @click="handleSort('annualServiceDuration')">
年度累计公益服务时长
<span class="sort-icon">{{ getSortIcon('annualServiceDuration') }}</span>
</div>
<div class="report-cell sortable" @click="handleSort('annualServiceCost')">
年度累计公益服务成本
<span class="sort-icon">{{ getSortIcon('annualServiceCost') }}</span>
</div>
</div>
<!-- 数据行 -->
@ -76,9 +88,7 @@
<div class="report-cell">{{ item.certificateNumber || '-' }}</div>
<div class="report-cell">{{ formatNumber(item.quarterlyServiceDuration) }}</div>
<div class="report-cell">{{ formatCurrency(item.quarterlyServiceCost) }}</div>
<!--<div class="report-cell">0</div>-->
<div class="report-cell">{{ formatNumber(item.annualServiceDuration) }}</div>
<!--<div class="report-cell">0</div>-->
<div class="report-cell">{{ formatCurrency(item.annualServiceCost) }}</div>
</div>
@ -150,6 +160,66 @@ function initYearOptions() {
//
const tableData = ref(props.tableData || []);
// { field: 'annualServiceCost', order: 'desc' | 'asc' | null }
const sortState = ref({ field: '', order: '' });
//
async function handleSort(field) {
let newOrder;
if (sortState.value.field !== field) {
//
newOrder = 'desc';
} else if (sortState.value.order === 'desc') {
//
newOrder = 'asc';
} else {
//
newOrder = 'desc';
}
sortState.value = { field, order: newOrder };
//
queryLoading.value = true;
try {
const params = {
...localQueryForm,
sortField: field,
sortOrder: newOrder
};
const res = await serviceApplicationsApi.statistics(params);
if (res.data && Array.isArray(res.data)) {
tableData.value = res.data.map(item => ({
...item,
quarterlyServiceDuration: Number(item.quarterlyServiceDuration) || 0,
quarterlyServiceCost: Number(item.quarterlyServiceCost) || 0,
annualServiceDuration: Number(item.annualServiceDuration) || 0,
annualServiceCost: Number(item.annualServiceCost) || 0
}));
} else {
tableData.value = [];
}
} catch (error) {
console.error('排序失败', error);
message.error('排序失败');
} finally {
queryLoading.value = false;
}
}
//
function getSortIcon(field) {
if (sortState.value.field !== field) {
return '⇅';
}
if (sortState.value.order === 'asc') {
return '↑';
}
if (sortState.value.order === 'desc') {
return '↓';
}
return '⇅';
}
//
const summaryData = computed(() => {
@ -191,6 +261,8 @@ function formatCurrency(value) {
//
async function handleQuery() {
queryLoading.value = true;
//
sortState.value = { field: '', order: '' };
try {
const params = {
...localQueryForm
@ -368,4 +440,21 @@ onMounted(() => {
.report-row:last-child .report-cell {
border-bottom: none;
}
/* 可排序列样式 */
.report-cell.sortable {
cursor: pointer;
user-select: none;
transition: background-color 0.2s;
}
.report-cell.sortable:hover {
background-color: #003399;
}
.sort-icon {
margin-left: 4px;
font-size: 12px;
opacity: 0.85;
}
</style>

21
src/views/business/erp/cost/firm-reports-form.vue

@ -399,17 +399,21 @@
calculatedPublicWelfareCost.value = Math.min(publicWelfareCost, remainingAllowedCost).toFixed(2);
const actualTotalCost = annualActualCost.value + parseFloat(calculatedPublicWelfareCost.value);
const ratio = (actualTotalCost / totalRevenue) * 100;
// = /
const ratio = (parseFloat(calculatedPublicWelfareCost.value) / totalRevenue) * 100;
form.costIncomeRatio = ratio.toFixed(2);
} else {
calculatedPublicWelfareCost.value = publicWelfareCost.toFixed(2);
form.costIncomeRatio = costRatio.value.toFixed(2);
// = /
const ratio = (publicWelfareCost / totalRevenue) * 100;
form.costIncomeRatio = ratio.toFixed(2);
}
} else {
showCostWarning.value = false;
calculatedPublicWelfareCost.value = publicWelfareCost.toFixed(2);
form.costIncomeRatio = costRatio.value.toFixed(2);
// = /
const ratio = (publicWelfareCost / totalRevenue) * 100;
form.costIncomeRatio = ratio.toFixed(2);
}
} else {
showCostWarning.value = false;
@ -484,15 +488,10 @@
//
saveData.publicWelfareCost = parseFloat(calculatedPublicWelfareCost.value);
//
// /
const totalRevenue = safeParseFloat(saveData.revenue) + safeParseFloat(annualIncomeInfo.value.revenue);
const annualTotalCost = annualActualCost.value + safeParseFloat(saveData.publicWelfareCost);
let ratio = totalRevenue > 0 ? ((annualTotalCost / totalRevenue) * 100) : 0;
const ratio = totalRevenue > 0 ? ((safeParseFloat(saveData.publicWelfareCost) / totalRevenue) * 100) : 0;
// 25%
if (ratio > 25) {
ratio = 25;
}
saveData.costIncomeRatio = ratio.toFixed(2);
}

24
src/views/business/erp/service/service-applications-form.vue

@ -93,12 +93,12 @@
<a-row :gutter="24">
<a-col :span="8">
<a-form-item label="服务开始时间" name="serviceStart" label-align="left">
<a-date-picker show-time format="YYYY-MM-DD HH:mm:ss" valueFormat="YYYY-MM-DD HH:mm:ss" v-model:value="form.serviceStart" style="width: 100%" placeholder="服务开始时间" :disabled="readonlyMode" />
<a-date-picker show-time format="YYYY-MM-DD HH:mm" valueFormat="YYYY-MM-DD HH:mm:ss" v-model:value="form.serviceStart" style="width: 100%" placeholder="服务开始时间" :disabled="readonlyMode" />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="服务结束时间" name="serviceEnd" label-align="left">
<a-date-picker show-time format="YYYY-MM-DD HH:mm:ss" valueFormat="YYYY-MM-DD HH:mm:ss" v-model:value="form.serviceEnd" style="width: 100%" placeholder="服务结束时间" :disabled="readonlyMode" />
<a-date-picker show-time format="YYYY-MM-DD HH:mm" valueFormat="YYYY-MM-DD HH:mm:ss" v-model:value="form.serviceEnd" style="width: 100%" placeholder="服务结束时间" :disabled="readonlyMode" />
</a-form-item>
</a-col>
<a-col :span="8">
@ -214,6 +214,20 @@
</template>
<SmartWangeditor ref="serviceContentRef" v-model="form.serviceContent" :height="200" :readonly="readonlyMode" />
</a-form-item>
<!-- 备案信息仅详情模式且已备案时展示 -->
<a-row :gutter="16" v-if="readonlyMode && form.recordStatus === 1">
<a-col :span="12">
<a-form-item label="备案编号">
<a-input :value="form.filingNo" :disabled="true" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="备案时间">
<a-input :value="form.recordTime" :disabled="true" />
</a-form-item>
</a-col>
</a-row>
<a-form-item name="proofMaterials" required>
<template #label>
<span class="ant-form-item-required">证明材料</span>
@ -224,7 +238,7 @@
<Upload
:defaultFileList="defaultFileList"
:maxUploadSize="5"
:maxSize="10"
:maxSize="50"
:multiple="true"
accept=".jpg,.jpeg,.png,.gif,.pdf,.doc,.docx,.ppt,.pptx,.PNG,.JPG,.GIF"
:folder="FILE_FOLDER_TYPE_ENUM.COMMON.value"
@ -242,7 +256,7 @@
v-else
:defaultFileList="defaultFileList"
:maxUploadSize="5"
:maxSize="10"
:maxSize="50"
:multiple="true"
accept=".jpg,.jpeg,.png,.gif,.pdf,.doc,.docx,.ppt,.pptx,.PNG,.JPG,.GIF"
:folder="FILE_FOLDER_TYPE_ENUM.COMMON.value"
@ -580,6 +594,8 @@
attachmentIds: undefined, //ID
firmAuditStatus: undefined, //0-1-2-退
recordStatus: undefined, //0-1-
filingNo: undefined, //
recordTime: undefined, //
updateTime: undefined, //
createTime: undefined, //
};

31
src/views/business/erp/service/service-applications-list.vue

@ -62,6 +62,9 @@
<a-form-item label="执业机构审核时间" v-if="isCtoRole || isFirmRole" class="smart-query-form-item">
<a-range-picker style="width: 240px" v-model:value="queryForm.firmAuditTimeRange" :placeholder="['开始时间', '结束时间']" />
</a-form-item>
<a-form-item label="备案编号" class="smart-query-form-item">
<a-input style="width: 160px" v-model:value="queryForm.filingNo" placeholder="请输入备案编号" />
</a-form-item>
<a-form-item class="smart-query-form-item smart-margin-left10">
<a-button v-privilege="'serviceApplications:query'" type="primary" @click="onSearch">
<template #icon>
@ -372,12 +375,14 @@ import dayjs from 'dayjs';
dataIndex: 'serviceStart',
ellipsis: true,
width: 100,
customRender: ({ text }) => text ? dayjs(text).format('YYYY-MM-DD HH:mm') : '',
},
{
title: '服务结束时间',
dataIndex: 'serviceEnd',
ellipsis: true,
width: 100,
customRender: ({ text }) => text ? dayjs(text).format('YYYY-MM-DD HH:mm') : '',
},
{
title: '服务时长(小时)',
@ -450,6 +455,12 @@ import dayjs from 'dayjs';
dataIndex: 'associationAuditUserName',
ellipsis: true,
},
{
title: '备案编号',
dataIndex: 'filingNo',
ellipsis: true,
width: 150,
},
{
title: '操作',
@ -472,6 +483,7 @@ import dayjs from 'dayjs';
firmAuditStatus: undefined, //
firmAuditTimeRange: undefined, //
associationAuditStatus: undefined, //
filingNo: undefined, //
selfFirmFilter: undefined, //
};
// form
@ -1487,19 +1499,18 @@ async function requestDelete(data){
return canReject;
} else if (isCtoRole.value || isFirmAdmin.value) {
// CTO44
if (record.associationAuditStatus !== 4) {
console.log('驳回按钮不显示:协会审核状态不是驳回', record.associationAuditStatus);
return false;
}
// CTO
// 144
// 230/null/undefined
const isAssociationRejected = record.associationAuditStatus === 4 && record.firmAuditStatus !== 4;
const isFirmApprovedNotReported = record.firmAuditStatus === 3 && (record.associationAuditStatus === 0 || record.associationAuditStatus === null || record.associationAuditStatus === undefined);
// 4
if (record.firmAuditStatus === 4) {
console.log('驳回按钮不显示:执业机构审核状态是驳回', record.firmAuditStatus);
return false;
if (isAssociationRejected || isFirmApprovedNotReported) {
return true;
}
return true;
console.log('驳回按钮不显示:不满足(协会已驳回且律所未驳回),也不满足(律所已审核且协会未提交)');
return false;
}
return false;

2
src/views/business/erp/service/service-applications-report-list.vue

@ -290,12 +290,14 @@ import { getRoleInfo } from '/@/utils/role-util';
dataIndex: 'serviceStart',
ellipsis: true,
width: 100,
customRender: ({ text }) => text ? dayjs(text).format('YYYY-MM-DD HH:mm') : '',
},
{
title: '服务结束时间',
dataIndex: 'serviceEnd',
ellipsis: true,
width: 100,
customRender: ({ text }) => text ? dayjs(text).format('YYYY-MM-DD HH:mm') : '',
},
{
title: '服务时长(小时)',

32
src/views/mobile/service/create.vue

@ -118,11 +118,13 @@
<label class="form-label required">服务开始时间</label>
<a-date-picker
show-time
format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm"
valueFormat="YYYY-MM-DD HH:mm:ss"
v-model:value="form.serviceStart"
class="form-input"
placeholder="服务开始时间"
:minuteStep="5"
inputReadOnly
/>
</div>
@ -131,11 +133,13 @@
<label class="form-label required">服务结束时间</label>
<a-date-picker
show-time
format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm"
valueFormat="YYYY-MM-DD HH:mm:ss"
v-model:value="form.serviceEnd"
class="form-input"
placeholder="服务结束时间"
:minuteStep="5"
inputReadOnly
/>
</div>
@ -671,8 +675,8 @@ async function handleFileUpload(event) {
}
for (let file of files) {
if (file.size > 10 * 1024 * 1024) {
message.error(`文件 ${file.name} 超过10MB限制`)
if (file.size > 50 * 1024 * 1024) {
message.error(`文件 ${file.name} 超过50MB限制`)
continue
}
@ -1373,4 +1377,24 @@ onMounted(async () => {
font-size: 14px;
}
}
/* 时间选择器优化(移动端滚动体验) */
:deep(.ant-picker-time-panel) {
touch-action: pan-y;
-webkit-overflow-scrolling: touch;
}
:deep(.ant-picker-time-panel-column) {
-webkit-overflow-scrolling: touch;
overflow-y: auto;
}
:deep(.ant-picker-time-panel-cell-inner) {
font-size: 16px;
padding: 4px 0;
}
:deep(.ant-picker-dropdown) {
max-width: 100vw;
}
</style>

4
src/views/mobile/service/detail.vue

@ -159,7 +159,7 @@
<label class="form-label">服务开始时间</label>
<a-date-picker
show-time
format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm"
valueFormat="YYYY-MM-DD HH:mm:ss"
v-model:value="form.serviceStart"
class="form-input"
@ -173,7 +173,7 @@
<label class="form-label">服务结束时间</label>
<a-date-picker
show-time
format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm"
valueFormat="YYYY-MM-DD HH:mm:ss"
v-model:value="form.serviceEnd"
class="form-input"

2
src/views/system/account/components/center/index.vue

@ -164,7 +164,7 @@
//
const accept = ref('.jpg,.jpeg,.png,.gif,.PNG,.GIF,.JPG');
const maxSize = ref(10);
const maxSize = ref(50);
const folder = ref(FILE_FOLDER_TYPE_ENUM.COMMON.value);
let updateAvatarLoading = ref(false);
function beforeUpload(file, files) {

59
src/views/system/home/home-header.vue

@ -6,7 +6,7 @@
<a-card class="user-header">
<a-page-header :title="welcomeSentence">
<template #subTitle>
<a-typography-text type="secondary" style="margin-left: 20px">所属部门 {{ departmentName }}</a-typography-text>
<a-typography-text type="secondary" style="margin-left: 20px">执业机构 {{ departmentName }}</a-typography-text>
</template>
<template #extra>
<a-typography-text type="secondary">{{ dayInfo }}</a-typography-text>
@ -16,22 +16,13 @@
<p class="last-login-info"><AlertOutlined />{{ lastLoginInfo }}</p>
<a class="sentence" href="#" target="_blank"> <smile-outlined spin /> {{ heartSentence }} </a>
</span>
<div class="weather">
<iframe
width="100%"
scrolling="no"
height="50"
frameborder="0"
allowtransparency="true"
src="//i.tianqi.com/index.php?c=code&id=12&icon=1&num=3&site=12"
></iframe>
</div>
</a-row>
</a-page-header>
</a-card>
</template>
<script setup>
import { computed } from 'vue';
import { computed, ref, onMounted, onUnmounted } from 'vue';
import { useUserStore } from '/@/store/modules/system/user';
import uaparser from 'ua-parser-js';
import { Solar, Lunar } from 'lunar-javascript';
@ -42,6 +33,27 @@
const departmentName = computed(() => userStore.departmentName);
//
const currentTime = ref('');
let timer = null;
function updateTime() {
const now = new Date();
const pad = (n) => String(n).padStart(2, '0');
currentTime.value = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
}
onMounted(() => {
updateTime();
timer = setInterval(updateTime, 1000);
});
onUnmounted(() => {
if (timer) {
clearInterval(timer);
}
});
//
const welcomeSentence = computed(() => {
let sentence = '';
@ -101,12 +113,11 @@
let lunar = Lunar.fromDate(new Date());
let lunarMonth = lunar.getMonthInChinese();
let lunarDay = lunar.getDayInChinese();
//
let jieqi = lunar.getJieQi();
// -
let next = lunar.getNextJieQi();
let nextJieqi = next.getName() + ' ' + next.getSolar().toYmd();
return `${day} 星期${week},农历${lunarMonth}${lunarDay}当前${jieqi}${nextJieqi} `;
return `${day} 星期${week},农历${lunarMonth}${lunarDay}下一个节气:${nextJieqi}`;
});
//
@ -135,9 +146,25 @@
.content {
display: flex;
justify-content: space-between;
align-items: center;
.weather {
.city-time {
width: 400px;
text-align: right;
color: rgba(0, 0, 0, 0.65);
.city {
font-size: 16px;
font-weight: 500;
margin: 0;
color: #1890ff;
}
.time {
font-size: 14px;
margin: 4px 0 0 0;
color: #666;
}
}
}

11
src/views/system/login/login.less

@ -211,4 +211,15 @@
}
}
}
.tech-contact {
position: fixed;
bottom: 20px;
left: 0;
right: 0;
text-align: center;
color: #ffffff;
font-size: 14px;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
}
}

1
src/views/system/login/login.vue

@ -31,6 +31,7 @@
</a-form-item>
</a-form>
</div>
<div class="tech-contact">技术支持电话18226700205</div>
</div>
</template>
<script setup>

19
src/views/system/login3/login.less

@ -4,8 +4,16 @@
background: url(/@/assets/images/login/login-bg.png) no-repeat center;
background-size: cover;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.box-wrapper {
display: flex;
align-items: center;
justify-content: center;
}
.box-item {
width: 460px;
height: 600px;
@ -24,6 +32,8 @@
border-radius: 0px 12px 12px 0px;
padding: 34px 42px;
position: relative;
display: flex;
flex-direction: column;
}
.login-qr {
position: absolute;
@ -170,6 +180,15 @@
line-height: 50px;
cursor: pointer;
}
.tech-contact {
text-align: center;
color: #333333;
font-size: 13px;
margin-top: auto;
margin-bottom: 0;
padding-bottom: 0;
}
}
.more {

69
src/views/system/login3/login.vue

@ -4,40 +4,43 @@
-->
<template>
<div class="login-container">
<div class="box-item desc">
<div class="welcome">
<p>欢迎登录</p>
<p class="sub-welcome">合肥市律师公益法律服务管理系统</p>
<div class="box-wrapper">
<div class="box-item desc">
<div class="welcome">
<p>欢迎登录</p>
<p class="sub-welcome">合肥市律师公益法律服务管理系统</p>
</div>
<img class="welcome-img" :src="loginGif" />
</div>
<div class="box-item login">
<img class="login-qr" :src="loginQR" />
<div class="login-title">账号登录</div>
<a-form ref="formRef" class="login-form" :model="loginForm" :rules="rules">
<a-form-item name="loginName">
<a-input v-model:value.trim="loginForm.loginName" placeholder="请输入用户名" />
</a-form-item>
<a-form-item name="password">
<a-input-password
v-model:value="loginForm.password"
autocomplete="on"
:type="showPassword ? 'text' : 'password'"
placeholder="请输入密码:至少三种字符,最小 8 位"
/>
</a-form-item>
<a-form-item name="captchaCode">
<a-input class="captcha-input" v-model:value.trim="loginForm.captchaCode" placeholder="请输入验证码" />
<img class="captcha-img" :src="captchaBase64Image" @click="getCaptcha" />
</a-form-item>
<a-form-item>
<a-checkbox v-model:checked="rememberPwd">记住密码</a-checkbox>
</a-form-item>
<a-form-item>
<div class="btn" @click="onLogin">登录</div>
</a-form-item>
</a-form>
<div class="tech-contact">技术支持联系电话王经理 18226700205</div>
</div>
<img class="welcome-img" :src="loginGif" />
</div>
<div class="box-item login">
<img class="login-qr" :src="loginQR" />
<div class="login-title">账号登录</div>
<a-form ref="formRef" class="login-form" :model="loginForm" :rules="rules">
<a-form-item name="loginName">
<a-input v-model:value.trim="loginForm.loginName" placeholder="请输入用户名" />
</a-form-item>
<a-form-item name="password">
<a-input-password
v-model:value="loginForm.password"
autocomplete="on"
:type="showPassword ? 'text' : 'password'"
placeholder="请输入密码:至少三种字符,最小 8 位"
/>
</a-form-item>
<a-form-item name="captchaCode">
<a-input class="captcha-input" v-model:value.trim="loginForm.captchaCode" placeholder="请输入验证码" />
<img class="captcha-img" :src="captchaBase64Image" @click="getCaptcha" />
</a-form-item>
<a-form-item>
<a-checkbox v-model:checked="rememberPwd">记住密码</a-checkbox>
</a-form-item>
<a-form-item>
<div class="btn" @click="onLogin">登录</div>
</a-form-item>
</a-form>
</div>
</div>
</template>

Loading…
Cancel
Save