7/17 添加公区收益

This commit is contained in:
Zy
2026-07-21 17:32:22 +08:00
parent 37970c547b
commit 0c4c9676b5
41 changed files with 1583 additions and 132 deletions

View File

@@ -1,6 +1,6 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { ResidentVO, ResidentForm, ResidentQuery } from '@/api/system/resident/type';
import { ResidentVO, ResidentForm, ResidentQuery } from '@/api/system/residentReviewProcess/resident/type';
/**
*

View File

@@ -0,0 +1,63 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { RevenueNoticeVO, RevenueNoticeForm, RevenueNoticeQuery } from './type';
/**
* 查询公区收益通知列表
* @param query
* @returns {*}
*/
export const listRevenueNotice = (query?: RevenueNoticeQuery): AxiosPromise<RevenueNoticeVO[]> => {
return request({
url: '/revenueNotice/list',
method: 'get',
params: query
});
};
/**
* 查询公区收益通知详细
* @param noticeId
*/
export const getRevenueNotice = (noticeId: string | number): AxiosPromise<RevenueNoticeVO> => {
return request({
url: '/revenueNotice/' + noticeId,
method: 'get'
});
};
/**
* 新增公区收益通知
* @param data
*/
export const addRevenueNotice = (data: RevenueNoticeForm) => {
return request({
url: '/revenueNotice',
method: 'post',
data: data
});
};
/**
* 修改公区收益通知
* @param data
*/
export const updateRevenueNotice = (data: RevenueNoticeForm) => {
return request({
url: '/revenueNotice',
method: 'put',
data: data
});
};
/**
* 删除公区收益通知
* @param noticeId
*/
export const delRevenueNotice = (noticeId: string | number | Array<string | number>) => {
return request({
url: '/revenueNotice/' + noticeId,
method: 'delete'
});
};

View File

@@ -0,0 +1,140 @@
export interface RevenueNoticeVO {
/**
* 公告ID
*/
noticeId: string | number;
/**
* 公告标题
*/
noticeTitle: string;
/**
* 公告内容
*/
noticeContent: string;
/**
* 作者
*/
author: string;
/**
* 紧急程度 0=普通 1=紧急 2=非常紧急
*/
urgencyLevel: number;
/**
* 发布状态 0=草稿 1=已发布
*/
publishStatus: number;
/**
* 发布人
*/
publishBy: string;
/**
* 发布日期
*/
publishTime: string;
/**
* 所属小区id
*/
villageId: string | number;
}
export interface RevenueNoticeForm extends BaseEntity {
/**
* 公告ID
*/
noticeId?: string | number;
/**
* 公告标题
*/
noticeTitle?: string;
/**
* 公告内容
*/
noticeContent?: string;
/**
* 作者
*/
author?: string;
/**
* 紧急程度 0=普通 1=紧急 2=非常紧急
*/
urgencyLevel?: number;
/**
* 发布状态 0=草稿 1=已发布
*/
publishStatus?: number;
/**
* 发布人
*/
publishBy?: string;
/**
* 发布日期
*/
publishTime?: string;
/**
* 所属小区id
*/
villageId?: string | number;
}
export interface RevenueNoticeQuery extends PageQuery {
/**
* 公告标题
*/
noticeTitle?: string;
/**
* 公告内容
*/
noticeContent?: string;
/**
* 作者
*/
author?: string;
/**
* 紧急程度 0=普通 1=紧急 2=非常紧急
*/
urgencyLevel?: number;
/**
* 发布状态 0=草稿 1=已发布
*/
publishStatus?: number;
/**
* 发布人
*/
publishBy?: string;
/**
* 发布日期
*/
publishTime?: string;
/**
* 所属小区id
*/
villageId?: string | number;
/**
* 日期范围参数
*/
params?: any;
}

View File

@@ -65,6 +65,33 @@ export const workSpaceIncomeTrendListApi = (
}
});
};
/**
* 收费率统计
* @returns {*}
*/
export const paymentRateApi = (
startDate: string,
endDate: string
): Promise<{
data: {
'receivableAmount': string;
'receivablePercent': string;
'receivedAmount': string;
'receivedPercent': string;
'unpaidAmount': string;
'unpaidPercent': string;
};
}> => {
return request({
url: '/workSpace/paymentRate',
method: 'GET',
params: {
startDate: startDate,
endDate: endDate
}
});
};
/**
* 查询近一周支出趋势
* @returns {*}

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1784617479089" class="icon" viewBox="0 0 1057 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1942" xmlns:xlink="http://www.w3.org/1999/xlink" width="206.4453125" height="200"><path d="M811.80137 607.92511a170.673929 170.673929 0 1 0 170.761074 170.695715 170.870006 170.870006 0 0 0-170.673929-170.673929m0-74.661674A245.357389 245.357389 0 1 1 566.443981 778.620825a245.357389 245.357389 0 0 1 245.357389-245.357389z" p-id="1943" fill="#2c2c2c"></path><path d="M873.718055 889.447768a251.413982 251.413982 0 0 1-3.442237-7.973788c-1.568614-3.616527-2.984724-6.971618-4.466193-10.348496a1687.218757 1687.218757 0 0 1-132.787541 13.529297l-4.771201 0.326794-5.816944-31.067273 4.553338-1.394324c10.32671-3.355091 30.195821-24.24816 66.666099-104.835709l2.047913-4.553338 32.309094 11.132802-2.592571 5.555509a669.209991 669.209991 0 0 1-51.08889 90.60925c26.535722-2.178631 52.505-4.945492 77.297817-8.540232a388.340922 388.340922 0 0 0-20.413769-34.967023l-3.921536-5.882302 30.50083-10.413855 2.178631 3.42045a501.455427 501.455427 0 0 1 44.378707 81.611506l2.178631 4.945492-30.740479 13.856091z m26.513935-91.502489a233.919578 233.919578 0 0 1-72.766265-114.792051l-1.263606-4.139399 28.627207-16.274371 2.047913 6.535892a209.802136 209.802136 0 0 0 66.361091 102.047062l4.204758 3.59474-23.137058 26.535722z m-204.050551-19.607676l4.357261-4.139399a236.686439 236.686439 0 0 0 61.655249-103.049232l1.786477-5.555508 30.91477 12.265691-1.633973 4.858346a277.470406 277.470406 0 0 1-67.537552 114.639548l-3.59474 3.442236z" p-id="1944" fill="#2c2c2c"></path><path d="M515.267946 643.240713l142.090294 13.311434-13.33322-140.151313 13.33322-140.020595-145.968257 13.071784-157.013914-12.810348 13.485724 135.075103-13.071784 144.138207z m-81.872942-188.887281l78.299988 6.383388 66.949321-6.034807-5.882303 61.698821 5.860517 61.524531-62.83171-5.882303-82.787966 6.535892 6.100166-67.145398z" p-id="1945" fill="#2c2c2c"></path><path d="M589.297817 900.863793a408.798264 408.798264 0 1 1 312.764223-317.317561 45.903749 45.903749 0 1 0 89.933875 18.365857 500.583975 500.583975 0 1 0-383.025063 388.602357 45.903749 45.903749 0 1 0-19.607676-89.650653z" p-id="1946" fill="#2c2c2c"></path></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -1,17 +1,37 @@
<template>
<!-- 原有图片上传 -->
<div>
<el-upload
v-if="type"
action=""
:action="uploadUrl"
:headers="headerToken()"
:before-upload="handleBeforeUpload"
:http-request="handleUploadRequest"
class="editor-img-uploader"
name="file"
name="files"
:show-file-list="false"
>
<i ref="uploadRef"></i>
</el-upload>
</div>
<!-- 新增文件附件上传Word/PDF/Excel -->
<div>
<el-upload
v-if="type"
:action="uploadUrl"
:headers="headerToken()"
:before-upload="handleBeforeFileUpload"
:http-request="handleFileUploadRequest"
class="editor-file-uploader"
name="files"
accept=".pdf,.doc,.docx,.xls,.xlsx,.zip"
:show-file-list="false"
>
<i ref="fileUploadRef"></i>
</el-upload>
</div>
<div class="editor">
<quill-editor
ref="quillEditorRef"
@@ -19,7 +39,7 @@
content-type="html"
:options="options"
:style="styles"
@text-change="(e: any) => $emit('update:modelValue', content)"
@text-change="() => $emit('update:modelValue', content)"
/>
</div>
</template>
@@ -27,9 +47,11 @@
<script setup lang="ts">
import '@vueup/vue-quill/dist/vue-quill.snow.css';
import { QuillEditor, Quill } from '@vueup/vue-quill';
import { Quill, QuillEditor } from '@vueup/vue-quill';
import { propTypes } from '@/utils/propTypes';
import type { UploadRequestHandler, UploadRequestOptions } from 'element-plus';
import { useUserStore } from '@/store/modules/user';
import axios from 'axios';
defineEmits(['update:modelValue']);
@@ -45,14 +67,20 @@ const props = defineProps({
/* 上传文件大小限制(MB) */
fileSize: propTypes.number.def(5),
/* 类型base64格式、url格式 */
type: propTypes.string.def('base64')
type: propTypes.string.def('url')
});
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const quillEditorRef = ref();
const useStore = useUserStore();
function headerToken() {
return {
Authorization: 'Bearer ' + useStore.token
};
}
const uploadRef = ref<HTMLDivElement>();
const uploadUrl = import.meta.env.VITE_APP_BASE_API + '/handleLog/uploadAttachments';
const options = ref<any>({
theme: 'snow',
bounds: document.body,
@@ -70,7 +98,8 @@ const options = ref<any>({
[{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
[{ align: [] }], // 对齐方式
['clean'], // 清除文本格式
['link', 'image', 'video'] // 链接、图片、视频
// ['image', 'video', 'link'] // 图片、视频、链接、文件
['image'] // 图片
],
handlers: {
image: (value: boolean) => {
@@ -83,8 +112,9 @@ const options = ref<any>({
},
link: function (value) {
if (value) {
const href = prompt('输入文件链接地址');
Quill.format('link', href);
// const href = prompt('输入网络文件链接地址');
// Quill.format('link', href);
fileUploadRef.value?.click();
} else {
Quill.format('link', false);
}
@@ -140,47 +170,218 @@ const handleBeforeUpload = (file: any) => {
};
// base64 模式插入图片
const handleUploadRequest: UploadRequestHandler = (options: UploadRequestOptions) => {
return new Promise<void>((resolve, reject) => {
const file = options.file as File;
const quill = toRaw(quillEditorRef.value)?.getQuill();
if (!quill) {
proxy?.$modal.msgError('编辑器未就绪');
proxy?.$modal.closeLoading();
reject(new Error('editor not ready'));
return;
}
const reader = new FileReader();
reader.onload = () => {
const base64 = reader.result as string;
const range = quill.selection?.savedRange;
const length = range ? range.index : quill.getLength();
quill.insertEmbed(length, 'image', base64);
quill.setSelection(length + 1);
proxy?.$modal.closeLoading();
options.onSuccess?.({ url: base64 });
resolve();
};
reader.onerror = () => {
proxy?.$modal.msgError('图片插入失败');
proxy?.$modal.closeLoading();
const err = Object.assign(new Error('read image failed'), {
status: 0,
method: 'POST',
url: options.action || ''
});
options.onError?.(err as any);
reject(err);
};
reader.readAsDataURL(file);
});
};
</script>
const handleUploadRequest: UploadRequestHandler = async (options: UploadRequestOptions) => {
const file = options.file as File;
const quill = toRaw(quillEditorRef.value)?.getQuill();
if (!quill) {
proxy?.$modal.msgError('编辑器未就绪');
proxy?.$modal.closeLoading();
return Promise.reject(new Error('editor not ready'));
}
const range = quill.selection?.savedRange;
const insertIndex = range ? range.index : quill.getLength();
<style>
.editor-img-uploader {
display: none;
if (props.type === 'base64') {
return new Promise<void>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const base64 = reader.result as string;
const range = quill.selection?.savedRange;
const length = range ? range.index : quill.getLength();
quill.insertEmbed(length, 'image', base64);
quill.setSelection(length + 1);
proxy?.$modal.closeLoading();
options.onSuccess?.({ url: base64 });
resolve();
};
reader.onerror = () => {
proxy?.$modal.msgError('图片插入失败');
proxy?.$modal.closeLoading();
const err = Object.assign(new Error('read image failed'), {
status: 0,
method: 'POST',
url: options.action || ''
});
options.onError?.(err as any);
reject(err);
};
reader.readAsDataURL(file);
});
}
try {
const formData = new FormData();
formData.append('files', file); // 和ImageExtend配置name保持一致 img
const res = await axios.post(uploadUrl, formData, {
headers: {
...headerToken(),
'Content-Type': 'multipart/form-data'
}
});
const successList = res.data.data.success;
if (successList.length > 0) {
// 多张图片依次向后插入,不覆盖光标
let index = insertIndex;
for (let i = 0; i < successList.length; i++) {
// 根据后端返回结构取图片地址
const imgUrl = successList[i].url;
// 插入真实图片地址到富文本
quill.insertEmbed(index, 'image', imgUrl);
index += 1;
proxy?.$modal.closeLoading();
options.onSuccess?.({ url: imgUrl });
}
} else {
proxy?.$modal.msgError('图片上传服务器失败');
return Promise.reject();
}
return Promise.resolve(null);
} catch (err) {
proxy?.$modal.msgError('图片上传服务器失败');
options.onError?.(err as any);
return Promise.reject(err);
} finally {
proxy?.$modal.closeLoading();
}
};
// 解决【粘贴图片自动base64】核心修复
// 监听编辑器粘贴事件,拦截粘贴图片并调用上传接口
let pasteHandler: ((e: ClipboardEvent) => Promise<void>) | null = null;
watch(
quillEditorRef,
(val) => {
if (!val || props.type !== 'url') return;
const quill = toRaw(val).getQuill();
pasteHandler = async (e: ClipboardEvent) => {
const items = e.clipboardData?.items;
if (!items) return;
const fileItems = Array.from(items).filter((it) => it.kind === 'file');
if (!fileItems.length) return;
e.preventDefault();
proxy?.$modal.loading('正在上传粘贴图片...');
for (const item of fileItems) {
const file = item.getAsFile();
if (!file) continue;
const pass = handleBeforeUpload(file);
if (!pass) {
proxy?.$modal.closeLoading();
return;
}
// 构造合法参数,去除不安全强转
await handleUploadRequest({
file,
action: uploadUrl,
onSuccess: () => {},
onError: () => {},
data: [],
name: 'files'
} as unknown as UploadRequestOptions);
}
};
quill.container.addEventListener('paste', pasteHandler);
},
{ flush: 'post' }
);
// 新增文件上传ref
const fileUploadRef = ref<HTMLElement>();
// 文件上传前校验(限制文档格式)
const handleBeforeFileUpload = (file: File) => {
const allowTypes = [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/zip'
];
const ext = file.name.split('.').pop();
const allowExt = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'zip'];
if (!allowTypes.includes(file.type) && !allowExt.includes(ext)) {
proxy?.$modal.msgError('仅支持PDF、Word、Excel、Zip文件');
return false;
}
// 文件大小限制
const maxByte = props.fileSize * 1024 * 1024;
if (file.size > maxByte) {
proxy?.$modal.msgError(`文件不能超过${props.fileSize}MB`);
return false;
}
proxy?.$modal.loading('附件上传中...');
return true;
};
// 文件自定义上传逻辑上传成功插入a链接
const handleFileUploadRequest: UploadRequestHandler = async (options) => {
const file = options.file as File;
const quill = toRaw(quillEditorRef.value)?.getQuill();
if (!quill) {
proxy?.$modal.msgError('编辑器未就绪');
proxy?.$modal.closeLoading();
return Promise.reject();
}
const insertIndex = quill.selection?.savedRange?.index ?? quill.getLength();
try {
const formData = new FormData();
formData.append('files', file);
const res = await axios.post(uploadUrl, formData, {
headers: {
...headerToken(),
'Content-Type': 'multipart/form-data'
}
});
const successList = res.data.data?.success ?? [];
if (!successList.length) {
throw new Error('上传无返回地址');
}
// 循环插入下载链接 <a href="url" target="_blank">文件名</a>
let curIndex = insertIndex;
successList.forEach((item) => {
// 拼接带下载属性的a标签HTML
const linkHtml = `<a href="${item.url}" target="_blank" download="${file.name}" style="color:#409EFF;text-decoration:underline;margin:0 4px;">${file.name}</a>`;
// 在光标位置插入HTML链接
quill.clipboard.dangerouslyPasteHTML(curIndex, linkHtml);
curIndex += linkHtml.length;
});
quill.setSelection(curIndex);
options.onSuccess?.({ url: successList[0].url });
} catch (err) {
proxy?.$modal.msgError('附件上传失败');
options.onError?.(err as any);
return Promise.reject(err);
} finally {
proxy?.$modal.closeLoading();
}
};
onUnmounted(() => {
const quill = toRaw(quillEditorRef.value)?.getQuill();
if (quill) {
if (pasteHandler) {
quill.container.removeEventListener('paste', pasteHandler);
pasteHandler = null;
}
quill.destroy();
}
});
</script>
<style scoped>
:deep(.ql-snow .ql-toolbar button.ql-file) {
width: 40px;
font-size: 12px;
}
:deep(.ql-snow .ql-toolbar button.ql-file::before) {
all: unset !important;
content: '附件';
display: block;
text-align: center;
line-height: 24px;
}
</style>
<style scoped>
.editor,
.ql-toolbar {
white-space: pre-wrap !important;

View File

@@ -76,8 +76,8 @@
</template>
<script setup lang="ts">
import { listResident } from '@/api/system/resident';
import { ResidentVO } from '@/api/system/resident/type';
import { listResident } from '@/api/system/residentReviewProcess/resident';
import { ResidentVO } from '@/api/system/residentReviewProcess/resident/type';
import { ref, nextTick, getCurrentInstance, ComponentInternalInstance, toRefs } from 'vue';
import { useHouseStore } from '@/store/modules/house';

View File

@@ -64,7 +64,7 @@
</template>
<script setup lang="ts">
import { ResidentVO } from '@/api/system/resident/type';
import { ResidentVO } from '@/api/system/residentReviewProcess/resident/type';
import { getUserByRoleKey } from '@/api/system/user';
import { ref, watch, nextTick, getCurrentInstance, ComponentInternalInstance, toRefs } from 'vue';

View File

@@ -87,7 +87,7 @@ const toggleSideBar = () => {
};
const logout = async () => {
await ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
await ElMessageBox.confirm('确定退出系统吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'

View File

@@ -156,9 +156,10 @@ export function toFixedMoney(num: string | number, digit = 2): number {
* 智能金额格式化(带单位:元/万元/亿元)
* 兼容数字、字符串、null、undefined
* @param {number|string} amount 金额
* @param {number} unitNumber 保留几个小数 默认 2位
* @returns {string} 格式化后带单位的金额
*/
export function formatMoneyWithUnit(amount) {
export function formatMoneyWithUnit(amount: number | string, unitNumber: number = 2): string {
// 空值处理
if (amount == null || amount === '') return '0.00 元';
@@ -170,13 +171,13 @@ export function formatMoneyWithUnit(amount) {
if (absNum >= 100000000) {
// 亿元
return (num / 100000000).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',') + ' 亿元';
return (num / 100000000).toFixed(unitNumber).replace(/\B(?=(\d{3})+(?!\d))/g, ',') + ' 亿元';
} else if (absNum >= 10000) {
// 万元
return (num / 10000).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',') + ' 万元';
return (num / 10000).toFixed(unitNumber).replace(/\B(?=(\d{3})+(?!\d))/g, ',') + ' 万元';
} else {
// 元
return num.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',') + ' 元';
return num.toFixed(unitNumber).replace(/\B(?=(\d{3})+(?!\d))/g, ',') + ' 元';
}
}
// 带货币符号(¥)版本

View File

@@ -327,6 +327,9 @@ export function printTicketDiv(elementId: string, title: string = '票据打印'
table-layout: fixed;
margin: 0.1cm 0;
page-break-inside: avoid !important;
transform-origin: top left;
/* 初始不缩放由JS动态控制 */
page-break-after: avoid !important;
}
table th, table td {
border: 1px solid #333;
@@ -360,6 +363,154 @@ export function printTicketDiv(elementId: string, title: string = '票据打印'
</html>
`);
win.document.close();
win.onload = () => {
const printContainer = win.document.getElementById(elementId);
if (!printContainer) {
win.close();
return;
}
setTimeout(() => {
const containerHeightPx = printContainer.clientHeight;
const contentHeightPx = printContainer.scrollHeight;
const containerWidthPx = printContainer.clientWidth;
const contentWidthPx = printContainer.scrollWidth;
// 固定顶部偏移量约0.2cm确保打印边缘不裁切内容╬D2╬╬2Σ╬╬╬╬2╬2222╬╬2╬22╬22╬22╬2╬╬2╬╬
const offsetPx = 20; // 可根据实际效果调整
// 计算可用高度(减去偏移)
const availableHeight = containerHeightPx - offsetPx;
// 如果内容高度超过可用高度,或宽度超过容器宽度,则等比缩放
let scale = 1;
let needScale = false;
if (contentHeightPx > availableHeight) {
scale = availableHeight / contentHeightPx;
needScale = true;
}
if (contentWidthPx > containerWidthPx) {
const scaleW = containerWidthPx / contentWidthPx;
if (scaleW < scale) {
scale = scaleW;
needScale = true;
}
}
// 应用 transform先缩放如果需要再整体下移固定偏移
if (needScale) {
printContainer.style.transform = `translateY(${offsetPx}px) scale(${scale})`;
} else {
// 即使不缩放,也下移避免顶部贴边
printContainer.style.transform = `translateY(${offsetPx}px)`;
}
// 确保缩放原点在左上,保证缩放后位置正确
printContainer.style.transformOrigin = 'top left';
// 执行打印
win.print();
win.close();
callback && callback();
}, 300); // 等待渲染完成
};
}
export function printTicketDiv2(elementId: string, title: string = '票据打印', callback?: () => void) {
const el = document.getElementById(elementId);
if (!el) {
console.error('未找到打印元素');
return;
}
const win = window.open('', '_blank', 'width=1200,height=1000');
if (!win) return;
// 复制全局非scoped样式Element Plus、全局公共样式
const styles = Array.from(document.querySelectorAll('link[rel="stylesheet"], style:not([scoped])'))
.map((item) => item.outerHTML)
.join('');
// 专属打印样式,根治空白第二页
const printScopeStyle = `
<style>
* {
box-sizing: border-box;
margin: 0 !important;
padding: 0 !important;
}
html, body {
height: auto !important;
min-height: auto !important;
margin: 0 !important;
padding: 0 !important;
background: #fff;
font-size: 12px;
font-family: "Microsoft YaHei", sans-serif;
}
/* 隐藏弹窗无关组件:输入框、按钮、弹窗头部 */
.el-input {
display: none !important;
}
/* 纸张自适应宽高,无边距 */
@page {
size: auto auto;
margin: 0mm !important;
}
/* 打印容器核心禁止分页 */
#${elementId} {
margin: 0 auto !important;
overflow: visible;
page-break-inside: avoid !important;
page-break-after: avoid !important;
break-inside: avoid !important;
break-after: avoid !important;
}
/* 表格禁止截断、压缩高度 */
table {
border-collapse: collapse;
width: 100% !important;
table-layout: fixed;
margin: 0.1cm 0;
page-break-inside: avoid !important;
break-inside: avoid !important;
}
table th, table td {
border: 1px solid #333;
font-size: 10px;
height:32px; /* 缩小行高减少整体高度 */
min-width:80px;
text-align: center;
padding: 1px 2px;
line-height: 1.1;
white-space: normal;
overflow: hidden;
page-break-inside: avoid !important;
}
</style>
`;
// 只复制票据内部innerHTML不带外层el-dialog多余结构
const printContent = el.innerHTML;
win.document.write(`
<!DOCTYPE html>
<html>
<head>
<title>${title}</title>
<meta charset="UTF-8">
${styles}
${printScopeStyle}
</head>
<body>
<div id="${elementId}">${printContent}</div>
</body>
</html>
`);
win.document.close();
win.onload = () => {
setTimeout(() => {
win.print();

View File

@@ -245,13 +245,13 @@ service.interceptors.response.use(
}
);
// 通用下载方法
export function download(url: string, params: any, fileName: string) {
export function download(url: string, params: any, fileName: string, format: boolean = true) {
downloadLoadingInstance = ElLoading.service({ text: '正在下载数据,请稍候', background: 'rgba(0, 0, 0, 0.7)' });
// prettier-ignore
return service.post(url, params, {
transformRequest: [
(params: any) => {
return tansParams(params);
return format ? tansParams(params) : JSON.stringify(params);
}
],
headers: { 'Content-Type': 'application/x-www-form-urlencoded',silentError: 'true' },

View File

@@ -108,7 +108,7 @@ import repairUser from '@/components/Holder/repairUser.vue';
import PageHeader from '@/components/Pageheader/index.vue';
import { getComplaint, updateComplaint } from '@/api/system/Complaint';
import { listComplaintType } from '@/api/system/ComplainType';
import { ResidentVO } from '@/api/system/resident/type';
import { ResidentVO } from '@/api/system/residentReviewProcess/resident/type';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { com_complaint_status } = toRefs<any>(proxy?.useDict('com_complaint_status'));

View File

@@ -57,23 +57,25 @@
<el-col :span="12">
<el-form-item label="附属材料">
<el-upload
:auto-upload="false"
class="upload-demo"
:file-list="fileList"
:action="uploadUrl"
:multiple="true"
:auto-upload="false"
ref="uploadRef"
:show-file-list="true"
name="files"
:headers="headerToken()"
:show-file-list="true"
:on-remove="handleAvatarRemove"
:on-change="handleProgress"
:before-upload="beforeAvatarUpload"
accept=".doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png,.gif,.bmp,.webp"
>
<div class="uploadBtn flex flex-items-center">
<el-icon><Upload /></el-icon>
<span>上传文件</span>
</div>
<template #tip>仅支持doc/docx/xls/xlsx/jpg/png/gif等</template>
</el-upload>
</el-form-item>
</el-col>
@@ -144,14 +146,16 @@ const handleAvatarRemove: UploadProps['onRemove'] = (uploadFile: UploadUserFile,
// 定义大小限制常量 (单位: MB)
const MAX_SINGLE_FILE_SIZE = 10 * 1024 * 1024; // 10MB in bytes
const MAX_TOTAL_FILE_SIZE = 20 * 1024 * 1024; // 20MB in bytes
// 禁止的危险文件后缀
const forbidSuffix = ['exe', 'msi', 'bat', 'cmd', 'sh', 'apk', 'dmg'];
const uploadRef = ref();
// 添加一个锁,防止 on-change 递归调用导致重复提示
const isChecking = ref(false);
const handleProgress: UploadProps['onChange'] = (uploadFile: UploadUserFile, uploadFiles: UploadFiles) => {
// 如果正在校验中,直接返回,避免重复执行
if (!beforeAvatarUpload(uploadFile)) {
uploadRef.value.handleRemove(uploadFile);
return;
}
if (isChecking.value) return;
// 加锁
isChecking.value = true;
// 创建一个新数组来存储校验通过的文件
@@ -229,12 +233,41 @@ const handleProgress: UploadProps['onChange'] = (uploadFile: UploadUserFile, upl
};
const blockedExts = ['.exe', '.bat', '.cmd', '.msi'];
const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
const allowExts = [
// word
'.doc',
'.docx',
'.dot',
'.dotx',
// excel
'.xls',
'.xlsx',
'.xlsm',
'.xlsb',
// img
'.jpg',
'.jpeg',
'.png',
'.gif',
'.bmp',
'.webp',
// 专业/矢量图
'.psd',
'.ai',
'.svg',
'.tif',
'.tiff'
];
const beforeAvatarUpload = (rawFile: UploadUserFile) => {
const ext = rawFile.name.toLowerCase().slice(rawFile.name.lastIndexOf('.'));
if (blockedExts.includes(ext)) {
ElMessage.warning(`禁止上传 ${ext} 文件`);
return false;
}
if (!allowExts.includes(ext)) {
ElMessage.warning('只能上传word,excel,图片等文件');
return false;
}
return true;
};

View File

@@ -48,6 +48,7 @@
:height="500"
:min-height="300"
:read-only="false"
type="url"
:file-size="5"
/>
</el-form-item>
@@ -71,7 +72,6 @@ import PageHeader from '@/components/Pageheader/index.vue';
import { addNotice, getNotice } from '@/api/system/NoticePc';
import { updateNotice } from '@/api/system/NoticePc';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { com_noticepc_level } = toRefs<any>(proxy?.useDict('com_noticepc_level'));
const { com_publish_status } = toRefs<any>(proxy?.useDict('com_publish_status'));
const route = useRoute();
@@ -94,6 +94,7 @@ const rules = {
const userid = ref();
// ! 富文本 =============================================================================================================
function handleContent(val: string) {
console.log(val);
form.value.noticeContent = val.trim();
}
// ! 富文本 =============================================================================================================
@@ -134,9 +135,7 @@ const submitForm = () => {
// 返回
function handleBack() {
router.push({
path: '/repair/noticepc'
});
router.back();
}
</script>

View File

@@ -47,23 +47,23 @@
<dict-tag :options="com_publish_status" :value="scope.row.publishStatus"></dict-tag>
</template>
</el-table-column>
<el-table-column label="已读" width="120" align="center" prop="readNum">
<el-table-column label="已读" align="center" prop="readNum">
<template #default="scope">
<span>{{ scope.row.readNum ?? '--' }} </span>
</template>
</el-table-column>
<el-table-column label="未读" width="120" align="center" prop="unReadNum">
<el-table-column label="未读" align="center" prop="unReadNum">
<template #default="scope">
<span>{{ scope.row.unReadNum ?? '--' }} </span>
</template>
</el-table-column>
<el-table-column label="发布人" align="center" prop="publishBy" />
<el-table-column label="发布日期" align="center" prop="publishTime" width="100">
<template #default="scope">
<span>{{ parseTime(scope.row.publishTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="220" fixed="right" class-name="small-padding fixed-width">
<!-- <el-table-column label="发布人" align="center" prop="publishBy" />-->
<!-- <el-table-column label="发布日期" align="center" prop="publishTime" width="100">-->
<!-- <template #default="scope">-->
<!-- <span>{{ parseTime(scope.row.publishTime, '{y}-{m}-{d}') }}</span>-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" @click="handleMain(scope.row)">详情</el-button>
<el-button link type="primary" @click="handleUpdate(scope.row)" v-has-permi="['notice:notice:edit']">编辑</el-button>
@@ -94,7 +94,6 @@ import { NoticeVO, NoticeQuery, NoticeForm } from '@/api/system/NoticePc/type';
import { checkPermi } from '@/utils/permission';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { com_noticepc_level } = toRefs<any>(proxy?.useDict('com_noticepc_level'));
const { com_publish_status } = toRefs<any>(proxy?.useDict('com_publish_status'));
const noticeList = ref<NoticeVO[]>([]);

View File

@@ -73,7 +73,7 @@ const { com_questionnaire_scope } = toRefs<any>(proxy?.useDict('com_questionnair
const router = useRouter();
const route = useRoute();
import { listResident } from '@/api/system/resident';
import { listResident } from '@/api/system/residentReviewProcess/resident';
const form = ref({
scope: '0',

View File

@@ -218,7 +218,7 @@
</el-form-item>
</div>
<el-form-item label="备注" prop="remark">
<el-input type="textarea" show-word-limit maxlength="100" :rows="10" v-model="inComeFrom.remark"></el-input>
<el-input type="textarea" show-word-limit maxlength="100" :rows="5" v-model="inComeFrom.remark"></el-input>
</el-form-item>
<div class="flex justify-end">
<el-button @click="closer">取消</el-button>
@@ -234,7 +234,7 @@
</div>
</template>
<div class="printBox" id="printBox-Bill">
<div class="printBox" id="printBox-Bill" style="padding: 0; margin: 0">
<div class="title">{{ print_form.title }}</div>
<div class="nav gridBox">
<div>单据日期{{ print_form.printDate }}</div>
@@ -254,7 +254,7 @@
<th style="min-width: 80px">优免金额</th>
<th style="min-width: 80px">实收金额</th>
<th style="min-width: 80px">收款方式</th>
<th style="width: 200px">备注</th>
<th style="min-width: 100px">备注</th>
</tr>
</thead>
<tbody>
@@ -283,7 +283,7 @@
</tbody>
</table>
<p style="font-size: 10px">说明本收据收款单位和收款具签章方为有效本收据手写无效</p>
<div class="flex flex-items-center" style="font-size: 12px">
<div class="flex flex-items-center" style="font-size: 12px; margin-bottom: 0">
<div class="flex-1">交款人</div>
<div class="flex-1 flex flex-items-center">
<span style="white-space: nowrap">收款人 </span>
@@ -488,6 +488,7 @@ function initInComeFrom() {
discount: 0, // 优惠金额
residentName: '' // 缴费人
};
totalDiscountAmount.value = 0;
isMore = false;
}
// 改变优惠方式
@@ -598,6 +599,7 @@ function handleIncome(row: billlistType | false) {
inComeFrom.value.allCost = row.amount;
inComeFrom.value.billResidentId = [row];
inComeFrom.value.residentName = row.residentName;
totalDiscountAmount.value = Number(row.amount);
} else {
if (ids.value.length <= 0) return ElMessage.warning('请选择账单');
const set = new Set();

View File

@@ -267,7 +267,6 @@ function handleChange(pop) {
holderInfo.value = pop;
form.value.userId = '';
form.value.userName = '';
console.log(pop);
if (pop !== null) {
form.value.userId = pop.user_id;
form.value.userName = pop.user_name;

View File

@@ -66,7 +66,7 @@
import Pageheader from '@/components/Pageheader/index.vue';
import { listCar, delCar } from '@/api/system/cars/index';
import { CarVO, CarQuery, CarForm } from '@/api/system/cars/type';
import { listResident } from '@/api/system/resident';
import { listResident } from '@/api/system/residentReviewProcess/resident';
import { checkPermi } from '@/utils/permission';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;

View File

@@ -299,7 +299,7 @@ const handleChangeFile = (uploadFile: UploadFile, uploadFiles: UploadFiles) => {
const ext = uploadFile.name.toLowerCase().slice(uploadFile.name.lastIndexOf('.'));
if (blockedExts.includes(ext)) {
ElMessage.warning(`禁止上传 ${ext} 文件`);
fileList.value = uploadFiles.filter((f) => f.uid !== uploadFile.uid);
uploadRef.value.handleRemove(uploadFile);
return;
}
fileList.value = uploadFiles;

View File

@@ -34,12 +34,14 @@
</el-col>
<el-col :span="14"></el-col>
<el-col :span="5" style="text-align: right">
<el-button type="warning" @click="exportCurrentPage">导出当前页</el-button>
<el-button type="warning" @click="exportAll">导出所有</el-button>
<el-button type="primary" @click="exportChecked">导出选中</el-button>
<el-button type="primary" @click="exportCurrentPage">导出当前页</el-button>
<el-button type="primary" @click="exportAll">导出所有</el-button>
</el-col>
</el-row>
</template>
<el-table v-loading="loading" border :data="itemList">
<el-table v-loading="loading" border row-key="id" :data="itemList" @selectionChange="handleChange">
<el-table-column type="selection" width="55" />
<el-table-column label="日期" width="100" align="center" prop="statDate" />
<el-table-column label="计划巡检任务" width="120" align="center" prop="totalPlans" />
<el-table-column label="按时完成" align="center" prop="completedTasks" />
@@ -126,6 +128,26 @@ const exportAll = () => {
`巡检日报_${new Date().toLocaleString()}.xlsx`
);
};
const multipleSelection = ref([]);
function handleChange(val) {
multipleSelection.value = val;
}
const exportChecked = () => {
if (multipleSelection.value.length > 0) {
const listValue = multipleSelection.value.map((item) => item.id);
console.log(listValue);
proxy?.download(
'/inspection/data/export/selected',
{
list: listValue.join(','),
dataType: '1'
},
`巡检日报_选中${listValue.length}条数据_${new Date().toLocaleString()}.xlsx`,
);
} else {
ElMessage.warning('请选择数据');
}
};
onMounted(() => {
getList();

View File

@@ -34,13 +34,15 @@
</el-col>
<el-col :span="14"> </el-col>
<el-col :span="5" style="text-align: right">
<el-button type="warning" @click="exportCurrentPage">导出当前页</el-button>
<el-button type="warning" @click="exportAll">导出所有</el-button>
<el-button type="primary" @click="exportCurrentPage">导出当前页</el-button>
<el-button type="primary" @click="exportAll">导出所有</el-button>
<el-button type="primary" @click="exportChecked">导出选中</el-button>
</el-col>
</el-row>
</template>
<el-table v-loading="loading" border :data="itemList">
<el-table v-loading="loading" border :data="itemList" @selectionChange="handleChange">
<el-table-column type="selection" width="55" />
<el-table-column label="日期" width="100" align="center" prop="statDate" />
<el-table-column label="计划巡检任务" width="120" align="center" prop="totalPlans" />
<el-table-column label="按时完成" align="center" prop="completedTasks" />
@@ -125,6 +127,26 @@ const exportAll = () => {
`巡检月报_${new Date().toLocaleString()}.xlsx`
);
};
const multipleSelection = ref([]);
function handleChange(val) {
multipleSelection.value = val;
}
const exportChecked = () => {
if (multipleSelection.value.length > 0) {
const listValue = multipleSelection.value.map((item) => item.id);
console.log(listValue);
proxy?.download(
'/inspection/data/export/selected',
{
list: listValue.join(','),
dataType: '3'
},
`巡检月报_选中${listValue.length}条数据_${new Date().toLocaleString()}.xlsx`
);
} else {
ElMessage.warning('请选择数据');
}
};
onMounted(() => {
getList();
});

View File

@@ -34,13 +34,15 @@
</el-col>
<el-col :span="14"> </el-col>
<el-col :span="5" style="text-align: right">
<el-button type="warning" @click="exportCurrentPage">导出当前页</el-button>
<el-button type="warning" @click="exportAll">导出所有</el-button>
<el-button type="primary" @click="exportCurrentPage">导出当前页</el-button>
<el-button type="primary" @click="exportAll">导出所有</el-button>
<el-button type="primary" @click="exportChecked">导出选中</el-button>
</el-col>
</el-row>
</template>
<el-table v-loading="loading" border :data="itemList">
<el-table v-loading="loading" border :data="itemList" @selectionChange="handleChange">
<el-table-column type="selection" width="55" />
<el-table-column label="日期" width="120" align="center" prop="statDate" />
<el-table-column label="计划巡检任务" width="120" align="center" prop="totalPlans" />
<el-table-column label="按时完成" align="center" prop="completedTasks" />
@@ -129,6 +131,27 @@ const exportAll = () => {
onMounted(() => {
getList();
});
const multipleSelection = ref([]);
function handleChange(val) {
multipleSelection.value = val;
}
const exportChecked = () => {
if (multipleSelection.value.length > 0) {
const listValue = multipleSelection.value.map((item) => item.id);
console.log(listValue);
proxy?.download(
'/inspection/data/export/selected',
{
list: listValue.join(','),
dataType: '2'
},
`巡检周报_选中${listValue.length}条数据_${new Date().toLocaleString()}.xlsx`
);
} else {
ElMessage.warning('请选择数据');
}
};
</script>
<style lang="scss" scoped>

View File

@@ -139,8 +139,8 @@
<script setup lang="ts">
import { ref } from 'vue';
import PageHeader from '@/components/Pageheader/index.vue';
import { getResident, getresidentReviewlistAPI, UpdateResidentReviewAPI } from '@/api/system/resident';
import { ResidentVO } from '@/api/system/resident/type';
import { getResident, getresidentReviewlistAPI, UpdateResidentReviewAPI } from '@/api/system/residentReviewProcess/resident';
import { ResidentVO } from '@/api/system/residentReviewProcess/resident/type';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { com_review } = toRefs<any>(proxy?.useDict('com_review'));

View File

@@ -107,10 +107,10 @@
import { ref } from 'vue';
import PageHeader from '@/components/Pageheader/index.vue';
import { getHousePathById } from '@/utils/house';
import { ResidentForm } from '@/api/system/resident/type';
import { ResidentForm } from '@/api/system/residentReviewProcess/resident/type';
import { CascaderValue, UploadFiles, UploadInstance, UploadProps, UploadRawFile } from 'element-plus';
import { genFileId } from 'element-plus';
import { addResident, getResident, updateResident } from '@/api/system/resident';
import { addResident, getResident, updateResident } from '@/api/system/residentReviewProcess/resident';
import { validator } from '@/utils/Reg';
import { useHouseStore } from '@/store/modules/house';
import { Plus } from '@element-plus/icons-vue';

View File

@@ -96,8 +96,8 @@ import pageHeader from '@/components/Pageheader/index.vue';
import RightToolbar from '@/components/RightToolbar/index.vue';
import DictTag from '@/components/DictTag/index.vue';
import { listResident, delResident } from '@/api/system/resident';
import { ResidentVO, ResidentQuery, ResidentForm } from '@/api/system/resident/type';
import { listResident, delResident } from '@/api/system/residentReviewProcess/resident';
import { ResidentVO, ResidentQuery, ResidentForm } from '@/api/system/residentReviewProcess/resident/type';
import { useHouseStore } from '@/store/modules/house';
import { checkPermi } from '@/utils/permission';

View File

@@ -0,0 +1,259 @@
<template>
<div class="ResidentMainBox p-2 w-full">
<PageHeader></PageHeader>
<div class="ResidentMainBody">
<el-card>
<template #header>
<div class="title">基本信息</div>
</template>
<div class="addBuildingFormBody">
<div class="formbox">
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
<el-row>
<el-col :span="12">
<el-form-item label="公告标题" prop="noticeTitle">
<el-input v-model.trim="form.noticeTitle" placeholder="请输入公告标题" />
</el-form-item>
</el-col>
<!-- <el-col :span="12">-->
<!-- <el-form-item label="作者" prop="author">-->
<!-- <el-input v-model.trim="form.author" placeholder="请输入作者" />-->
<!-- </el-form-item>-->
<!-- </el-col>-->
</el-row>
<el-row>
<!-- <el-col :span="12">-->
<!-- <el-form-item label="公告紧急程度" prop="urgencyLevel">-->
<!-- <el-select v-model="form.urgencyLevel" clearable placeholder="请选择公告紧急程度">-->
<!-- <el-option v-for="dict in com_noticepc_level" :key="dict.value" :label="dict.label" :value="Number(dict.value)" />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<!-- </el-col>-->
<!-- <el-col :span="12">-->
<!-- <el-form-item label="是否发布" prop="publishStatus">-->
<!-- <el-radio-group v-model="form.publishStatus">-->
<!-- <el-radio v-for="dict in com_publish_status" :key="dict.value" :value="Number(dict.value)">{{-->
<!-- dict.value === '0' ? dict.label : '发布'-->
<!-- }}</el-radio>-->
<!-- </el-radio-group>-->
<!-- </el-form-item>-->
<!-- </el-col>-->
</el-row>
<el-row>
<el-col>
<el-form-item label="公告详情" prop="noticeContent">
<editor
@update:model-value="handleContent"
:model-value="form.noticeContent"
:height="500"
:min-height="300"
:read-only="false"
type="url"
:file-size="5"
/>
</el-form-item>
</el-col>
</el-row>
<!-- <el-row>-->
<!-- <el-col>-->
<!-- <el-form-item label="附加上传">-->
<!-- <el-upload-->
<!-- v-model:file-list="fileList"-->
<!-- class="upload-demo"-->
<!-- name="files"-->
<!-- :action="uploadUrl"-->
<!-- :headers="headerToken()"-->
<!-- :on-success="handleAvatarSuccess"-->
<!-- :on-remove="handleRemove"-->
<!-- :before-upload="beforeAvatarUpload"-->
<!-- >-->
<!-- <el-button type="primary">上传附件</el-button>-->
<!-- </el-upload>-->
<!-- </el-form-item>-->
<!-- </el-col>-->
<!-- </el-row>-->
<el-form-item>
<el-button type="primary" @click="submitForm">立即发布</el-button>
<el-button @click="handleBack">返回</el-button>
</el-form-item>
</el-form>
</div>
</div>
</el-card>
</div>
</div>
</template>
<script setup lang="ts">
import editor from '@/components/Editor/index.vue';
import { ref } from 'vue';
import PageHeader from '@/components/Pageheader/index.vue';
import { addRevenueNotice, getRevenueNotice, updateRevenueNotice } from '@/api/system/revenueNotice';
import { ElMessage } from 'element-plus';
// import { useUserStore } from '@/store/modules/user';
// const uploadUrl = import.meta.env.VITE_APP_BASE_API + '/revenueNotice/uploadAttachments';
// const { proxy } = getCurrentInstance() as ComponentInternalInstance;
// const { com_publish_status } = toRefs<any>(proxy?.useDict('com_publish_status'));
// const useStore = useUserStore();
// function headerToken() {
// return {
// Authorization: 'Bearer ' + useStore.token
// };
// }
const route = useRoute();
const router = useRouter();
const formRef = ref();
const form = ref({
'id': null,
'author': '',
'noticeTitle': '',
'urgencyLevel': 0,
'publishStatus': 1,
'noticeContent': '',
attachments: []
});
const rules = {
noticeTitle: [{ required: true, message: '请输入公告标题', trigger: 'blur' }],
urgencyLevel: [{ required: true, message: '请选择紧急程度', trigger: 'blur' }],
author: [{ required: true, message: '请输入作者名称', trigger: 'blur' }],
noticeContent: [{ required: true, message: '请输入公告内容', trigger: 'blur' }]
};
const userid = ref();
function handleContent(val: string) {
form.value.noticeContent = val.trim();
}
// ! 投诉 ====================================================================================================
function init() {
getRevenueNotice(userid.value).then((res) => {
form.value = {
...form.value,
...res.data
};
// fileList.value = form.value.attachments.map((item) => {
// return {
// name: item.fileName,
// url: item.fileUrl
// };
// });
});
}
if (route.query.id) {
userid.value = route.query.id;
init();
}
// interface myUploadUserFile extends UploadUserFile {
// response: {
// code: number;
// msg: string;
// data: {
// success: {
// fileName: string;
// url: string;
// ossId: string;
// }[];
// error: string[];
// };
// };
// }
// const fileList = ref<myUploadUserFile[]>([]);
//
// const blockedExts = ['.exe', '.bat', '.cmd', '.msi'];
// const beforeAvatarUpload = (rawFile: UploadUserFile) => {
// const ext = rawFile.name.toLowerCase().slice(rawFile.name.lastIndexOf('.'));
// if (blockedExts.includes(ext)) {
// ElMessage.warning(`禁止上传 ${ext} 文件`);
// return false;
// }
// return true;
// };
// const handleAvatarSuccess: UploadProps['onSuccess'] = (response, uploadFile, uploadFiles) => {};
// const handleRemove: UploadProps['onRemove'] = (_, uploadFiles) => {};
// 提交
const submitForm = () => {
form.value.attachments = [];
// if (fileList.value.length > 0) {
// fileList.value.forEach((item, index) => {
// if (item.response) {
// const file = item.response.data.success[0];
// const ext = file.fileName.toLowerCase().slice(file.fileName.lastIndexOf('.'));
// form.value.attachments.push({
// fileName: file.fileName,
// fileUrl: file.url,
// fileType: ext.split('.')[1],
// sort: index
// });
// } else {
// const ext = item.fileName.toLowerCase().slice(item.fileName.lastIndexOf('.'));
// form.value.attachments.push({
// fileName: item.fileName,
// fileUrl: item.url,
// fileType: ext.split('.')[1],
// sort: index
// });
// }
// });
// }
formRef.value?.validate(async (valid: boolean) => {
if (valid) {
if (userid.value) {
updateRevenueNotice(form.value).then(() => {
ElMessage.success('编辑成功');
handleBack();
});
} else {
addRevenueNotice(form.value).then(() => {
ElMessage.success('添加成功');
handleBack();
});
}
}
});
};
// 返回
function handleBack() {
router.back();
}
</script>
<style scoped lang="scss">
.ResidentMainBox {
display: flex;
flex-direction: column;
.ResidentMainBody {
margin-top: 20px;
flex: 1;
.formbox {
width: 1000px;
margin: 0 auto;
.upload-demo {
width: 100%;
}
}
&:deep(div.el-step__head.is-process) {
& > div.el-step__line {
width: 0;
border: 1px dashed gray;
background: transparent;
}
}
&:deep(.el-step.is-vertical .el-step__line) {
top: 20px;
}
&:deep(div.el-step__head.is-finish) {
& > div.el-step__line {
width: 0;
border: 1px solid var(--el-color-primary);
background: transparent;
}
}
}
}
</style>

View File

@@ -0,0 +1,276 @@
<template>
<div class="flex flex-col h-full p-2">
<pageHeader></pageHeader>
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="公告标题" prop="noticeTitle">
<el-input v-model="queryParams.noticeTitle" placeholder="请输入公告标题" clearable @keyup.enter="handleQuery" />
</el-form-item>
<!-- <el-form-item label="作者" prop="author">-->
<!-- <el-input v-model="queryParams.author" placeholder="请输入作者" clearable @keyup.enter="handleQuery" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="发布人" prop="publishBy">-->
<!-- <el-input v-model="queryParams.publishBy" placeholder="请输入发布人" clearable @keyup.enter="handleQuery" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="发布日期" prop="publishTime">-->
<!-- <el-date-picker clearable v-model="queryParams.publishTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择发布日期" />-->
<!-- </el-form-item>-->
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never" class="flex-1">
<template #header>
<el-row :gutter="10" class="mb8">
<!-- <el-col :span="1.5">-->
<!-- <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:revenueNotice:export']">导出</el-button>-->
<!-- </el-col>-->
<right-toolbar
:show-edit="checkPermi(['revenue:revenueNotice:edit'])"
:show-delete="checkPermi(['revenue:revenueNotice:remove'])"
:showAdd="checkPermi(['revenue:revenueNotice:add'])"
@update:add="handleAdd"
@update:edit="handleUpdate()"
@update:delete="handleDelete()"
></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="revenueNoticeList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="公告标题" align="center" prop="noticeTitle" />
<!-- <el-table-column label="公告内容" align="center" prop="noticeContent" />-->
<!-- <el-table-column label="作者" align="center" prop="author" />-->
<!-- <el-table-column label="发布状态" align="center" prop="publishStatus">-->
<!-- <template #default="scope">-->
<!-- <dict-tag :options="com_publish_status" :value="scope.row.publishStatus"></dict-tag>-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- <el-table-column label="发布人" align="center" prop="publishBy" />-->
<!-- <el-table-column label="发布日期" align="center" prop="publishTime" width="180">-->
<!-- <template #default="scope">-->
<!-- <span>{{ parseTime(scope.row.publishTime, '{y}-{m}-{d}') }}</span>-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['revenue:revenueNotice:edit']">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['revenue:revenueNotice:remove']"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改公区收益通知对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="revenueNoticeFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="公告标题" prop="noticeTitle">
<el-input v-model="form.noticeTitle" placeholder="请输入公告标题" />
</el-form-item>
<el-form-item label="公告内容">
<editor v-model="form.noticeContent" :min-height="192" />
</el-form-item>
<el-form-item label="作者" prop="author">
<el-input v-model="form.author" placeholder="请输入作者" />
</el-form-item>
<el-form-item label="紧急程度 0=普通 1=紧急 2=非常紧急" prop="urgencyLevel">
<el-input v-model="form.urgencyLevel" placeholder="请输入紧急程度" />
</el-form-item>
<el-form-item label="发布人" prop="publishBy">
<el-input v-model="form.publishBy" placeholder="请输入发布人" />
</el-form-item>
<el-form-item label="发布日期" prop="publishTime">
<el-date-picker clearable v-model="form.publishTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择发布日期">
</el-date-picker>
</el-form-item>
<el-form-item label="所属小区id" prop="villageId">
<el-input v-model="form.villageId" placeholder="请输入所属小区id" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="RevenueNotice" lang="ts">
import pageHeader from '@/components/Pageheader/index.vue';
import { listRevenueNotice, delRevenueNotice, addRevenueNotice, updateRevenueNotice } from '@/api/system/revenueNotice';
import { RevenueNoticeVO, RevenueNoticeQuery, RevenueNoticeForm } from '@/api/system/revenueNotice/type';
import { checkPermi } from '@/utils/permission';
import { NoticeVO } from '@/api/system/NoticePc/type';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
// const { com_publish_status } = toRefs<any>(proxy?.useDict('com_publish_status'));
const revenueNoticeList = ref<RevenueNoticeVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const revenueNoticeFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: RevenueNoticeForm = {
noticeId: undefined,
noticeTitle: undefined,
noticeContent: undefined,
author: undefined,
urgencyLevel: undefined,
publishStatus: undefined,
publishBy: undefined,
publishTime: undefined,
villageId: undefined
};
const data = reactive<PageData<RevenueNoticeForm, RevenueNoticeQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
noticeTitle: undefined,
noticeContent: undefined,
author: undefined,
urgencyLevel: undefined,
publishStatus: undefined,
publishBy: undefined,
publishTime: undefined,
villageId: undefined,
params: {}
},
rules: {
noticeTitle: [{ required: true, message: '公告标题不能为空', trigger: 'blur' }],
urgencyLevel: [{ required: true, message: '紧急程度', trigger: 'blur' }],
publishStatus: [{ required: true, message: '发布状态', trigger: 'change' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询公区收益通知列表 */
const getList = async () => {
loading.value = true;
const res = await listRevenueNotice(queryParams.value);
revenueNoticeList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
revenueNoticeFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: RevenueNoticeVO[]) => {
ids.value = selection.map((item) => item.noticeId);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
const router = useRouter();
/** 新增按钮操作 */
const handleAdd = () => {
router.push({
path: '/repair/addRevenueNotice'
});
};
/** 修改按钮操作 */
const handleUpdate = (row?: NoticeVO) => {
const _id = row?.noticeId || ids.value[0];
router.push({
path: '/repair/editRevenueNotice',
query: {
id: _id
}
});
};
/** 提交按钮 */
const submitForm = () => {
revenueNoticeFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.noticeId) {
await updateRevenueNotice(form.value).finally(() => (buttonLoading.value = false));
} else {
await addRevenueNotice(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: RevenueNoticeVO) => {
const _noticeIds = row?.noticeId || ids.value;
await proxy?.$modal.confirm('是否确认删除该公区收益通知?').finally(() => (loading.value = false));
await delRevenueNotice(_noticeIds);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
/** 导出按钮操作 */
// const handleExport = () => {
// proxy?.download(
// 'system/revenueNotice/export',
// {
// ...queryParams.value
// },
// `revenueNotice_${new Date().getTime()}.xlsx`
// );
// };
onMounted(() => {
getList();
});
</script>
<style scoped lang="scss">
.el-table {
height: calc(100% - 80px);
}
</style>

View File

@@ -19,9 +19,9 @@
<div class="cardbox">
<div>收入来源</div>
<div class="card_InfoAll">
<div>应缴费用{{ formatMoneyWithUnit(sourceFrom.totalPayable) }}</div>
<div>已收{{ formatMoneyWithUnit(sourceFrom.totalReceived) }}</div>
<div>待收{{ formatMoneyWithUnit(sourceFrom.totalPending) }}</div>
<div>应缴费用{{ formatMoneyWithUnit(sourceFrom.totalPayable, 3) }}</div>
<div>已收{{ formatMoneyWithUnit(sourceFrom.totalReceived, 3) }}</div>
<div>待收{{ formatMoneyWithUnit(sourceFrom.totalPending, 3) }}</div>
</div>
<div class="cardBody" style="display: flex; flex-wrap: wrap" v-if="sourceFromIsComputed">
<RingProgress name="停车费" color="#1a75ff" :value="sourceFrom.parkingFeePercent"></RingProgress>

View File

@@ -72,7 +72,6 @@ import { listStock } from '@/api/system/Tstock';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const router = useRouter();
const materialList = ref<MaterialVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
@@ -135,7 +134,7 @@ const handleAdd = () => {
});
};
/** 修改按钮操作 */
const handleUpdate = async (row?: MaterialVO) => {
const handleUpdate = (row?: MaterialVO) => {
const _ids = row?.materialId;
router.push({
path: '/warehouse/editInventory',

View File

@@ -35,8 +35,6 @@ import { ref } from 'vue';
import PageHeader from '@/components/Pageheader/index.vue';
import { getMaterial, updateMaterial, addMaterial } from '@/api/system/Tmaterial';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const router = useRouter();
const route = useRoute();

View File

@@ -8,13 +8,13 @@
<el-form-item label="供应商名称" prop="supplierName">
<el-input v-model.trim="form.supplierName" placeholder="请输入供应商名称" />
</el-form-item>
<el-form-item label="联系人" prop="contactPerson">
<el-form-item label="联系人">
<el-input v-model.trim="form.contactPerson" placeholder="请输入联系人" />
</el-form-item>
<el-form-item label="联系电话" prop="contactPhone">
<el-form-item label="联系电话">
<el-input v-model.trim="form.contactPhone" placeholder="请输入联系电话" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-form-item label="邮箱">
<el-input v-model.trim="form.email" placeholder="请输入邮箱" />
</el-form-item>
<el-form-item label="地址" prop="address">
@@ -39,8 +39,6 @@ import PageHeader from '@/components/Pageheader/index.vue';
import { getSupplier, updateSupplier, addSupplier } from '@/api/system/Tsupplier';
import { validator } from '@/utils/Reg';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const router = useRouter();
const route = useRoute();

View File

@@ -10,13 +10,13 @@
</el-form-item>
<el-form-item label="归属小区" prop="villageId">
<el-select v-model="form.villageId" placeholder="请选择归属小区">
<el-option v-for="(item, index) in cummunitylist" :key="index" :label="item.villageName" :value="item.villageId" />
<el-option v-for="(item, index) in communityList" :key="index" :label="item.villageName" :value="item.villageId" />
</el-select>
</el-form-item>
<el-form-item label="联系人" prop="contactPerson">
<el-form-item label="联系人">
<el-input v-model.trim="form.contactPerson" placeholder="请输入联系人" />
</el-form-item>
<el-form-item label="联系电话" prop="contactPhone">
<el-form-item label="联系电话">
<el-input v-model.trim="form.contactPhone" placeholder="请输入联系电话" />
</el-form-item>
<el-form-item label="地址" prop="address">
@@ -42,8 +42,6 @@ import { getCommunitylistAPI } from '@/api/system/community';
import { addWarehouse, getWarehouse, updateWarehouse } from '@/api/system/Twarehouse';
import { validator } from '@/utils/Reg';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const router = useRouter();
const route = useRoute();
@@ -63,7 +61,7 @@ const rules = ref({
contactPhone: [
{ required: true, message: '请输入 联系电话', trigger: 'blur' },
{
validator: (_, value) => validator(value, 'phone'),
validator: (_: any, value: string | number) => validator(value, 'phone'),
message: '请输入正确的电话号码',
trigger: 'blur'
}
@@ -74,10 +72,10 @@ const rules = ref({
const userid = ref(null);
// 小区列表
const cummunitylist = ref([]);
const communityList = ref([]);
function init() {
getCommunitylistAPI().then((res) => {
cummunitylist.value = res.rows;
communityList.value = res.rows;
});
if (route.query.id) {
userid.value = route.query.id;
@@ -103,12 +101,12 @@ const submitForm = () => {
formRef.value?.validate(async (valid: boolean) => {
if (valid) {
if (userid.value) {
updateWarehouse(form.value).then((res) => {
updateWarehouse(form.value).then(() => {
ElMessage.success('编辑成功');
cancelForm();
});
} else {
addWarehouse(form.value).then((res) => {
addWarehouse(form.value).then(() => {
ElMessage.success('添加成功');
cancelForm();
});

View File

@@ -0,0 +1,210 @@
<template>
<div class="echarts">
<div class="nav flex flex-items-center justify-between">
<div>
<span class="ecarts_title">收费情况</span>
</div>
<div class="flex flex-items-center justify-center">
<el-date-picker
v-model="time"
type="daterange"
range-separator=""
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
start-placeholder="开始日期"
@update:model-value="handleChangeDate"
end-placeholder="结束日期"
/>
</div>
</div>
<div class="echartsBody flex flex-items-center justify-between">
<div class="flex-1 h-full echartsItems">
<Echarts :options="options"></Echarts>
</div>
<div class="flex-1 h-full">
<div class="echartsMoneyItem">
<div class="label">应收</div>
<div class="value"> {{ feeForm.receivableAmount }}</div>
</div>
<div class="echartsMoneyItem">
<div class="label">已收</div>
<div class="value"> {{ feeForm.receivedAmount }}</div>
</div>
<div class="echartsMoneyItem">
<div class="label">待收</div>
<div class="value"> {{ feeForm.unpaidAmount }}</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import Echarts from '@/components/Echarts/index.vue';
import { EChartsOption } from 'echarts';
import { paymentRateApi } from '@/api/workflow';
import { getLastNDays } from '@/utils/time';
const time = ref([getLastNDays(30).at(0), getLastNDays(0).at(length - 1)]);
const value = ref(0);
const feeForm = ref({
'receivableAmount': '',
'receivablePercent': '',
'receivedAmount': '',
'receivedPercent': '',
'unpaidAmount': '',
'unpaidPercent': ''
});
const options = ref<EChartsOption>({
tooltip: {
show: false,
trigger: 'item',
formatter: '{b}: {c}%'
},
series: [
{
type: 'pie',
radius: ['50%', '80%'], // 环形大小
center: ['50%', '50%'], // 每个图表各自居中
labelLine: {
show: false
},
data: [
{
value: 100 - value.value,
itemStyle: {
color: '#ebeef5' // 背景色
},
emphasis: {
itemStyle: {
color: '#ebeef5'
},
scale: false
}
},
{
value: value.value,
name: '收费率',
itemStyle: { color: '#1684fc', borderRadius: 8, borderColor: '#1684fc', borderWidth: 0 },
label: {
show: true,
position: 'center', // 显示在圆环中心
formatter: '{c}%', // 显示百分比数值
fontSize: 25
},
emphasis: {
scale: true
}
}
]
}
]
});
function handleChangeDate(date: string[]) {
if (date) {
paymentRateApi(date[0], date[1]).then((res) => {
feeForm.value = res.data;
options.value = updateOptions(res.data.receivedPercent) as EChartsOption;
});
}
}
handleChangeDate(time.value);
function updateOptions(value: string) {
return {
tooltip: {
show: false,
trigger: 'item',
formatter: '{b}: {c}%'
},
title: {
show: true,
text: '收费率',
left: 'left'
},
series: [
{
type: 'pie',
radius: ['50%', '80%'], // 环形大小
center: ['50%', '50%'], // 每个图表各自居中
labelLine: {
show: false
},
startAngle: 90,
clockwise: false,
data: [
{
value: 100 - Number(value),
itemStyle: {
color: '#ebeef5' // 背景色
},
emphasis: {
itemStyle: {
color: '#ebeef5'
},
scale: false
}
},
{
value: Number(value),
name: '收费率',
itemStyle: { color: '#1684fc', borderRadius: 8, borderColor: '#1684fc', borderWidth: 0 },
label: {
show: true,
position: 'center',
formatter: '{c}%',
fontSize: 25
},
emphasis: {
scale: true
}
}
]
}
]
};
}
</script>
<style lang="scss" scoped>
.echarts {
height: 512px;
flex: 1;
display: flex;
flex-direction: column;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.2) 0%, #ffffff 70%);
box-sizing: border-box;
padding: 19px 23px;
box-shadow: 0px 2px 6px 0px rgba(26, 117, 255, 0.15);
border-radius: 10px;
.echartsBody {
margin-top: 40px;
.echartsItems {
border-right: 1px solid #dedede;
}
.echartsMoneyItem {
box-sizing: border-box;
border-bottom: 1px solid #dedede;
padding: 17px 34px;
height: 126px;
&:last-child {
border-bottom: none;
}
.label {
color: rgba(16, 16, 16, 1);
font-size: 16px;
height: 23px;
line-height: 23px;
margin-bottom: 10px;
}
.value {
color: rgba(16, 16, 16, 1);
font-size: 1.5rem;
height: 50px;
line-height: 50px;
font-weight: 600;
}
}
}
}
</style>

View File

@@ -31,7 +31,7 @@ const handleRoute = () => {
.echarts {
margin-right: 20px;
flex: 1;
height: 450px;
height: 512px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.2) 0%, #ffffff 70%);
border: 2px solid #ffffff;
border-radius: 12px;

View File

@@ -71,6 +71,31 @@ function headerToken() {
};
}
const blockedExts = ['.exe', '.bat', '.cmd', '.msi'];
const allowExts = [
// word
'.doc',
'.docx',
'.dot',
'.dotx',
// excel
'.xls',
'.xlsx',
'.xlsm',
'.xlsb',
// img
'.jpg',
'.jpeg',
'.png',
'.gif',
'.bmp',
'.webp',
// 专业/矢量图
'.psd',
'.ai',
'.svg',
'.tif',
'.tiff'
];
const handleAvatarRemove: UploadProps['onRemove'] = (uploadFile: UploadFile, uploadFiles: UploadFiles) => {
fileList.value = uploadFiles;
};
@@ -78,7 +103,12 @@ const handleProgress: UploadProps['onChange'] = (uploadFile: UploadFile, uploadF
const ext = uploadFile.name.toLowerCase().slice(uploadFile.name.lastIndexOf('.'));
if (blockedExts.includes(ext)) {
ElMessage.warning(`禁止上传 ${ext} 文件`);
fileList.value = uploadFiles.filter((f) => f.uid !== uploadFile.uid);
uploadRef.value.handleRemove(uploadFile);
return;
}
if (!allowExts.includes(ext)) {
ElMessage.warning('只允许上传word,excel和图片文件');
uploadRef.value.handleRemove(uploadFile);
return;
}
fileList.value = uploadFiles;

View File

@@ -57,7 +57,7 @@
</template>
<script lang="ts" setup>
import { delResident, listResident } from '@/api/system/resident';
import { delResident, listResident } from '@/api/system/residentReviewProcess/resident';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { com_resident_type } = toRefs<any>(proxy?.useDict('com_resident_type'));

View File

@@ -7,8 +7,7 @@
<VillageAllInfo :list="list"></VillageAllInfo>
<div class="echartsbox flex flex-items-center">
<repairechartsAll></repairechartsAll>
<repairechartsAll></repairechartsAll>
<inandoutEcharts></inandoutEcharts>
<feeOverview></feeOverview>
</div>
</el-col>
<!-- tabs -->
@@ -80,6 +79,7 @@ import weatherVue from './components/weather.vue';
import userTable from './components/usertable.vue';
import quick from './components/quick.vue';
import todo from './components/todo.vue';
import feeOverview from './components/feeOverview.vue';
import inandoutEcharts from './components/inandout.vue';
import repairechartsAll from './components/repairechartsAll.vue';