进度填报大屏界面基本功能

This commit is contained in:
Teo
2025-05-30 19:51:35 +08:00
parent 0a2d8b06f8
commit 6758437892
22 changed files with 2028 additions and 128 deletions

View File

@ -1,6 +1,6 @@
import request from '@/utils/request'; import request from '@/utils/request';
import { AxiosPromise } from 'axios'; import { AxiosPromise } from 'axios';
import { ProgressCategoryVO, ProgressCategoryForm, ProgressCategoryQuery, ProgressPlanForm, lastTimeVo, workScheduleListVO, workScheduleListQuery, progressPlanDetailForm } from '@/api/progress/plan/types'; import { ProgressCategoryVO, ProgressCategoryForm, ProgressCategoryQuery, ProgressPlanForm, lastTimeVo, workScheduleListVO, workScheduleListQuery, progressPlanDetailForm, pvModuleListQuery, pvModuleListVO } from '@/api/progress/plan/types';
/** /**
* 查询进度类别列表 * 查询进度类别列表
@ -99,7 +99,7 @@ export const workScheduleAddPlan = (data: ProgressPlanForm) => {
/** /**
* 获取进度计划详细信息 * 获取进度计划详细信息
* @param data * @param params
*/ */
export const workScheduleList = (query: workScheduleListQuery):AxiosPromise<workScheduleListVO[]> => { export const workScheduleList = (query: workScheduleListQuery):AxiosPromise<workScheduleListVO[]> => {
return request({ return request({
@ -109,6 +109,17 @@ export const workScheduleList = (query: workScheduleListQuery):AxiosPromise<work
}); });
}; };
/**
* 获取进度类别坐标信息
* @param params
*/
export const workScheduleListPosition = (id: string):AxiosPromise<any> => {
return request({
url: '/progress/progressCategory/coordinate/' + id,
method: 'get',
});
};
/** /**
* 新增进度计划详情(百分比设施) * 新增进度计划详情(百分比设施)
* @param data * @param data
@ -120,9 +131,53 @@ export const workScheduleSubmit = (data: progressPlanDetailForm) => {
data: data data: data
}); });
}; };
export const workScheduleDel=()=>{}
export const pvModuleList=()=>{} /**
export const addDaily=()=>{} * 获取进度计划详情未完成设施详细信息
export const getDailyBook=()=>{} * @param params
export const deleteDaily=()=>{} */
export const pvModuleList = (query: pvModuleListQuery):AxiosPromise<pvModuleListVO[]> => {
return request({
url: '/progress/progressPlanDetail/detail/unFinish/' + query.id ,
method: 'get',
params:query
});
};
/**
* 新增进度计划详情(普通设施)
* @param data
*/
export const addDaily = (data: progressPlanDetailForm) => {
return request({
url: '/progress/progressPlanDetail/insert/detail',
method: 'post',
data: data
});
};
/**
* 获取进度计划详情已完成设施详细信息
* @param params
*/
export const getDailyBook = (query: pvModuleListQuery):AxiosPromise<pvModuleListVO[]> => {
return request({
url: '/progress/progressPlanDetail/detail/finished/'+ query.id,
method: 'get',
params: query
});
};
/**
* 删除进度计划详情
* @param id
*/
export const deleteDaily = (query: {id:string,detailIdList:string[]}) => {
return request({
url: '/progress/progressPlanDetail/remove/detail',
method: 'delete',
params: query
});
};
export const workScheduleDel=()=>{}

View File

@ -28,6 +28,8 @@ export interface ProgressCategoryVO {
* 子对象 * 子对象
*/ */
children: ProgressCategoryVO[]; children: ProgressCategoryVO[];
threeChildren: any[];
hasChildren:any;
} }
export interface ProgressCategoryForm extends BaseEntity { export interface ProgressCategoryForm extends BaseEntity {
@ -88,9 +90,22 @@ export interface workScheduleListVO {
}[]; }[];
} }
export interface pvModuleListQuery {
id: string | number;
pageSize?: number;
pageNum?: number;
}
export interface pvModuleListVO {
id: string | number;
name: string;
status: string;
}
export interface progressPlanDetailForm { export interface progressPlanDetailForm {
id: string | number; id: string | number;
finishedNumber: number; finishedNumber?: number;
finishedDetailIdList?: any[];
submitTime?: string; submitTime?: string;
} }

View File

@ -0,0 +1,63 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { ProgressCategoryTemplateVO, ProgressCategoryTemplateForm, ProgressCategoryTemplateQuery } from '@/api/progress/progressCategoryTemplate/types';
/**
* 查询进度类别模版列表
* @param query
* @returns {*}
*/
export const listProgressCategoryTemplate = (query?: ProgressCategoryTemplateQuery): AxiosPromise<ProgressCategoryTemplateVO[]> => {
return request({
url: '/progress/progressCategoryTemplate/list',
method: 'get',
params: query
});
};
/**
* 查询进度类别模版详细
* @param id
*/
export const getProgressCategoryTemplate = (id: string | number): AxiosPromise<ProgressCategoryTemplateVO> => {
return request({
url: '/progress/progressCategoryTemplate/' + id,
method: 'get'
});
};
/**
* 新增进度类别模版
* @param data
*/
export const addProgressCategoryTemplate = (data: ProgressCategoryTemplateForm) => {
return request({
url: '/progress/progressCategoryTemplate',
method: 'post',
data: data
});
};
/**
* 修改进度类别模版
* @param data
*/
export const updateProgressCategoryTemplate = (data: ProgressCategoryTemplateForm) => {
return request({
url: '/progress/progressCategoryTemplate',
method: 'put',
data: data
});
};
/**
* 删除进度类别模版
* @param id
*/
export const delProgressCategoryTemplate = (id: string | number | Array<string | number>) => {
return request({
url: '/progress/progressCategoryTemplate/' + id,
method: 'delete'
});
};

View File

@ -0,0 +1,105 @@
export interface ProgressCategoryTemplateVO {
/**
* 类别名称
*/
name: string;
/**
* 计量方式0无 1数量 2百分比
*/
unitType: string;
/**
* 工作类型
*/
workType: string;
/**
* 项目id0表示共用
*/
projectId: string | number;
/**
* 备注
*/
remark: string;
/**
* 子对象
*/
children: ProgressCategoryTemplateVO[];
}
export interface ProgressCategoryTemplateForm extends BaseEntity {
/**
* 主键id
*/
id?: string | number;
/**
* 父类别id
*/
pid?: string | number;
/**
* 类别名称
*/
name?: string;
/**
* 计量方式0无 1数量 2百分比
*/
unitType?: string;
/**
* 工作类型
*/
workType?: string;
/**
* 项目id0表示共用
*/
projectId?: string | number;
/**
* 备注
*/
remark?: string;
}
export interface ProgressCategoryTemplateQuery {
/**
* 父类别id
*/
pid?: string | number;
/**
* 类别名称
*/
name?: string;
/**
* 计量方式0无 1数量 2百分比
*/
unitType?: string;
/**
* 工作类型
*/
workType?: string;
/**
* 项目id0表示共用
*/
projectId?: string | number;
/**
* 日期范围参数
*/
params?: any;
}

View File

@ -162,3 +162,4 @@
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }

View File

@ -0,0 +1,539 @@
/* Logo 字体 */
@font-face {
font-family: "iconfont logo";
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
}
.logo {
font-family: "iconfont logo";
font-size: 160px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* tabs */
.nav-tabs {
position: relative;
}
.nav-tabs .nav-more {
position: absolute;
right: 0;
bottom: 0;
height: 42px;
line-height: 42px;
color: #666;
}
#tabs {
border-bottom: 1px solid #eee;
}
#tabs li {
cursor: pointer;
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
position: relative;
z-index: 1;
margin-bottom: -1px;
color: #666;
}
#tabs .active {
border-bottom-color: #f00;
color: #222;
}
.tab-container .content {
display: none;
}
/* 页面布局 */
.main {
padding: 30px 100px;
width: 960px;
margin: 0 auto;
}
.main .logo {
color: #333;
text-align: left;
margin-bottom: 30px;
line-height: 1;
height: 110px;
margin-top: -50px;
overflow: hidden;
*zoom: 1;
}
.main .logo a {
font-size: 160px;
color: #333;
}
.helps {
margin-top: 40px;
}
.helps pre {
padding: 20px;
margin: 10px 0;
border: solid 1px #e7e1cd;
background-color: #fffdef;
overflow: auto;
}
.icon_lists {
width: 100% !important;
overflow: hidden;
*zoom: 1;
}
.icon_lists li {
width: 100px;
margin-bottom: 10px;
margin-right: 20px;
text-align: center;
list-style: none !important;
cursor: default;
}
.icon_lists li .code-name {
line-height: 1.2;
}
.icon_lists .icon {
display: block;
height: 100px;
line-height: 100px;
font-size: 42px;
margin: 10px auto;
color: #333;
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
-moz-transition: font-size 0.25s linear, width 0.25s linear;
transition: font-size 0.25s linear, width 0.25s linear;
}
.icon_lists .icon:hover {
font-size: 100px;
}
.icon_lists .svg-icon {
/* 通过设置 font-size 来改变图标大小 */
width: 1em;
/* 图标和文字相邻时,垂直对齐 */
vertical-align: -0.15em;
/* 通过设置 color 来改变 SVG 的颜色/fill */
fill: currentColor;
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
normalize.css 中也包含这行 */
overflow: hidden;
}
.icon_lists li .name,
.icon_lists li .code-name {
color: #666;
}
/* markdown 样式 */
.markdown {
color: #666;
font-size: 14px;
line-height: 1.8;
}
.highlight {
line-height: 1.5;
}
.markdown img {
vertical-align: middle;
max-width: 100%;
}
.markdown h1 {
color: #404040;
font-weight: 500;
line-height: 40px;
margin-bottom: 24px;
}
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
color: #404040;
margin: 1.6em 0 0.6em 0;
font-weight: 500;
clear: both;
}
.markdown h1 {
font-size: 28px;
}
.markdown h2 {
font-size: 22px;
}
.markdown h3 {
font-size: 16px;
}
.markdown h4 {
font-size: 14px;
}
.markdown h5 {
font-size: 12px;
}
.markdown h6 {
font-size: 12px;
}
.markdown hr {
height: 1px;
border: 0;
background: #e9e9e9;
margin: 16px 0;
clear: both;
}
.markdown p {
margin: 1em 0;
}
.markdown>p,
.markdown>blockquote,
.markdown>.highlight,
.markdown>ol,
.markdown>ul {
width: 80%;
}
.markdown ul>li {
list-style: circle;
}
.markdown>ul li,
.markdown blockquote ul>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown>ul li p,
.markdown>ol li p {
margin: 0.6em 0;
}
.markdown ol>li {
list-style: decimal;
}
.markdown>ol li,
.markdown blockquote ol>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown code {
margin: 0 3px;
padding: 0 5px;
background: #eee;
border-radius: 3px;
}
.markdown strong,
.markdown b {
font-weight: 600;
}
.markdown>table {
border-collapse: collapse;
border-spacing: 0px;
empty-cells: show;
border: 1px solid #e9e9e9;
width: 95%;
margin-bottom: 24px;
}
.markdown>table th {
white-space: nowrap;
color: #333;
font-weight: 600;
}
.markdown>table th,
.markdown>table td {
border: 1px solid #e9e9e9;
padding: 8px 16px;
text-align: left;
}
.markdown>table th {
background: #F7F7F7;
}
.markdown blockquote {
font-size: 90%;
color: #999;
border-left: 4px solid #e9e9e9;
padding-left: 0.8em;
margin: 1em 0;
}
.markdown blockquote p {
margin: 0;
}
.markdown .anchor {
opacity: 0;
transition: opacity 0.3s ease;
margin-left: 8px;
}
.markdown .waiting {
color: #ccc;
}
.markdown h1:hover .anchor,
.markdown h2:hover .anchor,
.markdown h3:hover .anchor,
.markdown h4:hover .anchor,
.markdown h5:hover .anchor,
.markdown h6:hover .anchor {
opacity: 1;
display: inline-block;
}
.markdown>br,
.markdown>p>br {
clear: both;
}
.hljs {
display: block;
background: white;
padding: 0.5em;
color: #333333;
overflow-x: auto;
}
.hljs-comment,
.hljs-meta {
color: #969896;
}
.hljs-string,
.hljs-variable,
.hljs-template-variable,
.hljs-strong,
.hljs-emphasis,
.hljs-quote {
color: #df5000;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-type {
color: #a71d5d;
}
.hljs-literal,
.hljs-symbol,
.hljs-bullet,
.hljs-attribute {
color: #0086b3;
}
.hljs-section,
.hljs-name {
color: #63a35c;
}
.hljs-tag {
color: #333333;
}
.hljs-title,
.hljs-attr,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #795da3;
}
.hljs-addition {
color: #55a532;
background-color: #eaffea;
}
.hljs-deletion {
color: #bd2c00;
background-color: #ffecec;
}
.hljs-link {
text-decoration: underline;
}
/* 代码高亮 */
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre)>code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre)>code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

View File

@ -0,0 +1,211 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>iconfont Demo</title>
<link rel="shortcut icon" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg" type="image/x-icon"/>
<link rel="icon" type="image/svg+xml" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg"/>
<link rel="stylesheet" href="https://g.alicdn.com/thx/cube/1.3.2/cube.min.css">
<link rel="stylesheet" href="demo.css">
<link rel="stylesheet" href="iconfont.css">
<script src="iconfont.js"></script>
<!-- jQuery -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/7bfddb60-08e8-11e9-9b04-53e73bb6408b.js"></script>
<!-- 代码高亮 -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/a3f714d0-08e6-11e9-8a15-ebf944d7534c.js"></script>
<style>
.main .logo {
margin-top: 0;
height: auto;
}
.main .logo a {
display: flex;
align-items: center;
}
.main .logo .sub-title {
margin-left: 0.5em;
font-size: 22px;
color: #fff;
background: linear-gradient(-45deg, #3967FF, #B500FE);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
</style>
</head>
<body>
<div class="main">
<h1 class="logo"><a href="https://www.iconfont.cn/" title="iconfont 首页" target="_blank">
<img width="200" src="https://img.alicdn.com/imgextra/i3/O1CN01Mn65HV1FfSEzR6DKv_!!6000000000514-55-tps-228-59.svg">
</a></h1>
<div class="nav-tabs">
<ul id="tabs" class="dib-box">
<li class="dib active"><span>Unicode</span></li>
<li class="dib"><span>Font class</span></li>
<li class="dib"><span>Symbol</span></li>
</ul>
<a href="https://www.iconfont.cn/manage/index?manage_type=myprojects&projectId=4936044" target="_blank" class="nav-more">查看项目</a>
</div>
<div class="tab-container">
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe72d;</span>
<div class="name">问号</div>
<div class="code-name">&amp;#xe72d;</div>
</li>
</ul>
<div class="article markdown">
<h2 id="unicode-">Unicode 引用</h2>
<hr>
<p>Unicode 是字体在网页端最原始的应用方式,特点是:</p>
<ul>
<li>支持按字体的方式去动态调整图标大小,颜色等等。</li>
<li>默认情况下不支持多色,直接添加多色图标会自动去色。</li>
</ul>
<blockquote>
<p>注意:新版 iconfont 支持两种方式引用多色图标SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)</p>
</blockquote>
<p>Unicode 使用步骤如下:</p>
<h3 id="-font-face">第一步:拷贝项目下面生成的 <code>@font-face</code></h3>
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1748505165241') format('woff2'),
url('iconfont.woff?t=1748505165241') format('woff'),
url('iconfont.ttf?t=1748505165241') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
<pre><code class="language-css"
>.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取字体编码,应用于页面</h3>
<pre>
<code class="language-html"
>&lt;span class="iconfont"&gt;&amp;#x33;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"iconfont" 是你项目下的 font-family。可以通过编辑项目查看默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont icon-wenhao"></span>
<div class="name">
问号
</div>
<div class="code-name">.icon-wenhao
</div>
</li>
</ul>
<div class="article markdown">
<h2 id="font-class-">font-class 引用</h2>
<hr>
<p>font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。</p>
<p>与 Unicode 使用方式相比,具有如下特点:</p>
<ul>
<li>相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。</li>
<li>因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-fontclass-">第一步:引入项目下面生成的 fontclass 代码:</h3>
<pre><code class="language-html">&lt;link rel="stylesheet" href="./iconfont.css"&gt;
</code></pre>
<h3 id="-">第二步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;span class="iconfont icon-xxx"&gt;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"
iconfont" 是你项目下的 font-family。可以通过编辑项目查看默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-wenhao"></use>
</svg>
<div class="name">问号</div>
<div class="code-name">#icon-wenhao</div>
</li>
</ul>
<div class="article markdown">
<h2 id="symbol-">Symbol 引用</h2>
<hr>
<p>这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇<a href="">文章</a>
这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:</p>
<ul>
<li>支持多色图标了,不再受单色限制。</li>
<li>通过一些技巧,支持像字体那样,通过 <code>font-size</code>, <code>color</code> 来调整样式。</li>
<li>兼容性较差,支持 IE9+,及现代浏览器。</li>
<li>浏览器渲染 SVG 的性能一般,还不如 png。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-symbol-">第一步:引入项目下面生成的 symbol 代码:</h3>
<pre><code class="language-html">&lt;script src="./iconfont.js"&gt;&lt;/script&gt;
</code></pre>
<h3 id="-css-">第二步:加入通用 CSS 代码(引入一次就行):</h3>
<pre><code class="language-html">&lt;style&gt;
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
&lt;/style&gt;
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;svg class="icon" aria-hidden="true"&gt;
&lt;use xlink:href="#icon-xxx"&gt;&lt;/use&gt;
&lt;/svg&gt;
</code></pre>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('.tab-container .content:first').show()
$('#tabs li').click(function (e) {
var tabContent = $('.tab-container .content')
var index = $(this).index()
if ($(this).hasClass('active')) {
return
} else {
$('#tabs li').removeClass('active')
$(this).addClass('active')
tabContent.hide().eq(index).fadeIn()
}
})
})
</script>
</body>
</html>

View File

@ -0,0 +1,19 @@
@font-face {
font-family: "iconfont"; /* Project id 4936044 */
src: url('iconfont.woff2?t=1748505165241') format('woff2'),
url('iconfont.woff?t=1748505165241') format('woff'),
url('iconfont.ttf?t=1748505165241') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-wenhao:before {
content: "\e72d";
}

View File

@ -0,0 +1 @@
window._iconfont_svg_string_4936044='<svg><symbol id="icon-wenhao" viewBox="0 0 1024 1024"><path d="M463.99957 784.352211c0 26.509985 21.490445 48.00043 48.00043 48.00043s48.00043-21.490445 48.00043-48.00043c0-26.509985-21.490445-48.00043-48.00043-48.00043S463.99957 757.842226 463.99957 784.352211z" fill="#575B66" ></path><path d="M512 960c-247.039484 0-448-200.960516-448-448S264.960516 64 512 64 960 264.960516 960 512 759.039484 960 512 960zM512 128.287273c-211.584464 0-383.712727 172.128262-383.712727 383.712727 0 211.551781 172.128262 383.712727 383.712727 383.712727 211.551781 0 383.712727-172.159226 383.712727-383.712727C895.712727 300.415536 723.551781 128.287273 512 128.287273z" fill="#575B66" ></path><path d="M512 673.695256c-17.664722 0-32.00086-14.336138-32.00086-31.99914l0-54.112297c0-52.352533 39.999785-92.352318 75.32751-127.647359 25.887273-25.919957 52.67249-52.67249 52.67249-74.016718 0-53.343368-43.07206-96.735385-95.99914-96.735385-53.823303 0-95.99914 41.535923-95.99914 94.559333 0 17.664722-14.336138 31.99914-32.00086 31.99914s-32.00086-14.336138-32.00086-31.99914c0-87.423948 71.775299-158.559333 160.00086-158.559333s160.00086 72.095256 160.00086 160.735385c0 47.904099-36.32028 84.191695-71.424378 119.295794-27.839699 27.776052-56.575622 56.511974-56.575622 82.3356l0 54.112297C544.00086 659.328155 529.664722 673.695256 512 673.695256z" fill="#575B66" ></path></symbol></svg>',(n=>{var t=(e=(e=document.getElementsByTagName("script"))[e.length-1]).getAttribute("data-injectcss"),e=e.getAttribute("data-disable-injectsvg");if(!e){var i,o,c,d,s,a=function(t,e){e.parentNode.insertBefore(t,e)};if(t&&!n.__iconfont__svg__cssinject__){n.__iconfont__svg__cssinject__=!0;try{document.write("<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>")}catch(t){console&&console.log(t)}}i=function(){var t,e=document.createElement("div");e.innerHTML=n._iconfont_svg_string_4936044,(e=e.getElementsByTagName("svg")[0])&&(e.setAttribute("aria-hidden","true"),e.style.position="absolute",e.style.width=0,e.style.height=0,e.style.overflow="hidden",e=e,(t=document.body).firstChild?a(e,t.firstChild):t.appendChild(e))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(i,0):(o=function(){document.removeEventListener("DOMContentLoaded",o,!1),i()},document.addEventListener("DOMContentLoaded",o,!1)):document.attachEvent&&(c=i,d=n.document,s=!1,r(),d.onreadystatechange=function(){"complete"==d.readyState&&(d.onreadystatechange=null,l())})}function l(){s||(s=!0,c())}function r(){try{d.documentElement.doScroll("left")}catch(t){return void setTimeout(r,50)}l()}})(window);

View File

@ -0,0 +1,16 @@
{
"id": "4936044",
"name": "no name",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "577319",
"name": "问号",
"font_class": "wenhao",
"unicode": "e72d",
"unicode_decimal": 59181
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -139,28 +139,37 @@ const handlePosition = (data: any, node) => {
map.getView().setCenter(centerPosition.value); map.getView().setCenter(centerPosition.value);
}; };
const handleCheckChange = (data: any, bool) => { const handleCheckChange = (data: any, bool: boolean) => {
// 处理树形结构的选中变化
let features = treeData.value[data.index].features;
if (isMenuVisible.value) isMenuVisible.value = false; if (isMenuVisible.value) isMenuVisible.value = false;
const features = treeData.value[data.index].features;
if (bool) { if (bool) {
if (!layerData[features[0].properties.id]) {
features.forEach((item: any) => { features.forEach((item: any) => {
creatPoint(item.geometry.coordinates, item.geometry.type, item.geometry.id, item.properties.text); const fid = item.geometry.id;
});
} else { // 没创建过就先创建
features.forEach((item: any) => { if (!featureMap[fid]) {
map.addLayer(layerData[item.geometry.id]); creatPoint(item.geometry.coordinates, item.geometry.type, fid, item.properties.text);
});
}
} else {
features.forEach((item, index) => {
map.removeLayer(layerData[item.geometry.id]);
});
} }
// creatPoint(fromLonLat(data.geometry.coordinates), data.geometry.type); // 添加到共享 source 中(避免重复添加)
const feature = featureMap[fid];
if (!sharedSource.hasFeature(feature)) {
sharedSource.addFeature(feature);
}
});
} else {
features.forEach((item: any) => {
const fid = item.geometry.id;
const feature = featureMap[fid];
if (feature && sharedSource.hasFeature(feature)) {
sharedSource.removeFeature(feature);
}
});
}
}; };
function initOLMap() { function initOLMap() {
// 创造地图实例 // 创造地图实例
map = new Map({ map = new Map({
@ -246,58 +255,57 @@ function convertStrToNum(arr) {
* @param {*} id 唯一id * @param {*} id 唯一id
* @param {*} name 名称 * @param {*} name 名称
* */ * */
// 共享 source 和图层(全局一次性创建)
const sharedSource = new VectorSource();
const sharedLayer = new VectorLayer({
source: sharedSource,
renderMode: 'image' // 提高渲染性能
} as any);
// id => Feature 映射表
const featureMap: Record<string, Feature> = {};
const creatPoint = (pointObj: Array<any>, type: string, id: string, name?: string) => { const creatPoint = (pointObj: Array<any>, type: string, id: string, name?: string) => {
// 创建多边形的几何对象 let geometry;
let polygon;
if (type === 'Point') { if (type === 'Point') {
polygon = new Point(fromLonLat(pointObj)); geometry = new Point(fromLonLat(pointObj));
} else if (type === 'LineString') { } else if (type === 'LineString') {
const lineStringData = pointObj.map((arr: any) => fromLonLat(arr)); const coords = pointObj.map((arr: any) => fromLonLat(arr));
polygon = new Polygon([lineStringData]); // 注意:这里虽然是 LineString 类型,但数据实际表示的是闭合面
geometry = new Polygon([coords]);
} else { } else {
const polygonData = pointObj.map((arr: any) => arr.map((i: any) => fromLonLat(i))); const coords = pointObj.map((arr: any) => arr.map((i: any) => fromLonLat(i)));
polygon = new Polygon(polygonData); geometry = new Polygon(coords);
} }
// 创建特征Feature
let polygonFeature = new Feature({ const feature = new Feature({ geometry });
geometry: polygon
});
const pointStyle = new Style({ const pointStyle = new Style({
image: new Circle({ image: new Circle({
radius: 2, radius: 2,
fill: new Fill({ fill: new Fill({ color: 'red' })
color: 'red'
})
}), }),
text: new Text({ text: new Text({
font: '12px Microsoft YaHei', font: '12px Microsoft YaHei',
text: name, text: name,
scale: 1, scale: 1,
fill: new Fill({ fill: new Fill({ color: '#7bdd63' })
color: '#7bdd63'
})
}) })
}); });
const polygonStyle = new Style({ const polygonStyle = new Style({
stroke: new Stroke({ stroke: new Stroke({
color: type === 'LineString' ? 'skyblue' : 'purple', // 多边形边界线的颜色 color: type === 'LineString' ? 'skyblue' : 'purple',
width: 2 // 多边形边界线的宽度 width: 2
}), }),
fill: new Fill({ fill: new Fill({ color: 'transparent' })
color: 'transparent' // 多边形填充颜色,这里设置为半透明红色
})
}); });
// 设置多边形的样式Style
polygonFeature.setStyle(type === 'Point' ? pointStyle : polygonStyle);
// 创建和添加特征到源Source
let source = new VectorSource();
source.addFeature(polygonFeature);
// 创建图层并设置源Layer
layerData[id] = new VectorLayer();
layerData[id].setSource(source);
map.addLayer(layerData[id]); feature.setStyle(type === 'Point' ? pointStyle : polygonStyle);
// 缓存 feature用于后续判断
featureMap[id] = feature;
}; };
// 控制菜单是否显示 // 控制菜单是否显示
@ -490,6 +498,7 @@ watch(
onMounted(() => { onMounted(() => {
// 地图初始化 // 地图初始化
initOLMap(); initOLMap();
map.addLayer(sharedLayer);
// creatPoint( // creatPoint(
// [ // [
// [ // [

View File

@ -2,6 +2,7 @@ import { createApp } from 'vue';
// global css // global css
import 'virtual:uno.css'; import 'virtual:uno.css';
import '@/assets/styles/index.scss'; import '@/assets/styles/index.scss';
import '@/assets/iconfont/iconfont.css';
import 'element-plus/theme-chalk/dark/css-vars.css'; import 'element-plus/theme-chalk/dark/css-vars.css';
// App、router、store // App、router、store

View File

@ -103,7 +103,12 @@ export const constantRoutes: RouteRecordRaw[] = [
path: '/drone', path: '/drone',
component: () => import('@/views/drone/index.vue'), component: () => import('@/views/drone/index.vue'),
hidden: true hidden: true
} },
{
path: '/progress/progressPaper',
component: () => import('@/views/progress/progressPaper/index.vue'),
hidden: true
},
]; ];
// 动态路由,基于用户权限动态去加载 // 动态路由,基于用户权限动态去加载

View File

@ -1,8 +1,16 @@
<template> <template>
<div class="daily_paper"> <div class="daily_paper">
<el-dialog v-model="isShowDialog" @close="onCancel" width="1000px" :close-on-click-modal="false" :destroy-on-close="true"> <el-dialog
v-model="isShowDialog"
@close="onCancel"
width="1000px"
:close-on-click-modal="false"
:destroy-on-close="true"
:lock-scroll="false"
:append-to-body="false"
>
<template #header> <template #header>
<div v-drag="['.daily_paper .el-dialog', '.daily_paper .el-dialog__header']" style="font-size: 18px">{{ infoDetail.name }} 日报填写</div> <div style="font-size: 18px">{{ infoDetail.name }} 日报填写</div>
</template> </template>
<div class="box"> <div class="box">
<div class="box-left"> <div class="box-left">
@ -197,6 +205,32 @@ const clickOpen = (row: any) => {
defineExpose({ openDialog }); defineExpose({ openDialog });
const emit = defineEmits(['getProgressList']); const emit = defineEmits(['getProgressList']);
let scrollTop = 0;
watch(
() => isShowDialog.value,
(visible) => {
if (visible) {
scrollTop = window.scrollY || document.documentElement.scrollTop;
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
document.body.style.position = 'fixed';
document.body.style.top = `-${scrollTop}px`;
document.body.style.width = '100%';
document.body.style.paddingRight = `${scrollbarWidth}px`; // 👈 补偿滚动条宽度
} else {
document.body.style.position = '';
document.body.style.top = '';
document.body.style.width = '';
document.body.style.paddingRight = ''; // 👈 恢复
nextTick(() => {
window.scrollTo(0, scrollTop);
});
}
}
);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -1,6 +1,14 @@
<template> <template>
<div class="daily-paper-count"> <div class="daily-paper-count">
<el-dialog v-model="isShowDialog" @close="onCancel" width="65vw" :close-on-click-modal="false" :destroy-on-close="true"> <el-dialog
v-model="isShowDialog"
@close="onCancel"
width="70vw"
:close-on-click-modal="false"
:destroy-on-close="true"
:lock-scroll="false"
:append-to-body="false"
>
<template #header> <template #header>
<div v-drag="['.daily-paper-count .el-dialog', '.daily-paper-count .el-dialog__header']" style="font-size: 18px"> <div v-drag="['.daily-paper-count .el-dialog', '.daily-paper-count .el-dialog__header']" style="font-size: 18px">
{{ infoDetail.name }} 日报填写 {{ infoDetail.name }} 日报填写
@ -24,23 +32,23 @@
<div style="margin-left: 45px" m="4"> <div style="margin-left: 45px" m="4">
<el-table <el-table
:border="true" :border="true"
:data="propsRow.detail" :data="propsRow.detailList"
:header-cell-style="{ 'text-align': 'center' }" :header-cell-style="{ 'text-align': 'center' }"
:cell-style="{ 'text-align': 'center' }" :cell-style="{ 'text-align': 'center' }"
highlight-current-row highlight-current-row
> >
<el-table-column label="序号" type="index" width="60px" /> <el-table-column label="序号" type="index" width="60px" />
<el-table-column label="计划日期" prop="date" /> <el-table-column label="计划日期" prop="date" />
<el-table-column label="数量" prop="planNum" width="60" /> <el-table-column label="数量" prop="planNumber" width="60" />
<el-table-column label="完成量" prop="finishedNum" width="60" /> <el-table-column label="完成量" prop="finishedNumber" width="70" />
<el-table-column label="AI填报" prop="autoFill" width="60" /> <el-table-column label="AI填报" prop="aiFill" width="70" />
<el-table-column label="操作" class-name="small-padding" width="200px" fixed="right"> <el-table-column label="操作" class-name="small-padding" width="170px" fixed="right">
<template #default="{ row: scopeRow, $index }"> <template #default="{ row: scopeRow, $index }">
<el-button type="primary" link @click="handleDayAdd(scopeRow, propsRow)"> <el-button type="primary" link @click="handleDayAdd(scopeRow, propsRow)">
<el-icon><ele-Plus /></el-icon>日报 <el-icon><Plus /></el-icon>日报
</el-button> </el-button>
<el-button type="success" link @click="handleView(scopeRow, propsRow)"> <el-button type="success" link @click="handleView(scopeRow, propsRow)">
<el-icon><ele-View /></el-icon>查看 <el-icon><View /></el-icon>查看
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
@ -49,8 +57,8 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="序号" type="index" :index="indexMethod" width="60px" /> <el-table-column label="序号" type="index" :index="indexMethod" width="60px" />
<el-table-column label="计划数量" prop="planNum" min-width="100px" /> <el-table-column label="计划数量" prop="planNumber" min-width="100px" />
<el-table-column label="完成数量" prop="finishedNum" min-width="100px" /> <el-table-column label="完成数量" prop="finishedNumber" min-width="100px" />
<el-table-column label="延期量" min-width="100px"> <el-table-column label="延期量" min-width="100px">
<template #default="{ row: scopeRow }"> <template #default="{ row: scopeRow }">
<el-tag :type="filterW(scopeRow) ? 'danger' : 'success'"> <el-tag :type="filterW(scopeRow) ? 'danger' : 'success'">
@ -58,10 +66,10 @@
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="AI填报" prop="autoFill" min-width="100px" /> <el-table-column label="AI填报" prop="aiFill" min-width="100px" />
<el-table-column label="开始时间" min-width="100px"> <el-table-column label="开始时间" min-width="100px">
<template #default="{ row: scopeRow }"> <template #default="{ row: scopeRow }">
<span>{{ scopeRow.startAt.split(' ')[0] }}</span> <span>{{ scopeRow.startDate.split(' ')[0] }}</span>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -73,13 +81,14 @@
@pagination="getWorkList" @pagination="getWorkList"
layout="total, sizes, prev, pager, next" layout="total, sizes, prev, pager, next"
:isSmall="5" :isSmall="5"
class="float-left mt-4.5!"
/> />
</div> </div>
<div class="box_right" v-if="showDayWork"> <div class="box_right" v-if="showDayWork">
<div class="time_submit"> <div class="time_submit">
<span>{{ formDetail.submitTime }}</span> <span>{{ formDetail.submitTime }}</span>
<el-button type="primary" :disabled="!checkedList.length || flag" @click="onUploadDaily" size="large"> <el-button type="primary" :disabled="!checkedList.length || flag" @click="onUploadDaily" size="small">
<el-icon><ele-Upload /></el-icon>提交日报 <el-icon><Upload /></el-icon>提交日报
</el-button> </el-button>
</div> </div>
<el-table <el-table
@ -97,14 +106,14 @@
<el-table-column label="状态" align="center" min-width="100px"> <el-table-column label="状态" align="center" min-width="100px">
<template #default="{ row: scopeRow }"> <template #default="{ row: scopeRow }">
<el-tag :type="typeList[scopeRow.status]"> <el-tag :type="typeList[scopeRow.status]">
{{ filterStatus(scopeRow.status) }} {{ filterStatus(Number(scopeRow.status)) }}
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination <pagination
v-show="detailTotal > 0" v-show="1"
:total="detailTotal" :total="detailTotal"
v-model:page="detailQueryParams.pageNum" v-model:page="detailQueryParams.pageNum"
v-model:limit="detailQueryParams.pageSize" v-model:limit="detailQueryParams.pageSize"
@ -116,8 +125,8 @@
<div class="box_right" v-else> <div class="box_right" v-else>
<div class="time_submit"> <div class="time_submit">
<span>{{ formDetail.submitTime }}</span> <span>{{ formDetail.submitTime }}</span>
<el-button type="danger" :disabled="single" @click="handleRemove(null)" size="large"> <el-button type="danger" :disabled="single" @click="handleRemove(null)" size="small">
<el-icon><ele-SemiSelect /></el-icon>批量移除 <el-icon><Minus /></el-icon>批量移除
</el-button> </el-button>
</div> </div>
<el-table <el-table
@ -134,14 +143,14 @@
<el-table-column label="编号" align="center" prop="name" min-width="100px" /> <el-table-column label="编号" align="center" prop="name" min-width="100px" />
<el-table-column label="填报方式" align="center" prop="status" min-width="100px"> <el-table-column label="填报方式" align="center" prop="status" min-width="100px">
<template #default="{ row: scopeRow }"> <template #default="{ row: scopeRow }">
<span v-if="scopeRow.status === 2">手动填报</span> <span v-if="scopeRow.finishType === '1'">手动填报</span>
<span v-if="scopeRow.status === 3">AI识别</span> <span v-if="scopeRow.finishType === '2'">AI识别</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" min-width="100px"> <el-table-column label="操作" align="center" min-width="100px">
<template #default="{ row: scopeRow }"> <template #default="{ row: scopeRow }">
<el-button type="danger" link @click="handleRemove(scopeRow)"> <el-button type="danger" link @click="handleRemove(scopeRow)">
<el-icon><ele-SemiSelect /></el-icon>移除 <el-icon><Minus /></el-icon>移除
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
@ -175,7 +184,7 @@ import { workScheduleListQuery } from '@/api/progress/plan/types';
// 响应式状态 // 响应式状态
const state = reactive<{ const state = reactive<{
expandRowKeys: number[]; expandRowKeys: string[];
loading: boolean; loading: boolean;
isShowDialog: boolean; isShowDialog: boolean;
queryParams: workScheduleListQuery; queryParams: workScheduleListQuery;
@ -186,7 +195,7 @@ const state = reactive<{
workId: string; workId: string;
id: string; id: string;
submitTime: string; submitTime: string;
finishedNum: number; finishedNumber: number;
}; };
detialList: any[]; detialList: any[];
detailTotal: number; detailTotal: number;
@ -225,12 +234,12 @@ const state = reactive<{
workId: '', workId: '',
id: '', id: '',
submitTime: '选择日期', submitTime: '选择日期',
finishedNum: 0 finishedNumber: 0
}, },
detialList: [], detialList: [],
detailTotal: 0, detailTotal: 0,
detailQueryParams: { detailQueryParams: {
pageSize: 20, pageSize: 10,
pageNum: 1 pageNum: 1
}, },
loading1: false, loading1: false,
@ -242,7 +251,7 @@ const state = reactive<{
detialWordList: [], detialWordList: [],
detailTotalWork: 0, detailTotalWork: 0,
detailQueryParamsWork: { detailQueryParamsWork: {
pageSize: 20, pageSize: 10,
pageNum: 1 pageNum: 1
}, },
single: true, single: true,
@ -318,15 +327,13 @@ const resetForm = (bool: boolean) => {
const getPvModuleList = () => { const getPvModuleList = () => {
loading1.value = true; loading1.value = true;
pvModuleList({ pvModuleList({
workId: formDetail.value.workId, id: formDetail.value.id,
...detailQueryParams.value, ...detailQueryParams.value
type: infoDetail.value.work_type,
status: 0
}).then((res: any) => { }).then((res: any) => {
loading1.value = false; loading1.value = false;
if (res.code === 0) { if (res.code === 200) {
detialList.value = res.data.list; detialList.value = res.rows;
detailTotal.value = res.data.total; detailTotal.value = res.total;
} }
}); });
}; };
@ -335,13 +342,13 @@ const getPvModuleList = () => {
const getWorkList = (bool = false) => { const getWorkList = (bool = false) => {
loading.value = true; loading.value = true;
workScheduleList(queryParams.value).then((res: any) => { workScheduleList(queryParams.value).then((res: any) => {
if (res.code === 0) { if (res.code === 200) {
state.tableData = res.data.list.map((item: any, i: number) => { state.tableData = res.rows.map((item: any, i: number) => {
item.index = i + 1; item.index = i + 1;
item.autoFill = item.detail?.reduce((sum: number, child: any) => sum + child.autoFill, 0) || 0; item.aiFill = item.detailList?.reduce((sum: number, child: any) => sum + child.aiFill, 0) || 0;
return item; return item;
}); });
state.total = res.data.total; state.total = res.total;
} }
loading.value = false; loading.value = false;
}); });
@ -366,7 +373,7 @@ const indexMethod = (index: number): number => {
// 日报添加 // 日报添加
const handleDayAdd = (row: any, obj: any) => { const handleDayAdd = (row: any, obj: any) => {
resetForm(true); resetForm(true);
formDetail.value.id = obj.id; formDetail.value.id = row.id;
formDetail.value.submitTime = row.date; formDetail.value.submitTime = row.date;
state.updateRow = row; state.updateRow = row;
getPvModuleList(); getPvModuleList();
@ -376,8 +383,9 @@ const tableKey = (row: any) => row.id;
// 展开行处理 // 展开行处理
const clickOpen = (row: any) => { const clickOpen = (row: any) => {
const index = state.expandRowKeys.indexOf(row.id); const rowId = String(row.id);
index === -1 ? state.expandRowKeys.push(row.id) : state.expandRowKeys.splice(index, 1); const index = state.expandRowKeys.indexOf(rowId);
index === -1 ? state.expandRowKeys.push(rowId) : state.expandRowKeys.splice(index, 1);
state.expandRowKeys = [...new Set(state.expandRowKeys)]; state.expandRowKeys = [...new Set(state.expandRowKeys)];
}; };
@ -394,22 +402,21 @@ const onUploadDaily = () => {
} }
const obj = { const obj = {
ids: checkedList.value, finishedDetailIdList: checkedList.value,
workID: formDetail.value.workId, id: formDetail.value.id
doneTime: formDetail.value.submitTime,
planID: formDetail.value.id
}; };
state.flag = true; state.flag = true;
addDaily(obj).then((res: any) => { addDaily(obj).then((res: any) => {
if (res.code === 0) { if (res.code === 200) {
ElMessage.success('添加成功'); ElMessage.success('添加成功');
if (state.updateRow) { if (state.updateRow) {
state.updateRow.finishedNum += checkedList.value.length; state.updateRow.finishedNumber += checkedList.value.length;
} }
checkedList.value = []; checkedList.value = [];
multipleTableRef.value?.clearSelection(); multipleTableRef.value?.clearSelection();
getPvModuleList(); getPvModuleList();
getWorkList();
} else { } else {
ElMessage.error(res.message); ElMessage.error(res.message);
} }
@ -420,10 +427,11 @@ const onUploadDaily = () => {
// 查看日报 // 查看日报
const handleView = (row: any, obj: any) => { const handleView = (row: any, obj: any) => {
resetForm(false); resetForm(false);
getDailyBookList(row.date);
state.updateRow = row; state.updateRow = row;
formDetail.value.id = obj.id; formDetail.value.id = row.id;
formDetail.value.submitTime = row.date; formDetail.value.submitTime = row.date;
getDailyBookList(row.date);
showDayWork.value = false; showDayWork.value = false;
}; };
@ -431,12 +439,12 @@ const handleView = (row: any, obj: any) => {
const getDailyBookList = (doneTime: string) => { const getDailyBookList = (doneTime: string) => {
detialWordList.value = []; detialWordList.value = [];
getDailyBook({ getDailyBook({
workId: formDetail.value.workId, id: formDetail.value.id,
type: infoDetail.value.work_type, ...detailQueryParams.value
doneTime
}).then((res: any) => { }).then((res: any) => {
if (res.code === 0) { if (res.code === 200) {
detialWordList.value = res.data.list || []; detialWordList.value = res.rows || [];
detailTotalWork.value = res.total;
} else { } else {
ElMessage.error(res.message); ElMessage.error(res.message);
} }
@ -453,10 +461,8 @@ const handleSelectionChangeWork = (selection: any[]) => {
const handleRemove = (row?: any) => { const handleRemove = (row?: any) => {
const planID = row ? [row.id] : state.checkList; const planID = row ? [row.id] : state.checkList;
const obj = { const obj = {
planID, detailIdList: planID,
id: formDetail.value.id, id: formDetail.value.id
workID: formDetail.value.workId,
time: formDetail.value.submitTime
}; };
ElMessageBox.confirm('确认移除该条数据?', '温馨提示', { ElMessageBox.confirm('确认移除该条数据?', '温馨提示', {
@ -466,12 +472,13 @@ const handleRemove = (row?: any) => {
}) })
.then(() => { .then(() => {
deleteDaily(obj).then((res: any) => { deleteDaily(obj).then((res: any) => {
if (res.code === 0) { if (res.code === 200) {
ElMessage.success('移除成功'); ElMessage.success('移除成功');
if (state.updateRow) { if (state.updateRow) {
state.updateRow.finishedNum -= planID.length; state.updateRow.finishedNumber -= planID.length;
} }
getDailyBookList(formDetail.value.submitTime); getDailyBookList(formDetail.value.submitTime);
getWorkList();
} else { } else {
ElMessage.error(res.message); ElMessage.error(res.message);
} }
@ -482,14 +489,14 @@ const handleRemove = (row?: any) => {
// 延期计算 // 延期计算
const filterW = (row: any): number => { const filterW = (row: any): number => {
const { finishedNum, planNum, endAt } = row; const { finishedNumber, planNumber, endAt } = row;
if (!endAt) return 0; if (!endAt) return 0;
const endTime = new Date(endAt).getTime(); const endTime = new Date(endAt).getTime();
const now = new Date().getTime(); const now = new Date().getTime();
if (endTime <= now && planNum > finishedNum) { if (endTime <= now && planNumber > finishedNumber) {
return planNum - finishedNum; return planNumber - finishedNumber;
} }
return 0; return 0;
}; };
@ -500,6 +507,32 @@ defineExpose({
closeDialog closeDialog
}); });
const emit = defineEmits(['getProgressList']); const emit = defineEmits(['getProgressList']);
let scrollTop = 0;
watch(
() => isShowDialog.value,
(visible) => {
if (visible) {
scrollTop = window.scrollY || document.documentElement.scrollTop;
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
document.body.style.position = 'fixed';
document.body.style.top = `-${scrollTop}px`;
document.body.style.width = '100%';
document.body.style.paddingRight = `${scrollbarWidth}px`; // 👈 补偿滚动条宽度
} else {
document.body.style.position = '';
document.body.style.top = '';
document.body.style.width = '';
document.body.style.paddingRight = ''; // 👈 恢复
nextTick(() => {
window.scrollTo(0, scrollTop);
});
}
}
);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -253,6 +253,7 @@ const onSubmit = () => {
workScheduleAddPlan(payload).then((res: any) => { workScheduleAddPlan(payload).then((res: any) => {
if (res.code === 200) { if (res.code === 200) {
ElMessage.success('添加成功'); ElMessage.success('添加成功');
emit('getProgressList');
closeDialog(); closeDialog();
} else { } else {
ElMessage.error(res.message); ElMessage.error(res.message);
@ -292,6 +293,7 @@ const fetchLastTime = (row: typeof infoDetail) => {
// Export function if needed externally // Export function if needed externally
defineExpose({ openDialog }); defineExpose({ openDialog });
const emit = defineEmits(['getProgressList']);
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -47,10 +47,27 @@
border border
> >
<el-table-column label="" width="50" type="expand"> <el-table-column label="" width="50" type="expand">
<template #header>
<el-icon
class="cursor-pointer text-4! transform-rotate-z--90 transition-all-300"
:class="!isExpandAll ? 'transform-rotate-z--90' : 'transform-rotate-z-90'"
@click="handleToggleExpandAll"
><Expand
/></el-icon>
</template>
<template #default="scope"> <template #default="scope">
<el-card class="pl-25" shadow="hover"> <el-card class="pl-25" shadow="hover">
<el-table :data="scope.row.children" border> <el-table :data="scope.row.children" border>
<el-table-column label="名称" align="center" prop="name" width="150" /> <el-table-column label="名称" align="center" prop="name" width="170">
<template #default="{ row }">
<el-tooltip :content="row.remark" placement="top" effect="dark" v-if="row.remark">
<span class="flex items-center justify-center"
><i class="iconfont icon-wenhao mr-0.5 text-3.5! text-#999"></i>{{ row.name }}</span
>
</el-tooltip>
<span v-else>{{ row.name }}</span>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status" width="100"> <el-table-column label="状态" align="center" prop="status" width="100">
<template #default="{ row }"> <template #default="{ row }">
<dict-tag :options="progress_status" :value="row.status" /> <dict-tag :options="progress_status" :value="row.status" />
@ -85,7 +102,7 @@
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200">
<template #default="scope"> <template #default="scope">
<el-button <!-- <el-button
type="warning" type="warning"
icon="Download" icon="Download"
link link
@ -94,12 +111,13 @@
v-hasPermi="['progress:progressCategory:add']" v-hasPermi="['progress:progressCategory:add']"
> >
导入数据 导入数据
</el-button> </el-button> -->
<el-button <el-button
type="warning" type="warning"
icon="Download" icon="Download"
link link
size="small" size="small"
v-if="scope.row.name === '光伏板'"
@click="openDialog(scope.row, 'importTableStatus', '上传表格')" @click="openDialog(scope.row, 'importTableStatus', '上传表格')"
v-hasPermi="['progress:progressCategory:add']" v-hasPermi="['progress:progressCategory:add']"
> >
@ -254,10 +272,14 @@ const { queryParams, form, rules } = toRefs(data);
const getList = async () => { const getList = async () => {
if (!queryParams.value.matrixId) { if (!queryParams.value.matrixId) {
const res = await getProjectSquare(currentProject.value.id); const res = await getProjectSquare(currentProject.value.id);
if (res.rows.length === 0) {
proxy?.$modal.msgWarning('当前项目下没有方阵,请先创建方阵');
} else {
if (!matrixValue.value) matrixValue.value = res.rows[0].id; if (!matrixValue.value) matrixValue.value = res.rows[0].id;
matrixOptions.value = res.rows; matrixOptions.value = res.rows;
queryParams.value.matrixId = res.rows[0].id; queryParams.value.matrixId = res.rows[0].id;
} }
}
loading.value = true; loading.value = true;
const res = await listProgressCategory(queryParams.value); const res = await listProgressCategory(queryParams.value);
const data = proxy?.handleTree<ProgressCategoryVO>(res.data, 'id', 'pid'); const data = proxy?.handleTree<ProgressCategoryVO>(res.data, 'id', 'pid');
@ -288,6 +310,13 @@ const reset = () => {
progressCategoryFormRef.value?.resetFields(); progressCategoryFormRef.value?.resetFields();
}; };
//切换项目重置方阵
const resetMatrix = () => {
matrixValue.value = undefined;
queryParams.value.matrixId = undefined;
matrixOptions.value = [];
};
/** 搜索按钮操作 */ /** 搜索按钮操作 */
const handleQuery = () => { const handleQuery = () => {
getList(); getList();
@ -384,6 +413,7 @@ const listeningProject = watch(
(nid, oid) => { (nid, oid) => {
queryParams.value.projectId = nid; queryParams.value.projectId = nid;
form.value.projectId = nid; form.value.projectId = nid;
resetMatrix();
getList(); getList();
} }
); );

View File

@ -0,0 +1,282 @@
<template>
<div class="p-2">
<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="父类别id" prop="pid">
<el-input v-model="queryParams.pid" placeholder="请输入父类别id" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="类别名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入类别名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="项目id" prop="projectId">
<el-input v-model="queryParams.projectId" placeholder="请输入项目id" clearable @keyup.enter="handleQuery" />
</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">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['progress:progressCategoryTemplate:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table
ref="progressCategoryTemplateTableRef"
v-loading="loading"
:data="progressCategoryTemplateList"
row-key="id"
:default-expand-all="isExpandAll"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column label="类别名称" align="center" prop="name" />
<el-table-column label="计量方式" align="center" prop="unitType" />
<el-table-column label="工作类型" align="center" prop="workType" />
<el-table-column label="项目id" align="center" prop="projectId" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['progress:progressCategoryTemplate:edit']" />
</el-tooltip>
<el-tooltip content="新增" placement="top">
<el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['progress:progressCategoryTemplate:add']" />
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['progress:progressCategoryTemplate:remove']" />
</el-tooltip>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 添加或修改进度类别模版对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="progressCategoryTemplateFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="父类别id" prop="pid">
<el-tree-select
v-model="form.pid"
:data="progressCategoryTemplateOptions"
:props="{ value: 'id', label: 'name', children: 'children' }"
value-key="id"
placeholder="请选择父类别id"
check-strictly
/>
</el-form-item>
<el-form-item label="类别名称" prop="name">
<el-input v-model="form.name" placeholder="请输入类别名称" />
</el-form-item>
<el-form-item label="项目id" prop="projectId">
<el-input v-model="form.projectId" placeholder="请输入项目id" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</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="ProgressCategoryTemplate" lang="ts">
import { listProgressCategoryTemplate, getProgressCategoryTemplate, delProgressCategoryTemplate, addProgressCategoryTemplate, updateProgressCategoryTemplate } from "@/api/progress/progressCategoryTemplate";
import { ProgressCategoryTemplateVO, ProgressCategoryTemplateQuery, ProgressCategoryTemplateForm } from '@/api/progress/progressCategoryTemplate/types';
type ProgressCategoryTemplateOption = {
id: number;
name: string;
children?: ProgressCategoryTemplateOption[];
}
const { proxy } = getCurrentInstance() as ComponentInternalInstance;;
const progressCategoryTemplateList = ref<ProgressCategoryTemplateVO[]>([]);
const progressCategoryTemplateOptions = ref<ProgressCategoryTemplateOption[]>([]);
const buttonLoading = ref(false);
const showSearch = ref(true);
const isExpandAll = ref(true);
const loading = ref(false);
const queryFormRef = ref<ElFormInstance>();
const progressCategoryTemplateFormRef = ref<ElFormInstance>();
const progressCategoryTemplateTableRef = ref<ElTableInstance>()
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: ProgressCategoryTemplateForm = {
id: undefined,
pid: undefined,
name: undefined,
unitType: undefined,
workType: undefined,
projectId: undefined,
remark: undefined,
}
const data = reactive<PageData<ProgressCategoryTemplateForm, ProgressCategoryTemplateQuery>>({
form: {...initFormData},
queryParams: {
pid: undefined,
name: undefined,
unitType: undefined,
workType: undefined,
projectId: undefined,
params: {
}
},
rules: {
id: [
{ required: true, message: "主键id不能为空", trigger: "blur" }
],
pid: [
{ required: true, message: "父类别id不能为空", trigger: "blur" }
],
name: [
{ required: true, message: "类别名称不能为空", trigger: "blur" }
],
unitType: [
{ required: true, message: "计量方式不能为空", trigger: "change" }
],
projectId: [
{ required: true, message: "项目id不能为空", trigger: "blur" }
],
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询进度类别模版列表 */
const getList = async () => {
loading.value = true;
const res = await listProgressCategoryTemplate(queryParams.value);
const data = proxy?.handleTree<ProgressCategoryTemplateVO>(res.data, "id", "pid");
if (data) {
progressCategoryTemplateList.value = data;
loading.value = false;
}
}
/** 查询进度类别模版下拉树结构 */
const getTreeselect = async () => {
const res = await listProgressCategoryTemplate();
progressCategoryTemplateOptions.value = [];
const data: ProgressCategoryTemplateOption = { id: 0, name: '顶级节点', children: [] };
data.children = proxy?.handleTree<ProgressCategoryTemplateOption>(res.data, "id", "pid");
progressCategoryTemplateOptions.value.push(data);
}
// 取消按钮
const cancel = () => {
reset();
dialog.visible = false;
}
// 表单重置
const reset = () => {
form.value = {...initFormData}
progressCategoryTemplateFormRef.value?.resetFields();
}
/** 搜索按钮操作 */
const handleQuery = () => {
getList();
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
}
/** 新增按钮操作 */
const handleAdd = (row?: ProgressCategoryTemplateVO) => {
reset();
getTreeselect();
if (row != null && row.id) {
form.value.pid = row.id;
} else {
form.value.pid = 0;
}
dialog.visible = true;
dialog.title = "添加进度类别模版";
}
/** 展开/折叠操作 */
const handleToggleExpandAll = () => {
isExpandAll.value = !isExpandAll.value;
toggleExpandAll(progressCategoryTemplateList.value, isExpandAll.value)
}
/** 展开/折叠操作 */
const toggleExpandAll = (data: ProgressCategoryTemplateVO[], status: boolean) => {
data.forEach((item) => {
progressCategoryTemplateTableRef.value?.toggleRowExpansion(item, status)
if (item.children && item.children.length > 0) toggleExpandAll(item.children, status)
})
}
/** 修改按钮操作 */
const handleUpdate = async (row: ProgressCategoryTemplateVO) => {
reset();
await getTreeselect();
if (row != null) {
form.value.pid = row.pid;
}
const res = await getProgressCategoryTemplate(row.id);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = "修改进度类别模版";
}
/** 提交按钮 */
const submitForm = () => {
progressCategoryTemplateFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.id) {
await updateProgressCategoryTemplate(form.value).finally(() => buttonLoading.value = false);
} else {
await addProgressCategoryTemplate(form.value).finally(() => buttonLoading.value = false);
}
proxy?.$modal.msgSuccess("操作成功");
dialog.visible = false;
getList();
}
});
}
/** 删除按钮操作 */
const handleDelete = async (row: ProgressCategoryTemplateVO) => {
await proxy?.$modal.confirm('是否确认删除进度类别模版编号为"' + row.id + '"的数据项?');
loading.value = true;
await delProgressCategoryTemplate(row.id).finally(() => loading.value = false);
await getList();
proxy?.$modal.msgSuccess("删除成功");
}
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,479 @@
<template>
<div class="header flex justify-end">
<el-form :model="queryParams" ref="form" label-width="80px" inline class="flex items-center">
<el-form-item label="请选择项目:" prop="pid" label-width="100">
<el-select v-model="selectedProjectId" placeholder="请选择" @change="handleSelect" clearable>
<el-option v-for="item in ProjectList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="请选择方阵:" prop="pid" label-width="100">
<el-select v-model="matrixValue" placeholder="请选择" @change="handleChange" clearable>
<el-option v-for="item in matrixOptions" :key="item.id" :label="item.matrixName" :value="item.id" />
</el-select>
</el-form-item>
</el-form>
</div>
<div class="ol-map" id="olMap"></div>
<div class="aside">
<el-tree
style="max-width: 600px"
:data="progressCategoryList"
ref="treeRef"
show-checkbox
@check-change="handleCheckChange"
:props="treeProps"
:load="loadNode"
node-key="id"
lazy
@node-collapse="closeNode"
@node-expand="openNode"
/>
</div>
<div class="submit">
<el-button type="primary" size="default" @click="submit">提交</el-button>
</div>
</template>
<script lang="ts" setup>
import Map from 'ol/Map'; // OpenLayers的主要类用于创建和管理地图
import View from 'ol/View'; // OpenLayers的视图类定义地图的视图属性
import { Tile as TileLayer } from 'ol/layer'; // OpenLayers的瓦片图层类
import { XYZ } from 'ol/source'; // OpenLayers的瓦片数据源包括XYZ格式和OpenStreetMap专用的数据源
import { defaults as defaultControls, defaults, FullScreen, MousePosition, ScaleLine } from 'ol/control';
import { fromLonLat } from 'ol/proj';
import { useUserStoreHook } from '@/store/modules/user';
import { getProjectSquare, listProgressCategory, addDaily, workScheduleListPosition } from '@/api/progress/plan';
import { ProgressCategoryVO, progressPlanDetailForm } from '@/api/progress/plan/types';
import { Circle, Fill, Stroke, Style, Text } from 'ol/style';
import Feature from 'ol/Feature';
import { Point, Polygon } from 'ol/geom';
import VectorSource from 'ol/source/Vector';
import VectorLayer from 'ol/layer/Vector';
import Node from 'element-plus/es/components/tree/src/model/node.mjs';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
// 获取用户 store
const userStore = useUserStoreHook();
// 从 store 中获取项目列表和当前选中的项目
const currentProject = computed(() => userStore.selectedProject);
const ProjectList = computed(() => userStore.projects);
const selectedProjectId = ref(userStore.selectedProject?.id || '');
const treeRef = ref();
const queryParams = ref({
pid: undefined,
name: undefined,
unitType: undefined,
projectId: currentProject.value.id,
matrixId: undefined,
params: {}
});
const submitForm = ref<progressPlanDetailForm>({
id: '',
finishedDetailIdList: [] as string[]
});
const loading = ref(false);
const matrixOptions = ref([]);
const matrixValue = ref<number | undefined>(matrixOptions.value.length > 0 ? matrixOptions.value[0].id : undefined);
const progressCategoryList = ref<ProgressCategoryVO[]>([]);
const treeProps = {
children: 'children',
label: 'name',
isLeaf: 'leaf',
hasChildren: 'hasChildren' // 重要
};
//切换项目
const handleSelect = (projectId: string) => {
const selectedProject = ProjectList.value.find((p) => p.id === projectId);
if (selectedProject) {
userStore.setSelectedProject(selectedProject);
resetMatrix();
getList();
}
};
/** 进度类别树选中事件 */
const handleCheckChange = (data: any, checked: boolean, indeterminate: boolean) => {
const node: Node | undefined = treeRef.value?.getNode(data.id);
if (node && node.level === 3) {
console.log('第三级节点被选中:', data, '选中状态:', checked);
}
if (!node || node.level !== 3 || !checked) return;
const parent = node.parent;
if (!parent) return;
// 遍历兄弟节点,取消选中除当前节点之外的其他第三级节点
parent.childNodes.forEach((sibling: Node) => {
if (sibling !== node) {
treeRef.value.setChecked(sibling.data.id, false, false);
}
});
submitForm.value.id = data.id; // 设置提交表单的id
};
/** 关闭节点事件 */
const closeNode = (node: any) => {
// 清除子节点
if (node.pid) {
node.threeChildren.forEach((child: any) => {
const feature = featureMap[child.id];
if (feature && sharedSource.hasFeature(feature)) {
sharedSource.removeFeature(feature);
}
});
}
};
/** 打开节点事件 */
const openNode = (node: any) => {
// 清除子节点
if (!node.pid) return;
addPointToMap(node.threeChildren); // 添加点到地图
};
//懒加载子节点
const loadNode = async (node: any, resolve: (data: any[]) => void) => {
if (node.level !== 2) {
// 只对二级节点加载子节点
resolve(node.data.children || []);
return;
}
const secondLevelNodeId = node.data.id;
const res = await workScheduleListPosition(secondLevelNodeId); // 替换成你的 API
const children = res.data.detailList || [];
if (children.length === 0) {
proxy?.$modal.msgWarning(`节点 "${node.data.name}" 为空`);
resolve([]);
}
// 标记子节点为叶子节点
const threeLeafList = children.map((detail) => {
return {
...detail,
name: detail.date, // 设置为叶子节点
leaf: true // 标记为叶子节点
};
});
progressCategoryList.value.forEach((item, i) => {
let indexNum = item.children.findIndex((item) => item.id === secondLevelNodeId);
if (indexNum !== -1) {
item.children[indexNum].threeChildren = res.data.facilityList; // 将子节点添加到当前节点的threeChildren属性中
}
});
resolve(threeLeafList);
};
/** 提交按钮点击事件 */
const submit = () => {
console.log('sunbmitForm', submitForm.value);
addDaily(submitForm.value)
.then(() => {
proxy?.$modal.msgSuccess('提交成功');
resetTreeAndMap();
})
.catch((error) => {
proxy?.$modal.msgError(`提交失败: ${error.message}`);
});
};
//重置树形结构选中以及图层高亮
const resetTreeAndMap = () => {
// 重置树形结构选中状态
treeRef.value?.setCheckedKeys([]);
// 清除地图上的所有高亮
const scale = Math.max(map.getView().getZoom() / 10, 1); // 获取当前缩放比例
sharedSource.getFeatures().forEach((feature) => {
if (feature.get('highlighted')) {
feature.setStyle(defaultStyle(feature.get('name'), scale)); // 恢复默认样式
feature.set('highlighted', false); // 重置高亮状态
}
});
// 清空已完成列表
submitForm.value.finishedDetailIdList = [];
};
/** 方阵选择器改变事件 */
const handleChange = (value: number) => {
queryParams.value.matrixId = value;
getList();
};
//切换项目重置方阵
const resetMatrix = () => {
matrixValue.value = undefined;
queryParams.value.matrixId = undefined;
matrixOptions.value = [];
};
/** 查询进度类别列表 */
const getList = async () => {
if (!queryParams.value.matrixId) {
const res = await getProjectSquare(currentProject.value.id);
if (res.rows.length === 0) {
proxy?.$modal.msgWarning('当前项目下没有方阵,请先创建方阵');
} else {
if (!matrixValue.value) matrixValue.value = res.rows[0].id;
matrixOptions.value = res.rows;
queryParams.value.matrixId = res.rows[0].id;
}
}
loading.value = true;
queryParams.value.projectId = currentProject.value.id;
const res = await listProgressCategory(queryParams.value);
const data = proxy?.handleTree<ProgressCategoryVO>(res.data, 'id', 'pid');
if (data) {
progressCategoryList.value = data;
loading.value = false;
}
};
let map: any = null;
const layerData = reactive<any>({});
const centerPosition = ref(fromLonLat([107.12932403398425, 23.805564054229908]));
const initOLMap = () => {
// 创造地图实例
map = new Map({
// 设置地图容器的ID
target: 'olMap',
// 定义地图的图层列表,用于显示特定的地理信息。
layers: [
// 高德地图
// TileLayer表示一个瓦片图层它由一系列瓦片通常是图片组成用于在地图上显示地理数据。
new TileLayer({
// 设置图层的数据源为XYZ类型。XYZ是一个通用的瓦片图层源它允许你通过URL模板来获取瓦片
source: new XYZ({
url: 'https://webrd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}'
})
})
],
// 设置地图的视图参数
// View表示地图的视图它定义了地图的中心点、缩放级别、旋转角度等参数。
view: new View({
// fromLonLat是一个函数用于将经纬度坐标转换为地图的坐标系统。
center: centerPosition.value, //地图中心点
zoom: 15, // 缩放级别
minZoom: 0, // 最小缩放级别
// maxZoom: 18, // 最大缩放级别
constrainResolution: true // 因为存在非整数的缩放级别所以设置该参数为true来让每次缩放结束后自动缩放到距离最近的一个整数级别这个必须要设置当缩放在非整数级别时地图会糊
// projection: 'EPSG:4326' // 投影坐标系默认是3857
}),
//加载控件到地图容器中
controls: defaultControls({
zoom: false,
rotate: false,
attribution: false
})
});
map.on('click', (e: any) => {
const zoom = map.getView().getZoom();
const scale = Math.max(zoom / 10, 1); // 缩放比例,根据需要调整公式
map.forEachFeatureAtPixel(e.pixel, (feature: Feature) => {
if (feature.get('status') === '2') return; // 如果是完成状态,直接返回
const isHighlighted = feature.get('highlighted') === true;
const geomType = feature.getGeometry().getType();
if (isHighlighted) {
feature.setStyle(defaultStyle(feature.get('name'), scale)); // 清除高亮样式
feature.set('highlighted', false);
submitForm.value.finishedDetailIdList = submitForm.value.finishedDetailIdList.filter((id) => id !== feature.get('id')); // 从已完成列表中移除
return;
}
if (geomType === 'Polygon') {
feature.setStyle(highlightStyle(feature.get('name'), scale));
feature.set('highlighted', true);
submitForm.value.finishedDetailIdList.push(feature.get('id')); // 添加到已完成列表
}
});
});
map.getView().on('change:resolution', () => {
const zoom = map.getView().getZoom();
const scale = Math.max(zoom / 10, 1); // 缩放比例,根据需要调整公式
sharedSource.getFeatures().forEach((feature) => {
const style = feature.getStyle();
if (style instanceof Style && style.getText()) {
style.getText().setScale(scale);
feature.setStyle(style); // 重新应用样式
}
});
});
};
const highlightStyle = (name, scale) => {
return new Style({
stroke: new Stroke({
color: 'orange',
width: 4
}),
fill: new Fill({
color: 'rgba(255,165,0,0.3)' // 半透明橙色
}),
text: new Text({
font: '14px Microsoft YaHei',
text: name,
placement: 'line', // 👈 关键属性
offsetX: 50, // 向右偏移 10 像素
offsetY: 20, // 向下偏移 5 像素
scale,
fill: new Fill({ color: 'orange' })
})
});
};
const defaultStyle = (name, scale) => {
return new Style({
stroke: new Stroke({
color: '#003366',
width: 2
}),
text: new Text({
font: '12px Microsoft YaHei',
text: name,
scale,
placement: 'line', // 👈 关键属性
offsetX: 50, // 向右偏移 10 像素
offsetY: 20, // 向下偏移 5 像素
fill: new Fill({ color: '#003366 ' })
}),
fill: new Fill({ color: 'skyblue' })
});
};
const successStyle = (name, scale) => {
return new Style({
stroke: new Stroke({
color: '#2E7D32 ',
width: 2
}),
text: new Text({
font: '14px Microsoft YaHei',
text: name,
scale,
placement: 'line', // 👈 关键属性
offsetX: 50, // 向右偏移 10 像素
offsetY: 20, // 向下偏移 5 像素
fill: new Fill({ color: '#FFFFFF ' })
}),
fill: new Fill({ color: '#7bdd63 ' })
});
};
/**
* 创建图层
* @param {*} pointObj 坐标数组
* @param {*} type 类型
* @param {*} id 唯一id
* @param {*} name 名称
* */
// 共享 source 和图层(全局一次性创建)
const sharedSource = new VectorSource();
const sharedLayer = new VectorLayer({
source: sharedSource,
renderMode: 'image' // 提高渲染性能
} as any);
// id => Feature 映射表
const featureMap: Record<string, Feature> = {};
const creatPoint = (pointObj: Array<any>, type: string, id: string, name?: string, status?: string) => {
let geometry;
if (type === 'Point') {
geometry = new Point(fromLonLat(pointObj));
} else if (type === 'Polygon') {
const coords = pointObj.map((arr: any) => fromLonLat(arr));
geometry = new Polygon([coords]);
}
const feature = new Feature({ geometry });
const zoom = map.getView().getZoom();
const scale = Math.max(zoom / 10, 1); // 缩放比例,根据需要调整公式
const pointStyle = new Style({
image: new Circle({
radius: 2,
fill: new Fill({ color: 'red' })
}),
text: new Text({
font: '12px Microsoft YaHei',
text: name,
scale,
fill: new Fill({ color: '#7bdd63' })
})
});
const polygonStyle = status == '2' ? successStyle(name, scale) : defaultStyle(name || '', scale);
feature.setStyle(type === 'Point' ? pointStyle : polygonStyle);
feature.set('name', name || ''); // 设置名称
feature.set('status', status || ''); // 设置完成状态 2为完成 其他为未完成
feature.set('id', id || '');
// 缓存 feature用于后续判断
featureMap[id] = feature;
};
// 添加点到地图
const addPointToMap = (features: Array<any>) => {
features.forEach((item: any) => {
const fid = item.id;
// 没创建过就先创建
if (!featureMap[fid]) {
creatPoint(item.positions, item.type, fid, item.name, item.status);
}
// 添加到共享 source 中(避免重复添加)
const feature = featureMap[fid];
if (!sharedSource.hasFeature(feature)) {
sharedSource.addFeature(feature);
}
});
};
onMounted(() => {
// 地图初始化
initOLMap();
map.addLayer(sharedLayer);
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
getList();
});
</script>
<style lang="scss" scoped>
.ol-map {
height: 100vh;
width: 100%;
position: absolute;
z-index: 1;
}
.header {
height: 90px;
width: 100%;
position: absolute;
z-index: 2;
}
.aside {
position: absolute;
top: 10%;
left: 30px;
width: 300px;
height: 70vh;
background-color: rgba(255, 255, 255, 0.8);
z-index: 3;
overflow: auto;
}
.submit {
position: absolute;
bottom: 70px;
right: 70px;
z-index: 3;
}
</style>