Compare commits
13 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
18956dfa58 | |
|
|
1799d404bf | |
|
|
4ac19f78fd | |
|
|
49634c9285 | |
|
|
d607c2abcf | |
|
|
e35f1999fb | |
|
|
0f1f099144 | |
|
|
eb126006a1 | |
|
|
2be569f8eb | |
|
|
57811df6e2 | |
|
|
f46a01daf2 | |
|
|
45862052c7 | |
|
|
4e231ec401 |
|
|
@ -48,7 +48,6 @@
|
||||||
"simple-query-string": "^1.3.2",
|
"simple-query-string": "^1.3.2",
|
||||||
"solarlunar": "^2.0.7",
|
"solarlunar": "^2.0.7",
|
||||||
"store": "^2.0.12",
|
"store": "^2.0.12",
|
||||||
"three": "^0.153.0",
|
|
||||||
"uuid": "^3.3.2"
|
"uuid": "^3.3.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|
@ -59,7 +58,6 @@
|
||||||
"@types/react": "^17.0.2",
|
"@types/react": "^17.0.2",
|
||||||
"@types/react-dom": "^17.0.2",
|
"@types/react-dom": "^17.0.2",
|
||||||
"@types/store": "^2.0.5",
|
"@types/store": "^2.0.5",
|
||||||
"@types/three": "^0.152.1",
|
|
||||||
"@umijs/plugin-access": "2.4.2",
|
"@umijs/plugin-access": "2.4.2",
|
||||||
"@umijs/plugin-dva": "^0.13.0",
|
"@umijs/plugin-dva": "^0.13.0",
|
||||||
"@umijs/plugin-initial-state": "^2.4.0",
|
"@umijs/plugin-initial-state": "^2.4.0",
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,30 @@
|
||||||
<div class="content unicode" style="display: block;">
|
<div class="content unicode" style="display: block;">
|
||||||
<ul class="icon_lists dib-box">
|
<ul class="icon_lists dib-box">
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">标记-copy</div>
|
||||||
|
<div class="code-name">&#xe78c;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">标记</div>
|
||||||
|
<div class="code-name">&#xe604;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">取消标记</div>
|
||||||
|
<div class="code-name">&#xe710;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">双击</div>
|
||||||
|
<div class="code-name">&#xe6f0;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li class="dib">
|
<li class="dib">
|
||||||
<span class="icon iconfont"></span>
|
<span class="icon iconfont"></span>
|
||||||
<div class="name">export</div>
|
<div class="name">export</div>
|
||||||
|
|
@ -204,9 +228,9 @@
|
||||||
<pre><code class="language-css"
|
<pre><code class="language-css"
|
||||||
>@font-face {
|
>@font-face {
|
||||||
font-family: 'iconfont';
|
font-family: 'iconfont';
|
||||||
src: url('iconfont.woff2?t=1741245710478') format('woff2'),
|
src: url('iconfont.woff2?t=1750298885691') format('woff2'),
|
||||||
url('iconfont.woff?t=1741245710478') format('woff'),
|
url('iconfont.woff?t=1750298885691') format('woff'),
|
||||||
url('iconfont.ttf?t=1741245710478') format('truetype');
|
url('iconfont.ttf?t=1750298885691') format('truetype');
|
||||||
}
|
}
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
||||||
|
|
@ -232,6 +256,42 @@
|
||||||
<div class="content font-class">
|
<div class="content font-class">
|
||||||
<ul class="icon_lists dib-box">
|
<ul class="icon_lists dib-box">
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont icon-biaoji-copy"></span>
|
||||||
|
<div class="name">
|
||||||
|
标记-copy
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.icon-biaoji-copy
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont icon-biaoji"></span>
|
||||||
|
<div class="name">
|
||||||
|
标记
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.icon-biaoji
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont icon-quxiaobiaoji"></span>
|
||||||
|
<div class="name">
|
||||||
|
取消标记
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.icon-quxiaobiaoji
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont icon-shuangji"></span>
|
||||||
|
<div class="name">
|
||||||
|
双击
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.icon-shuangji
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li class="dib">
|
<li class="dib">
|
||||||
<span class="icon iconfont icon-export"></span>
|
<span class="icon iconfont icon-export"></span>
|
||||||
<div class="name">
|
<div class="name">
|
||||||
|
|
@ -457,6 +517,38 @@
|
||||||
<div class="content symbol">
|
<div class="content symbol">
|
||||||
<ul class="icon_lists dib-box">
|
<ul class="icon_lists dib-box">
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#icon-biaoji-copy"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">标记-copy</div>
|
||||||
|
<div class="code-name">#icon-biaoji-copy</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#icon-biaoji"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">标记</div>
|
||||||
|
<div class="code-name">#icon-biaoji</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#icon-quxiaobiaoji"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">取消标记</div>
|
||||||
|
<div class="code-name">#icon-quxiaobiaoji</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#icon-shuangji"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">双击</div>
|
||||||
|
<div class="code-name">#icon-shuangji</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li class="dib">
|
<li class="dib">
|
||||||
<svg class="icon svg-icon" aria-hidden="true">
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
<use xlink:href="#icon-export"></use>
|
<use xlink:href="#icon-export"></use>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "iconfont"; /* Project id 4257468 */
|
font-family: "iconfont"; /* Project id 4257468 */
|
||||||
src: url('iconfont.woff2?t=1741245710478') format('woff2'),
|
src: url('iconfont.woff2?t=1750298885691') format('woff2'),
|
||||||
url('iconfont.woff?t=1741245710478') format('woff'),
|
url('iconfont.woff?t=1750298885691') format('woff'),
|
||||||
url('iconfont.ttf?t=1741245710478') format('truetype');
|
url('iconfont.ttf?t=1750298885691') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
|
|
@ -13,6 +13,22 @@
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-biaoji-copy:before {
|
||||||
|
content: "\e78c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-biaoji:before {
|
||||||
|
content: "\e604";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-quxiaobiaoji:before {
|
||||||
|
content: "\e710";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-shuangji:before {
|
||||||
|
content: "\e6f0";
|
||||||
|
}
|
||||||
|
|
||||||
.icon-export:before {
|
.icon-export:before {
|
||||||
content: "\e61e";
|
content: "\e61e";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,34 @@
|
||||||
"css_prefix_text": "icon-",
|
"css_prefix_text": "icon-",
|
||||||
"description": "",
|
"description": "",
|
||||||
"glyphs": [
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"icon_id": "44653522",
|
||||||
|
"name": "标记-copy",
|
||||||
|
"font_class": "biaoji-copy",
|
||||||
|
"unicode": "e78c",
|
||||||
|
"unicode_decimal": 59276
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "14559255",
|
||||||
|
"name": "标记",
|
||||||
|
"font_class": "biaoji",
|
||||||
|
"unicode": "e604",
|
||||||
|
"unicode_decimal": 58884
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "33872224",
|
||||||
|
"name": "取消标记",
|
||||||
|
"font_class": "quxiaobiaoji",
|
||||||
|
"unicode": "e710",
|
||||||
|
"unicode_decimal": 59152
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39084114",
|
||||||
|
"name": "双击",
|
||||||
|
"font_class": "shuangji",
|
||||||
|
"unicode": "e6f0",
|
||||||
|
"unicode_decimal": 59120
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"icon_id": "10124054",
|
"icon_id": "10124054",
|
||||||
"name": "export",
|
"name": "export",
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 223 KiB |
|
Before Width: | Height: | Size: 361 KiB |
|
Before Width: | Height: | Size: 398 KiB |
|
Before Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 405 B |
|
|
@ -23,10 +23,7 @@ class CalculateService extends BasicService {
|
||||||
delete queryParams[key];
|
delete queryParams[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.post(
|
return this.post(url, queryParams);
|
||||||
url,
|
|
||||||
queryParams
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
//获取系统配置项
|
//获取系统配置项
|
||||||
getSysconfcode = async ({ code }: any) => {
|
getSysconfcode = async ({ code }: any) => {
|
||||||
|
|
@ -73,7 +70,7 @@ class CalculateService extends BasicService {
|
||||||
return this.post(`/api/bs/hrmsalary/salaryacct/acctresult/sum`, queryParams);
|
return this.post(`/api/bs/hrmsalary/salaryacct/acctresult/sum`, queryParams);
|
||||||
};
|
};
|
||||||
//合计行
|
//合计行
|
||||||
getAcctResultsum = async (url: string, params: any) => (this.post(url, params));
|
getAcctResultsum = async (url: string, params: any) => this.post(url, params);
|
||||||
//社保合计行
|
//社保合计行
|
||||||
getSyMixSum = async (params: any) => {
|
getSyMixSum = async (params: any) => {
|
||||||
return this.post(`/api/bs/hrmsalary/siaccount/detail/list/syMixSum`, params);
|
return this.post(`/api/bs/hrmsalary/siaccount/detail/list/syMixSum`, params);
|
||||||
|
|
@ -86,8 +83,12 @@ class CalculateService extends BasicService {
|
||||||
getAcctresultSum = async (params: any) => {
|
getAcctresultSum = async (params: any) => {
|
||||||
return this.post(`/api/bs/hrmsalary/salaryacct/acctresult/sjjtReportSum`, params);
|
return this.post(`/api/bs/hrmsalary/salaryacct/acctresult/sjjtReportSum`, params);
|
||||||
};
|
};
|
||||||
|
//工资单查询
|
||||||
|
getMySalaryBill = async (salaryInfoId: string) => {
|
||||||
|
return this.get(`/api/bs/hrmsalary/salaryBill/mySalaryBill?salaryInfoId=${salaryInfoId}`);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const calculateService = new CalculateService();
|
const calculateService = new CalculateService();
|
||||||
|
|
||||||
export default calculateService;
|
export default calculateService;
|
||||||
|
|
@ -23,6 +23,7 @@ module.exports = {
|
||||||
"/unitTable.*": "blank",
|
"/unitTable.*": "blank",
|
||||||
"/aliTable.*": "blank",
|
"/aliTable.*": "blank",
|
||||||
"/custom-project.*": "blank",
|
"/custom-project.*": "blank",
|
||||||
|
"/MPayroll.*": "blank",
|
||||||
"/hiprintDesign.*": "hiPrint",
|
"/hiprintDesign.*": "hiPrint",
|
||||||
"/manage.*": "manage",
|
"/manage.*": "manage",
|
||||||
"/portal.*": "template",
|
"/portal.*": "template",
|
||||||
|
|
@ -36,4 +37,4 @@ module.exports = {
|
||||||
"/500": "blank",
|
"/500": "blank",
|
||||||
"/": "template"
|
"/": "template"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
const AMAP_KEY = "2cc032a62120824fd454d1f35789ef32"; //https://console.amap.com/dev/key/app获取高德地图开发key
|
|
||||||
const pluginsList = [
|
|
||||||
"AMap.PolyEditor",
|
|
||||||
"AMap.CustomLayer",
|
|
||||||
"AMap.ControlBar",
|
|
||||||
"AMap.Heatmap",
|
|
||||||
"Map3D",
|
|
||||||
"AMap.GLCustomLayer",
|
|
||||||
"AMap.Buildings",
|
|
||||||
"AMap.Size",
|
|
||||||
"AMap.LngLat",
|
|
||||||
"AMap.3DTilesLayer",
|
|
||||||
"AMap.PolyEditor",
|
|
||||||
"AMap.PolylineEditor"
|
|
||||||
];
|
|
||||||
export default class AmapViewer {
|
|
||||||
public id: string;
|
|
||||||
public center: number[];
|
|
||||||
public amapDom!: HTMLElement;
|
|
||||||
public map!: any;
|
|
||||||
public AMap!: any;
|
|
||||||
public normalMarker!: any;
|
|
||||||
|
|
||||||
constructor(id: string, center: number[]) {
|
|
||||||
this.id = id;
|
|
||||||
this.center = center;
|
|
||||||
this.initAmap();
|
|
||||||
}
|
|
||||||
|
|
||||||
private initAmap() {
|
|
||||||
this.initAmapFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
private initAmapFile() {
|
|
||||||
// && (window as any).Loca
|
|
||||||
if ((window as any).AMap) {
|
|
||||||
this.initMap();
|
|
||||||
} else {
|
|
||||||
const url = `https://webapi.amap.com/maps?v=2.0&key=${AMAP_KEY}`;
|
|
||||||
const jsapi = document.createElement("script");
|
|
||||||
jsapi.charset = "utf-8";
|
|
||||||
jsapi.src = url;
|
|
||||||
document.head.appendChild(jsapi);
|
|
||||||
jsapi.onload = () => {
|
|
||||||
this.initMap();
|
|
||||||
};
|
|
||||||
jsapi.onerror = function () {
|
|
||||||
console.log(new Error("地图API文件加载失败"));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private initMap() {
|
|
||||||
// 获取画布dom
|
|
||||||
this.amapDom = document.getElementById(this.id) as HTMLElement;
|
|
||||||
this.map = new AMap.Map(this.amapDom, {
|
|
||||||
zoom: 14,
|
|
||||||
center: this.center,
|
|
||||||
resizeEnable: true,
|
|
||||||
viewMode: "3D",
|
|
||||||
defaultCursor: "default",
|
|
||||||
pitch: 42.0,
|
|
||||||
mapStyle: "amap://styles/light" || "amap://styles/grey",
|
|
||||||
expandZoomRange: true,
|
|
||||||
rotation: 4.9,
|
|
||||||
skyColor: "#c8edff",
|
|
||||||
showBuildingBlock: false, // 不显示默认建筑物
|
|
||||||
features: ["bg", "road", "point"], // 不显示默认建筑物
|
|
||||||
mask: null
|
|
||||||
});
|
|
||||||
// 添加卫星地图
|
|
||||||
const satelliteLayer = new AMap.TileLayer.Satellite();
|
|
||||||
this.map.add([satelliteLayer]);
|
|
||||||
|
|
||||||
this.map.on("click", (e: any) => {
|
|
||||||
const { lng, lat } = e.lnglat;
|
|
||||||
console.log(e);
|
|
||||||
const marker = new AMap.Marker({
|
|
||||||
map: this.map,
|
|
||||||
position: [lng, lat],
|
|
||||||
label: {
|
|
||||||
content: `[${lng},${lat}]`,
|
|
||||||
direction: "top"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (this.normalMarker) this.map.remove(this.normalMarker);
|
|
||||||
});
|
|
||||||
// 图层设置
|
|
||||||
this.normalMarker = new AMap.Marker({ offset: [70, -15], zooms: [1, 22] });
|
|
||||||
}
|
|
||||||
|
|
||||||
public destoryMap() {
|
|
||||||
if (this.map) {
|
|
||||||
this.map.clearMap();
|
|
||||||
this.map.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
import { AxesHelper, Camera, PerspectiveCamera, Scene, WebGLRenderer } from "three";
|
|
||||||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
|
|
||||||
|
|
||||||
export type Animate = {
|
|
||||||
fun: (arg: any) => any;
|
|
||||||
content: any;
|
|
||||||
};
|
|
||||||
export default class Viewer {
|
|
||||||
public id: string;
|
|
||||||
public viewerDom!: HTMLElement;
|
|
||||||
public renderer!: WebGLRenderer;
|
|
||||||
public scene!: Scene;
|
|
||||||
public camera!: PerspectiveCamera;
|
|
||||||
public controls!: OrbitControls;
|
|
||||||
public isDestroy = false; // 是否销毁
|
|
||||||
public animateEventList = new Map();
|
|
||||||
|
|
||||||
constructor(id: string) {
|
|
||||||
this.id = id;
|
|
||||||
this.initViewer();
|
|
||||||
}
|
|
||||||
|
|
||||||
private initViewer() {
|
|
||||||
this.initRender();
|
|
||||||
this.initScene();
|
|
||||||
this.initCamera();
|
|
||||||
this.initControl();
|
|
||||||
|
|
||||||
const animate = () => {
|
|
||||||
if (this.isDestroy) return;
|
|
||||||
requestAnimationFrame(animate);
|
|
||||||
this.updateDom();
|
|
||||||
this.renderDom();
|
|
||||||
|
|
||||||
// 全局的公共动画函数,添加函数可同步执行
|
|
||||||
this.animateEventList.forEach((event) => {
|
|
||||||
if (event.fun && event.content) event.fun(event.content);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
animate();
|
|
||||||
}
|
|
||||||
|
|
||||||
private initRender() {
|
|
||||||
// 获取画布dom
|
|
||||||
this.viewerDom = document.getElementById(this.id) as HTMLElement;
|
|
||||||
this.renderer = new WebGLRenderer({ antialias: true });
|
|
||||||
|
|
||||||
this.viewerDom.appendChild(this.renderer.domElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建场景
|
|
||||||
private initScene() {
|
|
||||||
this.scene = new Scene();
|
|
||||||
}
|
|
||||||
|
|
||||||
private initControl() {
|
|
||||||
this.controls = new OrbitControls(this.camera as Camera, this.renderer?.domElement);
|
|
||||||
|
|
||||||
this.controls.enableDamping = false;
|
|
||||||
this.controls.screenSpacePanning = false;
|
|
||||||
this.controls.minDistance = 2;
|
|
||||||
this.controls.maxDistance = 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化相机
|
|
||||||
public initCamera() {
|
|
||||||
this.camera = new PerspectiveCamera(75, this.viewerDom.clientWidth / this.viewerDom.clientHeight, 0.1, 1000);
|
|
||||||
this.camera.position.set(5, 5, 10);
|
|
||||||
this.camera.lookAt(0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
//创建坐标轴辅助对象
|
|
||||||
public addAxis() {
|
|
||||||
const axis = new AxesHelper(1000);
|
|
||||||
this.scene?.add(axis);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加动画事件
|
|
||||||
public addAnimate(id: string, animate: Animate) {
|
|
||||||
this.animateEventList.set(id, animate);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据传入的id,删除动画事件列表中的对应事件
|
|
||||||
public removeAnimate(id: string) {
|
|
||||||
this.animateEventList?.delete(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新参数
|
|
||||||
public updateDom() {
|
|
||||||
this.camera.aspect = this.viewerDom.clientWidth / this.viewerDom.clientHeight; // 摄像机视锥体的长宽比,通常是使用画布的宽/画布的高
|
|
||||||
this.camera.updateProjectionMatrix(); // 更新摄像机投影矩阵。在任何参数被改变以后必须被调用,来使得这些改变生效
|
|
||||||
this.renderer.setSize(this.viewerDom.clientWidth, this.viewerDom.clientHeight);
|
|
||||||
this.renderer.setPixelRatio(window.devicePixelRatio); // 设置设备像素比
|
|
||||||
}
|
|
||||||
|
|
||||||
// 渲染dom
|
|
||||||
public renderDom() {
|
|
||||||
this.renderer?.render(this.scene as Scene, this.camera as Camera);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
.pbmc_body {
|
||||||
|
height: calc(100% - 50px);
|
||||||
|
background: #f6f6f6;
|
||||||
|
overflow: auto;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
.weapp-salary-payroll-mobile-preview {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.bill-container {
|
||||||
|
background: #f6f6f6;
|
||||||
|
|
||||||
|
.space {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
.ant-btn {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bill-info-header {
|
||||||
|
padding-top: 16px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
padding: 0 16px;
|
||||||
|
font-size: 19px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time {
|
||||||
|
padding: 0 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img {
|
||||||
|
margin-top: 20px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.top {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.corporate-culture-text {
|
||||||
|
text-align: left;
|
||||||
|
color: #111;
|
||||||
|
padding: 8px 16px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.salary-detail-table-container {
|
||||||
|
padding-top: 8px;
|
||||||
|
|
||||||
|
.salary-group {
|
||||||
|
border-top: 1px solid #f2f2f2;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
.group-title {
|
||||||
|
background: #fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 45px;
|
||||||
|
padding: 0 16px;
|
||||||
|
border-bottom: 1px solid #f2f2f2;
|
||||||
|
font-weight: bolder;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-list {
|
||||||
|
.list-item {
|
||||||
|
display: flex;
|
||||||
|
min-height: 45px;
|
||||||
|
border-bottom: 1px solid #f2f2f2;
|
||||||
|
width: 100%;
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
padding: 8px 16px;
|
||||||
|
width: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: #fbfbfb;
|
||||||
|
border-right: 1px solid #f2f2f2;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-count {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pbmc_footer {
|
||||||
|
position: relative;
|
||||||
|
height: 50px;
|
||||||
|
|
||||||
|
.pbmcf_indicator {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 16px;
|
||||||
|
left: 50%;
|
||||||
|
-webkit-transform: translateX(-50%);
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 110px;
|
||||||
|
height: 4px;
|
||||||
|
background-color: #ebebeb;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
import styles from "./index.less";
|
||||||
|
import { Util } from "@/utils";
|
||||||
|
import cs from "classnames";
|
||||||
|
import API from "@/api";
|
||||||
|
|
||||||
|
interface OwnProps {}
|
||||||
|
|
||||||
|
type Props = OwnProps;
|
||||||
|
|
||||||
|
const MPayroll: React.FC<Props> = (props) => {
|
||||||
|
const [mySalaryBillData, setMySalaryBillData] = useState<any>({});
|
||||||
|
useEffect(() => {
|
||||||
|
getMySalaryBill();
|
||||||
|
return () => {};
|
||||||
|
}, []);
|
||||||
|
const getMySalaryBill = useCallback(() => {
|
||||||
|
API.CalculateService.getMySalaryBill(Util.getUrlData("salaryInfoId")).then(({ data: res }) => {
|
||||||
|
const { data, status } = res || {};
|
||||||
|
if (status) setMySalaryBillData(data);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
const dealTemplate = (itemTypeList: any, type: string) => {
|
||||||
|
let cloneItemTypeList = _.cloneDeep(itemTypeList);
|
||||||
|
let showData: any = [],
|
||||||
|
onlyOneGrup = false;
|
||||||
|
cloneItemTypeList.forEach((group: any) => {
|
||||||
|
const { items, groupName, groupId } = group;
|
||||||
|
if (items.length !== 0) {
|
||||||
|
items.forEach((item: any) => {
|
||||||
|
item.salaryItemValue = !_.isNil(item.salaryItemValue) ? item.salaryItemValue : "";
|
||||||
|
});
|
||||||
|
if (items.length % 2 && type === "pc") items.push({});
|
||||||
|
showData.push({ groupId, groupName, items });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (cloneItemTypeList.length === 1) {
|
||||||
|
onlyOneGrup = true;
|
||||||
|
cloneItemTypeList[0].name = "";
|
||||||
|
}
|
||||||
|
return { onlyOneGrup, showData };
|
||||||
|
};
|
||||||
|
const salaryTemplate = useMemo(() => {
|
||||||
|
return !_.isEmpty(mySalaryBillData) ? mySalaryBillData?.salaryTemplate : {};
|
||||||
|
}, [mySalaryBillData]);
|
||||||
|
const itemTypeList = useMemo(() => {
|
||||||
|
return !_.isEmpty(mySalaryBillData) ? [mySalaryBillData?.employeeInformation, ...mySalaryBillData?.salaryGroups] : [];
|
||||||
|
}, [mySalaryBillData]);
|
||||||
|
const payrollData: any = useMemo(() => {
|
||||||
|
return !_.isEmpty(itemTypeList)
|
||||||
|
? dealTemplate(
|
||||||
|
_.filter(itemTypeList, (o) => !!o),
|
||||||
|
"mobile"
|
||||||
|
)
|
||||||
|
: {};
|
||||||
|
}, [mySalaryBillData, itemTypeList]);
|
||||||
|
console.log(payrollData);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.pbmc_body}>
|
||||||
|
<div className={styles["weapp-salary-payroll-mobile-preview"]}>
|
||||||
|
<div className={styles["bill-container"]}>
|
||||||
|
<div className={styles["bill-info-header"]}>
|
||||||
|
<div className={styles["title"]}>{salaryTemplate?.theme || ""}</div>
|
||||||
|
</div>
|
||||||
|
{!payrollData?.onlyOneGrup && salaryTemplate?.textContentPosition == 1 && salaryTemplate.textContent && (
|
||||||
|
<div className={cs(styles["corporate-culture-text"], styles["top"])} title={salaryTemplate.textContent} dangerouslySetInnerHTML={{ __html: salaryTemplate.textContent }} />
|
||||||
|
)}
|
||||||
|
<div className={styles["salary-detail-table-container"]}>
|
||||||
|
{!_.isEmpty(payrollData?.showData) &&
|
||||||
|
payrollData?.showData.map((groupItem: any, index: number) => {
|
||||||
|
if (!groupItem) return null;
|
||||||
|
const { groupId, groupName, items = [] } = groupItem;
|
||||||
|
return (
|
||||||
|
<div className={styles["salary-group"]} key={groupId || index}>
|
||||||
|
{groupName ? <div className={styles["group-title"]}>{groupName}</div> : null}
|
||||||
|
<div className={styles["group-list"]}>
|
||||||
|
{payrollData?.onlyOneGrup && salaryTemplate?.textContentPosition == "1" && salaryTemplate.textContent && (
|
||||||
|
<div className={cs(styles["list-item"], styles["top"])}>
|
||||||
|
<div>发放说明</div>
|
||||||
|
<div>{salaryTemplate.textContent}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{items.map((templatItem: any, index: number) => {
|
||||||
|
const { salaryItemValue, name, salaryItemShowName } = templatItem || {};
|
||||||
|
return (
|
||||||
|
<div className={styles["list-item"]} key={index}>
|
||||||
|
<div className={styles["item-name"]} title={salaryItemShowName || name}>
|
||||||
|
<span>{salaryItemShowName || name || ""}</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles["item-count"]}>{salaryItemValue}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{payrollData?.onlyOneGrup && salaryTemplate?.textContentPosition == "2" && salaryTemplate.textContent && (
|
||||||
|
<div className={styles["list-item"]}>
|
||||||
|
<div>发放说明</div>
|
||||||
|
<div>{salaryTemplate.textContent}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
{!payrollData?.onlyOneGrup && salaryTemplate?.textContentPosition == 2 && salaryTemplate.textContent && (
|
||||||
|
<div className={cs(styles["corporate-culture-text"], styles["footer"])} title={salaryTemplate.textContent} dangerouslySetInnerHTML={{ __html: salaryTemplate.textContent }} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.pbmc_footer}>
|
||||||
|
<div className={styles.pbmcf_indicator}></div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MPayroll;
|
||||||
|
|
@ -65,10 +65,10 @@ const OCTable: FC = (props) => {
|
||||||
<span>{excelResultValue}</span>
|
<span>{excelResultValue}</span>
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
showDifference &&
|
showDifference && !!parseInt(calculateDifference(acctResultValue, excelResultValue || 0)) &&
|
||||||
<div className={cs(styles["danger"], styles["comparison-single-row"])}>
|
<div className={cs(styles["danger"], styles["comparison-single-row"])}>
|
||||||
<span>{lanObj["差值"]}:</span>
|
<span>{lanObj["差值"]}:</span>
|
||||||
<span>{calculateDifference(acctResultValue, excelResultValue)}</span>
|
<span>{calculateDifference(acctResultValue, excelResultValue || 0)}</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>;
|
</div>;
|
||||||
|
|
@ -104,7 +104,7 @@ const OCTable: FC = (props) => {
|
||||||
return <Table
|
return <Table
|
||||||
rowKey="id" className={styles.tableWrapper}
|
rowKey="id" className={styles.tableWrapper}
|
||||||
columns={columns} dataSource={dataSource} bordered size="small"
|
columns={columns} dataSource={dataSource} bordered size="small"
|
||||||
scroll={{ x: 1200, y: `calc(100vh - 137px)` }}
|
scroll={{ x: 1200, y: `calc(100vh - 165px)` }}
|
||||||
pagination={{
|
pagination={{
|
||||||
...paginationFun(pageInfo, sizeChange, onChange, i18n),
|
...paginationFun(pageInfo, sizeChange, onChange, i18n),
|
||||||
size: "default"
|
size: "default"
|
||||||
|
|
|
||||||
|
|
@ -198,7 +198,7 @@ const index: FunctionComponent<Props> = (props) => {
|
||||||
return (
|
return (
|
||||||
<Dropdown menu={{ items: !item.calcDetail || item.rightClickType ? items : [] }} trigger={["contextMenu"]} overlayClassName={styles.contextMenu} destroyPopupOnHide>
|
<Dropdown menu={{ items: !item.calcDetail || item.rightClickType ? items : [] }} trigger={["contextMenu"]} overlayClassName={styles.contextMenu} destroyPopupOnHide>
|
||||||
<span className={styles.contentSpan}>
|
<span className={styles.contentSpan}>
|
||||||
<span title={text} className={styles.contentTitle} style={{ color: `${record?.[item.dataIndex + "_color"]}` }}>
|
<span title={text} className={styles.contentTitle} style={record?.[item.dataIndex + "_color"] ? { color: `${record?.[item.dataIndex + "_color"]}` } : {}}>
|
||||||
{text}
|
{text}
|
||||||
</span>
|
</span>
|
||||||
{record.lockItems && record.lockItems.includes(item.dataIndex) ? <LockOutlined title={item.i18n["锁定的项目值"]} /> : null}
|
{record.lockItems && record.lockItems.includes(item.dataIndex) ? <LockOutlined title={item.i18n["锁定的项目值"]} /> : null}
|
||||||
|
|
@ -240,8 +240,8 @@ const index: FunctionComponent<Props> = (props) => {
|
||||||
};
|
};
|
||||||
const rowSelection = {
|
const rowSelection = {
|
||||||
columnWidth: 50,
|
columnWidth: 50,
|
||||||
columnTitle: isDetailTable ? "序号" : "",
|
columnTitle: isDetailTable && _.isNil(showSee) ? "序号" : "",
|
||||||
renderCell: (value: boolean, record: any, index: number, originNode: React.ReactNode) => (isDetailTable ? <span>{index + 1}</span> : originNode),
|
renderCell: (value: boolean, record: any, index: number, originNode: React.ReactNode) => (isDetailTable && _.isNil(showSee) ? <span>{index + 1}</span> : originNode),
|
||||||
selectedRowKeys,
|
selectedRowKeys,
|
||||||
preserveSelectedRowKeys: true,
|
preserveSelectedRowKeys: true,
|
||||||
onChange: (rowKeys: React.Key[]) => {
|
onChange: (rowKeys: React.Key[]) => {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
.block {
|
||||||
|
height: 300px;
|
||||||
|
border: 3px solid #ccc;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: white;
|
||||||
|
padding: 24px;
|
||||||
|
line-height: 32px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lazyloadImg{
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
&>li{
|
||||||
|
width: 400px;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
img{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
.threetemplate_container {
|
|
||||||
width: 100%;
|
|
||||||
height: 100vh;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global {
|
|
||||||
#TEMPLATE_CONTAINER {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
import React, { useEffect, useRef } from "react";
|
|
||||||
import * as THREE from "three";
|
|
||||||
import Viewer from "@/lib/three/Viewer";
|
|
||||||
import BoxGeometry from "./modules/BoxGeometry";
|
|
||||||
import styles from "./index.less";
|
|
||||||
|
|
||||||
interface OwnProps {}
|
|
||||||
|
|
||||||
type Props = OwnProps;
|
|
||||||
|
|
||||||
const PAGE_ID = "TEMPLATE_CONTAINER";
|
|
||||||
const Template: React.FC<Props> = (props) => {
|
|
||||||
const viewerRef = useRef<Viewer>();
|
|
||||||
let boxGeometry: BoxGeometry;
|
|
||||||
useEffect(() => {
|
|
||||||
init();
|
|
||||||
return () => {};
|
|
||||||
}, []);
|
|
||||||
const init = () => {
|
|
||||||
viewerRef.current = new Viewer(PAGE_ID);
|
|
||||||
const viewer = viewerRef.current;
|
|
||||||
const clock = new THREE.Clock();
|
|
||||||
|
|
||||||
viewer.addAxis();
|
|
||||||
boxGeometry = new BoxGeometry(viewer);
|
|
||||||
// boxGeometry.addBoxGeometry();
|
|
||||||
const fnOnj = {
|
|
||||||
fun: () => {
|
|
||||||
const elapsedTime = clock.getElapsedTime();
|
|
||||||
boxGeometry.cube.rotation.y = elapsedTime * Math.PI;
|
|
||||||
},
|
|
||||||
content: viewer
|
|
||||||
};
|
|
||||||
viewer?.addAnimate("cubeCraze", fnOnj);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.threetemplate_container}>
|
|
||||||
<div id={PAGE_ID} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Template;
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
import type Viewer from "@/lib/three/Viewer";
|
|
||||||
import * as THREE from "three";
|
|
||||||
import { AmbientLight, DirectionalLight, Mesh } from "three";
|
|
||||||
|
|
||||||
// 添加立方体
|
|
||||||
export default class BoxGeometry {
|
|
||||||
protected viewer: Viewer;
|
|
||||||
public cube!: Mesh;
|
|
||||||
public directionalLight!: DirectionalLight;
|
|
||||||
|
|
||||||
constructor(viewer: Viewer) {
|
|
||||||
this.viewer = viewer;
|
|
||||||
this.addBoxGeometry();
|
|
||||||
this.initLight();
|
|
||||||
this.initPlane();
|
|
||||||
}
|
|
||||||
|
|
||||||
private initLight() {
|
|
||||||
const material = new THREE.MeshStandardMaterial({ color: 0xff0000 });
|
|
||||||
// 添加环境光
|
|
||||||
const ambientLight = new AmbientLight(0xffffff, 0.4);
|
|
||||||
this.viewer.scene.add(ambientLight);
|
|
||||||
|
|
||||||
this.directionalLight = new THREE.DirectionalLight(0xffffff, 1);
|
|
||||||
this.directionalLight.position.set(10, 0, 10);
|
|
||||||
this.viewer.scene.add(this.directionalLight);
|
|
||||||
}
|
|
||||||
|
|
||||||
private initPlane() {
|
|
||||||
this.viewer.renderer.shadowMap.enabled = true; // 1. 渲染器能够渲染阴影效果
|
|
||||||
this.directionalLight.castShadow = true; // 2. 该方向会投射阴影效果
|
|
||||||
this.cube.castShadow = true;
|
|
||||||
|
|
||||||
const planeGeometry = new THREE.PlaneGeometry(20, 20);
|
|
||||||
const planeMaterial = new THREE.MeshStandardMaterial({ color: 0xffffff });
|
|
||||||
const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
|
|
||||||
planeMesh.rotation.x = -0.5 * Math.PI;
|
|
||||||
planeMesh.position.set(0, -3, 0);
|
|
||||||
planeMesh.receiveShadow = true;
|
|
||||||
this.viewer.scene.add(planeMesh);
|
|
||||||
|
|
||||||
const directionalLightHelper = new THREE.DirectionalLightHelper(this.directionalLight);
|
|
||||||
this.viewer.scene.add(directionalLightHelper);
|
|
||||||
}
|
|
||||||
|
|
||||||
public addBoxGeometry() {
|
|
||||||
// 创建长宽高都为4的立方几何体;
|
|
||||||
// 立方体的表明颜色为红色的材质;
|
|
||||||
// 将集合体和材质组合为Mesh对象;
|
|
||||||
// 将Mesh对象添加到场景中。
|
|
||||||
const geometry = new THREE.BoxGeometry(4, 4, 4);
|
|
||||||
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
|
|
||||||
this.cube = new THREE.Mesh(geometry, material);
|
|
||||||
|
|
||||||
this.viewer.scene.add(this.cube);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
.amap_container {
|
|
||||||
width: 100%;
|
|
||||||
height: 100vh;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global {
|
|
||||||
#DEMO_THREEAMAP {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
|
||||||
import AmapViewer from "@/lib/AmapViewer";
|
|
||||||
import ShadowMaterialAmap from "./modules/shadow";
|
|
||||||
import styles from "./index.less";
|
|
||||||
|
|
||||||
interface OwnProps {}
|
|
||||||
|
|
||||||
type Props = OwnProps;
|
|
||||||
|
|
||||||
const PAGE_ID = "DEMO_THREEAMAP",
|
|
||||||
CENTER = [118.794694, 32.434165];
|
|
||||||
const ThreeAmap: React.FC<Props> = (props) => {
|
|
||||||
const [isAMapLoaded, setIsAMapLoaded] = useState(false); //判断是否加载完成AMap对象
|
|
||||||
const amapRef = useRef<AmapViewer>();
|
|
||||||
useEffect(() => {
|
|
||||||
init();
|
|
||||||
return () => amapRef.current?.destoryMap();
|
|
||||||
}, []);
|
|
||||||
useEffect(() => {
|
|
||||||
const checkAMapLoaded = () => {
|
|
||||||
if (window.AMap && !isAMapLoaded) {
|
|
||||||
setIsAMapLoaded(true);
|
|
||||||
initLayer();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
checkAMapLoaded();
|
|
||||||
if (!isAMapLoaded) window.addEventListener("load", checkAMapLoaded);
|
|
||||||
return () => window.removeEventListener("load", checkAMapLoaded);
|
|
||||||
}, [isAMapLoaded]);
|
|
||||||
const init = () => {
|
|
||||||
amapRef.current = new AmapViewer(PAGE_ID, CENTER);
|
|
||||||
};
|
|
||||||
const initLayer = () => {
|
|
||||||
const amap = amapRef.current;
|
|
||||||
const shadowMaterialAmap = new ShadowMaterialAmap(amapRef.current as AmapViewer);
|
|
||||||
shadowMaterialAmap.CreateGLlayer();
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<div className={styles.amap_container}>
|
|
||||||
<div id={PAGE_ID} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ThreeAmap;
|
|
||||||
|
|
@ -1,169 +0,0 @@
|
||||||
import type AmapViewer from "src/lib/AmapViewer";
|
|
||||||
import * as THREE from "three";
|
|
||||||
import { Camera, MeshPhongMaterial, PerspectiveCamera, Scene, WebGLRenderer } from "three";
|
|
||||||
|
|
||||||
const guiCtrl = {
|
|
||||||
lightPositionX: 10000,
|
|
||||||
lightPositionY: 5000,
|
|
||||||
lightPositionZ: 5000,
|
|
||||||
cameraNear: 0,
|
|
||||||
caremaFar: 20000,
|
|
||||||
cameraLeft: -5000,
|
|
||||||
cameraRight: 5000,
|
|
||||||
cameraTop: 5000,
|
|
||||||
cameraBottom: -5000,
|
|
||||||
mapSize: 512,
|
|
||||||
planeMaterialOpacity: 0.5
|
|
||||||
};
|
|
||||||
export default class ShadowMaterialAmap {
|
|
||||||
protected Amap!: any;
|
|
||||||
public locationDataSource!: any;
|
|
||||||
public meshes!: any[];
|
|
||||||
public plane!: any;
|
|
||||||
public camera!: PerspectiveCamera;
|
|
||||||
public renderer!: WebGLRenderer;
|
|
||||||
public scene!: Scene;
|
|
||||||
public mat!: MeshPhongMaterial;
|
|
||||||
public isDestroy = false; // 是否销毁
|
|
||||||
|
|
||||||
constructor(map: AmapViewer) {
|
|
||||||
this.Amap = map;
|
|
||||||
this.meshes = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建 GL 图层
|
|
||||||
public CreateGLlayer() {
|
|
||||||
this.locationDataSource = this.Amap.map.customCoords.lngLatsToCoords([
|
|
||||||
[118.794, 32.43] // [118.795, 32.43],
|
|
||||||
// [118.796, 32.43]
|
|
||||||
]);
|
|
||||||
const gllayer = new AMap.GLCustomLayer({
|
|
||||||
zIndex: 10, // 初始化的操作
|
|
||||||
init: (gl: any) => {
|
|
||||||
this.initRender(gl);
|
|
||||||
this.initScene();
|
|
||||||
this.initCamera();
|
|
||||||
this.initMat();
|
|
||||||
this.addBoxGeometry();
|
|
||||||
// const animate = () => {
|
|
||||||
// if (this.isDestroy) return;
|
|
||||||
// requestAnimationFrame(animate);
|
|
||||||
//
|
|
||||||
// // 全局的公共动画函数,添加函数可同步执行
|
|
||||||
// this.animateEventList.forEach((event) => {
|
|
||||||
// if (event.fun && event.content) event.fun(event.content);
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
// animate();
|
|
||||||
// const aLight = new THREE.AmbientLight(0xffffff, 0.3);
|
|
||||||
// this.scene.add(aLight);
|
|
||||||
},
|
|
||||||
render: () => {
|
|
||||||
this.renderer.resetState();
|
|
||||||
const { near, far, fov, up, lookAt, position } = this.Amap.map.customCoords.getCameraParams();
|
|
||||||
// 设置相机参数
|
|
||||||
this.camera.near = near;
|
|
||||||
this.camera.far = far;
|
|
||||||
this.camera.fov = fov;
|
|
||||||
// @ts-ignore
|
|
||||||
this.camera.position.set(...position);
|
|
||||||
// @ts-ignore
|
|
||||||
this.camera.up.set(...up);
|
|
||||||
// @ts-ignore
|
|
||||||
this.camera.lookAt(...lookAt);
|
|
||||||
this.camera.updateProjectionMatrix();
|
|
||||||
this.updateDom();
|
|
||||||
this.renderDom();
|
|
||||||
this.renderer.resetState();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.Amap.map.add(gllayer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private initRender(gl: any) {
|
|
||||||
// 获取画布dom
|
|
||||||
this.renderer = new THREE.WebGLRenderer({ antialias: true, context: gl });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建场景
|
|
||||||
private initScene() {
|
|
||||||
this.scene = new THREE.Scene();
|
|
||||||
// 环境光
|
|
||||||
const aLight = new THREE.AmbientLight(0xffffff, 0.3);
|
|
||||||
this.scene.add(aLight);
|
|
||||||
// 平行光
|
|
||||||
const { lightPositionX, lightPositionY, lightPositionZ, cameraNear, caremaFar, cameraLeft, cameraRight, cameraTop, cameraBottom, mapSize, planeMaterialOpacity } = guiCtrl;
|
|
||||||
const dLight = new THREE.DirectionalLight(0xff0000, 3);
|
|
||||||
dLight.position.set(lightPositionX, lightPositionY, lightPositionZ);
|
|
||||||
dLight.castShadow = true; // 开启阴影投射
|
|
||||||
dLight.shadow.mapSize.width = mapSize; // 增加阴影分辨率
|
|
||||||
dLight.shadow.mapSize.height = mapSize;
|
|
||||||
dLight.shadow.camera.near = cameraNear;
|
|
||||||
dLight.shadow.camera.far = caremaFar;
|
|
||||||
dLight.shadow.camera.left = cameraLeft;
|
|
||||||
dLight.shadow.camera.right = cameraRight;
|
|
||||||
dLight.shadow.camera.top = cameraTop;
|
|
||||||
dLight.shadow.camera.bottom = cameraBottom;
|
|
||||||
this.scene.add(dLight);
|
|
||||||
|
|
||||||
// 创建接收阴影的平面
|
|
||||||
const planeGeo = new THREE.PlaneGeometry(50000, 50000);
|
|
||||||
const shadowMat = new THREE.ShadowMaterial({
|
|
||||||
opacity: planeMaterialOpacity
|
|
||||||
});
|
|
||||||
|
|
||||||
this.plane = new THREE.Mesh(planeGeo, shadowMat);
|
|
||||||
this.plane.receiveShadow = true; // 接收阴影
|
|
||||||
this.scene.add(this.plane);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化相机
|
|
||||||
public initCamera() {
|
|
||||||
this.camera = new THREE.PerspectiveCamera(60, this.Amap.amapDom.clientWidth / this.Amap.amapDom.clientHeight, 100, 1 << 30);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化背景(盒模型背景,视角在盒子里面,看到的是盒子内部)
|
|
||||||
private initMat() {
|
|
||||||
// 加载纹理
|
|
||||||
const texture = new THREE.TextureLoader().load("https://a.amap.com/jsapi_demos/static/demo-center-v2/three.jpeg");
|
|
||||||
texture.minFilter = THREE.LinearFilter;
|
|
||||||
// 创建材质
|
|
||||||
this.mat = new THREE.MeshPhongMaterial({
|
|
||||||
color: 0xfff0f0,
|
|
||||||
depthTest: true,
|
|
||||||
transparent: true,
|
|
||||||
map: texture
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private addBoxGeometry() {
|
|
||||||
// 创建几何体
|
|
||||||
const geo = new THREE.BoxGeometry(1000, 1000, 1000);
|
|
||||||
for (let i = 0; i < this.locationDataSource.length; i++) {
|
|
||||||
const d = this.locationDataSource[i];
|
|
||||||
const mesh = new THREE.Mesh(geo, this.mat);
|
|
||||||
mesh.position.set(d[0], d[1], 500);
|
|
||||||
mesh.castShadow = true; // 启用阴影投射
|
|
||||||
mesh.receiveShadow = true; // 接收阴影
|
|
||||||
this.meshes.push({ mesh, count: i });
|
|
||||||
this.scene.add(mesh);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新参数
|
|
||||||
public updateDom() {
|
|
||||||
this.camera.aspect = this.Amap.amapDom.clientWidth / this.Amap.amapDom.clientHeight; // 摄像机视锥体的长宽比,通常是使用画布的宽/画布的高
|
|
||||||
this.camera.updateProjectionMatrix(); // 更新摄像机投影矩阵。在任何参数被改变以后必须被调用,来使得这些改变生效
|
|
||||||
// 禁用自动清理,以保持地图底图可见
|
|
||||||
this.renderer.autoClear = false;
|
|
||||||
this.renderer.shadowMap.enabled = true;
|
|
||||||
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
||||||
this.renderer.setSize(this.Amap.amapDom.clientWidth, this.Amap.amapDom.clientHeight);
|
|
||||||
this.renderer.setPixelRatio(window.devicePixelRatio); // 设置设备像素比
|
|
||||||
}
|
|
||||||
|
|
||||||
// 渲染dom
|
|
||||||
public renderDom() {
|
|
||||||
this.renderer?.render(this.scene as Scene, this.camera as Camera);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
.login_container {
|
|
||||||
width: 100%;
|
|
||||||
height: 100vh;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.login_ground {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 9998;
|
|
||||||
width: 100%;
|
|
||||||
height: 400px;
|
|
||||||
background-image: url("../../../../public/images/ground.png");
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 100% 100%;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:global {
|
|
||||||
#LOGIN_CONTAINER {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
import React, { FC, useEffect, useRef } from "react";
|
|
||||||
import Viewer from "./modules/Viewer";
|
|
||||||
import Earth from "./modules/earth";
|
|
||||||
import styles from "./index.less";
|
|
||||||
|
|
||||||
interface OwnProps {}
|
|
||||||
|
|
||||||
type Props = OwnProps;
|
|
||||||
|
|
||||||
const PAGE_ID = "LOGIN_CONTAINER";
|
|
||||||
const ThreeJSIndex: FC<Props> = (props) => {
|
|
||||||
const viewerRef = useRef<Viewer>();
|
|
||||||
useEffect(() => {
|
|
||||||
init();
|
|
||||||
return () => viewerRef.current?.destroy();
|
|
||||||
}, []);
|
|
||||||
// 加载
|
|
||||||
const init = () => {
|
|
||||||
viewerRef.current = new Viewer(PAGE_ID);
|
|
||||||
const viewer = viewerRef.current;
|
|
||||||
viewer.addStats();
|
|
||||||
viewer.initSphereModal();
|
|
||||||
const earthLoader = new Earth(viewer);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.login_container}>
|
|
||||||
<div id={PAGE_ID} />
|
|
||||||
<div className={styles.login_ground} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ThreeJSIndex;
|
|
||||||
|
|
@ -1,212 +0,0 @@
|
||||||
import * as THREE from "three";
|
|
||||||
import { Camera, Mesh, PerspectiveCamera, Raycaster, Scene, SphereGeometry, SRGBColorSpace, Vector2, WebGLRenderer } from "three";
|
|
||||||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
|
|
||||||
import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer.js";
|
|
||||||
import Stats from "three/examples/jsm/libs/stats.module.js";
|
|
||||||
|
|
||||||
export type Animate = {
|
|
||||||
fun: (arg: any) => any;
|
|
||||||
content: any;
|
|
||||||
};
|
|
||||||
export default class Viewer {
|
|
||||||
public id: string;
|
|
||||||
public depth: number;
|
|
||||||
public zAxisNumber!: number;
|
|
||||||
public viewerDom!: HTMLElement;
|
|
||||||
public renderer!: WebGLRenderer;
|
|
||||||
public scene!: Scene;
|
|
||||||
public camera!: PerspectiveCamera;
|
|
||||||
public controls!: OrbitControls;
|
|
||||||
public css2Renderer: CSS2DRenderer | undefined;
|
|
||||||
public raycaster!: Raycaster;
|
|
||||||
public mouse!: Vector2;
|
|
||||||
public isDestroy = false; // 是否销毁
|
|
||||||
public animateEventList = new Map();
|
|
||||||
public statsControls!: Stats; // 声明球体几何
|
|
||||||
public sphereGeometry!: SphereGeometry; // 声明完整球
|
|
||||||
public sphere!: Mesh;
|
|
||||||
public Sphere_Group!: THREE.Group;
|
|
||||||
|
|
||||||
constructor(id: string) {
|
|
||||||
this.id = id;
|
|
||||||
this.depth = 1440;
|
|
||||||
this.initViewer();
|
|
||||||
}
|
|
||||||
|
|
||||||
private initViewer() {
|
|
||||||
this.initRenderer(); //创建渲染器
|
|
||||||
this.initScene(); //创建场景
|
|
||||||
this.initSceneBg(); //初始化背景
|
|
||||||
this.initLight(); // 创建光源
|
|
||||||
this.initCamera(); // 创建相机
|
|
||||||
this.initControl(); // 创建控制器
|
|
||||||
this.initCss2Renderer(); // 创建css2渲染器
|
|
||||||
//
|
|
||||||
// this.raycaster = new Raycaster(); // 创建射线
|
|
||||||
// this.mouse = new Vector2(); // 创建鼠标位置
|
|
||||||
//
|
|
||||||
const animate = () => {
|
|
||||||
if (this.isDestroy) return;
|
|
||||||
requestAnimationFrame(animate);
|
|
||||||
this.renderSphereRotate(); //渲染星球的自转
|
|
||||||
this.css2Renderer?.render(this.scene, this.camera);
|
|
||||||
this.updateDom();
|
|
||||||
this.renderDom();
|
|
||||||
|
|
||||||
// 全局的公共动画函数,添加函数可同步执行
|
|
||||||
this.animateEventList.forEach((event) => {
|
|
||||||
// console.log("animateEventList");
|
|
||||||
if (event.fun && event.content) event.fun(event.content);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
animate();
|
|
||||||
}
|
|
||||||
|
|
||||||
private initRenderer() {
|
|
||||||
// 获取画布dom
|
|
||||||
this.viewerDom = document.getElementById(this.id) as HTMLElement;
|
|
||||||
// 初始化渲染器
|
|
||||||
this.renderer = new WebGLRenderer({
|
|
||||||
logarithmicDepthBuffer: true, //true/false用于启用或禁用对数深度缓冲区。
|
|
||||||
antialias: true, // true/false表示是否开启反锯齿
|
|
||||||
alpha: true, // true/false 表示是否可以设置背景色透明
|
|
||||||
precision: "mediump", // highp/mediump/lowp 表示着色精度选择
|
|
||||||
premultipliedAlpha: true // true/false 表示是否可以设置像素深度(用来度量图像的分辨率)
|
|
||||||
// preserveDrawingBuffer: false, // true/false 表示是否保存绘图缓冲
|
|
||||||
// physicallyCorrectLights: true, // true/false 表示是否开启物理光照
|
|
||||||
});
|
|
||||||
this.renderer.clearDepth(); //清除深度缓冲区。在渲染之前,这通常用于重置深度缓冲区,以确保正确的深度测试
|
|
||||||
|
|
||||||
// 开启模型对象的局部剪裁平面功能
|
|
||||||
// 如果不设置为true,设置剪裁平面的模型不会被剪裁
|
|
||||||
this.renderer.localClippingEnabled = true;
|
|
||||||
|
|
||||||
this.renderer.shadowMap.enabled = true;
|
|
||||||
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
||||||
this.renderer.outputColorSpace = SRGBColorSpace; // 可以看到更亮的材质,同时这也影响到环境贴图。
|
|
||||||
this.viewerDom.appendChild(this.renderer.domElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
private initScene() {
|
|
||||||
this.scene = new Scene();
|
|
||||||
// 在场景中添加雾的效果,Fog参数分别代表‘雾的颜色’、‘开始雾化的视线距离’、刚好雾化至看不见的视线距离’
|
|
||||||
this.scene.fog = new THREE.Fog(0x000000, 0, 10000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化背景(盒模型背景,视角在盒子里面,看到的是盒子内部)
|
|
||||||
private initSceneBg() {
|
|
||||||
new THREE.TextureLoader().load("/images/sky.png", (texture) => {
|
|
||||||
const geometry = new THREE.BoxGeometry(this.viewerDom.clientWidth, this.viewerDom.clientHeight, this.depth); // 创建一个球形几何体 SphereGeometry
|
|
||||||
const material = new THREE.MeshBasicMaterial({ map: texture, side: THREE.BackSide }); // 创建基础为网格基础材料
|
|
||||||
const mesh = new THREE.Mesh(geometry, material);
|
|
||||||
this.scene.add(mesh);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private initLight() {
|
|
||||||
const ambientLight = new THREE.AmbientLight(0xffffff, 1);
|
|
||||||
// 右下角点光源
|
|
||||||
const light_rightBottom = new THREE.PointLight(0x0655fd, 5, 0);
|
|
||||||
light_rightBottom.position.set(0, 100, -200);
|
|
||||||
this.scene.add(light_rightBottom);
|
|
||||||
this.scene.add(ambientLight);
|
|
||||||
}
|
|
||||||
|
|
||||||
private initCamera() {
|
|
||||||
const distance = this.viewerDom.clientWidth / 2 / Math.tan(Math.PI / 12);
|
|
||||||
this.zAxisNumber = Math.floor(distance - this.depth / 2);
|
|
||||||
// 渲染相机
|
|
||||||
this.camera = new PerspectiveCamera(15, this.viewerDom.clientWidth / this.viewerDom.clientHeight, 1, 30000);
|
|
||||||
this.camera.position.set(0, 0, this.zAxisNumber);
|
|
||||||
this.camera.lookAt(new THREE.Vector3(0, 0, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
private initControl() {
|
|
||||||
this.controls = new OrbitControls(this.camera as Camera, this.renderer?.domElement);
|
|
||||||
this.controls.enabled = false; // enabled设置为true是可以使用鼠标控制视角
|
|
||||||
// this.controls.enableDamping = false;
|
|
||||||
// this.controls.screenSpacePanning = false; // 定义平移时如何平移相机的位置 控制不上下移动
|
|
||||||
this.controls.minDistance = 2;
|
|
||||||
this.controls.maxDistance = 2000;
|
|
||||||
this.controls.addEventListener("change", () => {
|
|
||||||
this.renderer.render(this.scene, this.camera);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private initCss2Renderer() {
|
|
||||||
this.css2Renderer = new CSS2DRenderer();
|
|
||||||
}
|
|
||||||
|
|
||||||
public updateDom() {
|
|
||||||
this.controls.update();
|
|
||||||
// // 更新参数
|
|
||||||
this.camera.aspect = this.viewerDom.clientWidth / this.viewerDom.clientHeight; // 摄像机视锥体的长宽比,通常是使用画布的宽/画布的高
|
|
||||||
this.camera.updateProjectionMatrix(); // 更新摄像机投影矩阵。在任何参数被改变以后必须被调用,来使得这些改变生效
|
|
||||||
this.renderer.setSize(this.viewerDom.clientWidth, this.viewerDom.clientHeight);
|
|
||||||
this.renderer.setPixelRatio(window.devicePixelRatio); // 设置设备像素比
|
|
||||||
}
|
|
||||||
|
|
||||||
// 渲染dom
|
|
||||||
public renderDom() {
|
|
||||||
this.renderer?.render(this.scene as Scene, this.camera as Camera);
|
|
||||||
}
|
|
||||||
|
|
||||||
private statsUpdate(statsControls: any) {
|
|
||||||
statsControls.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化球体模型
|
|
||||||
public initSphereModal() {
|
|
||||||
//材质
|
|
||||||
let material = new THREE.MeshPhongMaterial();
|
|
||||||
material.map = new THREE.TextureLoader().load("/images/earth_bg.png");
|
|
||||||
material.blendDstAlpha = 1;
|
|
||||||
//几何体
|
|
||||||
this.sphereGeometry = new THREE.SphereGeometry(50, 64, 32);
|
|
||||||
//模型
|
|
||||||
this.sphere = new THREE.Mesh(this.sphereGeometry, material);
|
|
||||||
this.Sphere_Group = new THREE.Group();
|
|
||||||
this.Sphere_Group.add(this.sphere);
|
|
||||||
this.Sphere_Group.position.x = -400;
|
|
||||||
this.Sphere_Group.position.y = 200;
|
|
||||||
this.Sphere_Group.position.z = -200;
|
|
||||||
this.scene.add(this.Sphere_Group);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 渲染星球的自转
|
|
||||||
private renderSphereRotate() {
|
|
||||||
if (this.sphere) {
|
|
||||||
this.Sphere_Group.rotateY(0.001);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public addAnimate(id: string, animate: Animate) {
|
|
||||||
this.animateEventList.set(id, animate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public addStats() {
|
|
||||||
if (!this.statsControls) this.statsControls = new Stats();
|
|
||||||
this.statsControls.dom.style.position = "absolute";
|
|
||||||
this.viewerDom.appendChild(this.statsControls.dom);
|
|
||||||
|
|
||||||
// 添加到动画
|
|
||||||
this.addAnimate("stats", { fun: this.statsUpdate, content: this.statsControls });
|
|
||||||
}
|
|
||||||
|
|
||||||
public destroy() {
|
|
||||||
this.scene.traverse((child: any) => {
|
|
||||||
if (child.material) {
|
|
||||||
child.material.dispose();
|
|
||||||
}
|
|
||||||
if (child.geometry) {
|
|
||||||
child.geometry.dispose();
|
|
||||||
}
|
|
||||||
child = null;
|
|
||||||
});
|
|
||||||
this.renderer.forceContextLoss();
|
|
||||||
this.renderer.dispose();
|
|
||||||
this.scene.clear();
|
|
||||||
|
|
||||||
this.isDestroy = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,240 +0,0 @@
|
||||||
import type Viewer from "../Viewer";
|
|
||||||
import { GUI } from "three/examples/jsm/libs/lil-gui.module.min";
|
|
||||||
import * as THREE from "three";
|
|
||||||
|
|
||||||
class Params {
|
|
||||||
x: number = 0;
|
|
||||||
y: number = 0;
|
|
||||||
z: number = 100;
|
|
||||||
size: number = 3;
|
|
||||||
length: number = 10;
|
|
||||||
radius: number = 16;
|
|
||||||
visible: boolean = true;
|
|
||||||
widthSegments: number = 64;
|
|
||||||
heightSegments: number = 32;
|
|
||||||
color: string = "#000";
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Earth {
|
|
||||||
protected viewer: Viewer;
|
|
||||||
public particles_init_position!: number;
|
|
||||||
public zprogress!: number; // 声明点在z轴上移动的进度
|
|
||||||
public zprogress_second!: number; // 声明同上(第二个几何点)
|
|
||||||
public particles_first!: any[]; // 声明粒子1
|
|
||||||
public particles_second!: any[]; // 声明粒子1
|
|
||||||
public parameters!: any; // 声明点的参数
|
|
||||||
public materials!: any[]; // 声明点材质
|
|
||||||
public cloudParameter_first!: any; // 声明流动的云对象1(包含路径、云实例)
|
|
||||||
public cloudParameter_second!: any; // 声明流动的云对象2(包含路径、云实例)
|
|
||||||
public renderCloudMove_first!: any; // 声明云流动的渲染函数1
|
|
||||||
public renderCloudMove_second!: any; // 声明云流动的渲染函数1
|
|
||||||
public gui!: any; // 声明调试工具
|
|
||||||
|
|
||||||
constructor(viewer: Viewer) {
|
|
||||||
this.viewer = viewer;
|
|
||||||
this.materials = [];
|
|
||||||
this.gui = new GUI();
|
|
||||||
this.particles_init_position = -this.viewer.zAxisNumber - this.viewer.depth / 2;
|
|
||||||
this.zprogress = this.particles_init_position;
|
|
||||||
this.zprogress_second = this.particles_init_position * 2;
|
|
||||||
this.particles_first = this.initSceneStar(this.particles_init_position);
|
|
||||||
this.particles_second = this.initSceneStar(this.zprogress_second);
|
|
||||||
this.cloudParameter_first = this.initTubeRoute(
|
|
||||||
[
|
|
||||||
new THREE.Vector3(-this.viewer.viewerDom.clientWidth / 10, 0, -this.viewer.depth / 2),
|
|
||||||
new THREE.Vector3(-this.viewer.viewerDom.clientWidth / 4, this.viewer.viewerDom.clientHeight / 8, 0),
|
|
||||||
new THREE.Vector3(-this.viewer.viewerDom.clientWidth / 4, 0, this.viewer.zAxisNumber)
|
|
||||||
],
|
|
||||||
400,
|
|
||||||
200
|
|
||||||
);
|
|
||||||
this.cloudParameter_second = this.initTubeRoute(
|
|
||||||
[
|
|
||||||
new THREE.Vector3(this.viewer.viewerDom.clientWidth / 8, this.viewer.viewerDom.clientHeight / 8, -this.viewer.depth / 2),
|
|
||||||
new THREE.Vector3(this.viewer.viewerDom.clientWidth / 8, this.viewer.viewerDom.clientHeight / 8, this.viewer.zAxisNumber)
|
|
||||||
],
|
|
||||||
200,
|
|
||||||
100
|
|
||||||
);
|
|
||||||
this.renderCloudMove_first = this.initCloudMove(this.cloudParameter_first, 0.0002);
|
|
||||||
this.renderCloudMove_second = this.initCloudMove(this.cloudParameter_second, 0.0008, 0.001);
|
|
||||||
this.initGUI();
|
|
||||||
|
|
||||||
const animate = () => {
|
|
||||||
if (this.viewer.isDestroy) return;
|
|
||||||
requestAnimationFrame(animate);
|
|
||||||
this.renderStarMove();
|
|
||||||
this.renderCloudMove_first();
|
|
||||||
this.renderCloudMove_second();
|
|
||||||
this.viewer.updateDom();
|
|
||||||
this.viewer.renderDom();
|
|
||||||
};
|
|
||||||
animate();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化gui
|
|
||||||
public initGUI = () => {
|
|
||||||
const params = new Params();
|
|
||||||
this.gui.add(params, "x", -1500, 1500).onChange((x: number) => {
|
|
||||||
//点击颜色面板,e为返回的10进制颜色
|
|
||||||
this.viewer.Sphere_Group.position.x = x;
|
|
||||||
});
|
|
||||||
this.gui.add(params, "y", -50, 1500).onChange((y: number) => {
|
|
||||||
//点击颜色面板,e为返回的10进制颜色
|
|
||||||
this.viewer.Sphere_Group.position.y = y;
|
|
||||||
});
|
|
||||||
this.gui.add(params, "z", -200, 1000).onChange((z: number) => {
|
|
||||||
//点击颜色面板,e为返回的10进制颜色
|
|
||||||
this.viewer.Sphere_Group.position.z = z;
|
|
||||||
});
|
|
||||||
// this.gui.add(params, "widthSegments", 0, 64).onChange((widthSegments: number) => {
|
|
||||||
// //点击颜色面板,e为返回的10进制颜色
|
|
||||||
// // this.viewer.sphereGeometry.parameters.widthSegments = widthSegments;
|
|
||||||
// });
|
|
||||||
// this.gui.add(params, "heightSegments", 0, 32).onChange((heightSegments: number) => {
|
|
||||||
// //点击颜色面板,e为返回的10进制颜色
|
|
||||||
// // this.viewer.sphereGeometry.parameters.heightSegments = heightSegments;
|
|
||||||
// });
|
|
||||||
// this.gui.add(params, "radius", 5, 30).onChange((radius: number) => {
|
|
||||||
// //点击颜色面板,e为返回的10进制颜色
|
|
||||||
// // this.viewer.sphereGeometry.parameters.radius = radius;
|
|
||||||
// this.viewer.renderer.render(this.viewer.scene, this.viewer.camera);
|
|
||||||
// });
|
|
||||||
// this.gui.add(params, "visible").onChange((e: any) => {
|
|
||||||
// //这是一个单选框,因为params.visible是一个布尔值,e返回所选布尔值
|
|
||||||
// // points.visible = e
|
|
||||||
// });
|
|
||||||
// this.gui.addColor(params, "color").onChange((e: any) => {
|
|
||||||
// //点击颜色面板,e为返回的10进制颜色
|
|
||||||
// // pointsMaterial.color.set(e)
|
|
||||||
// });
|
|
||||||
};
|
|
||||||
|
|
||||||
private initSceneStar(initZposition: number) {
|
|
||||||
const geometry = new THREE.BufferGeometry();
|
|
||||||
const vertices: number[] = [];
|
|
||||||
const pointsGeometry: any[] = [];
|
|
||||||
const textureLoader = new THREE.TextureLoader();
|
|
||||||
const sprite1 = textureLoader.load("/images/starflake1.png");
|
|
||||||
const sprite2 = textureLoader.load("/images/starflake2.png");
|
|
||||||
this.parameters = [
|
|
||||||
[[0.6, 100, 0.75], sprite1, 50],
|
|
||||||
[[0, 0, 1], sprite2, 20]
|
|
||||||
];
|
|
||||||
// 初始化500个节点
|
|
||||||
for (let i = 0; i < 500; i++) {
|
|
||||||
/**
|
|
||||||
* const x: number = Math.random() * 2 * width - width
|
|
||||||
* 等价
|
|
||||||
* THREE.MathUtils.randFloatSpread(width)
|
|
||||||
*/
|
|
||||||
const x: number = THREE.MathUtils.randFloatSpread(this.viewer.viewerDom.clientWidth);
|
|
||||||
const y: number = _.random(0, this.viewer.viewerDom.clientHeight / 2);
|
|
||||||
const z: number = _.random(-this.viewer.depth / 2, this.viewer.zAxisNumber);
|
|
||||||
vertices.push(x, y, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
geometry.setAttribute("position", new THREE.Float32BufferAttribute(vertices, 3));
|
|
||||||
// 创建2种不同的材质的节点(500 * 2)
|
|
||||||
for (let i = 0; i < this.parameters.length; i++) {
|
|
||||||
const color = this.parameters[i][0];
|
|
||||||
const sprite = this.parameters[i][1];
|
|
||||||
const size = this.parameters[i][2];
|
|
||||||
|
|
||||||
this.materials[i] = new THREE.PointsMaterial({
|
|
||||||
size,
|
|
||||||
map: sprite,
|
|
||||||
blending: THREE.AdditiveBlending,
|
|
||||||
depthTest: true,
|
|
||||||
transparent: true
|
|
||||||
});
|
|
||||||
this.materials[i].color.setHSL(color[0], color[1], color[2]);
|
|
||||||
const particles = new THREE.Points(geometry, this.materials[i]);
|
|
||||||
particles.rotation.x = Math.random() * 0.2 - 0.15;
|
|
||||||
particles.rotation.z = Math.random() * 0.2 - 0.15;
|
|
||||||
particles.rotation.y = Math.random() * 0.2 - 0.15;
|
|
||||||
particles.position.setZ(initZposition);
|
|
||||||
pointsGeometry.push(particles);
|
|
||||||
this.viewer.scene.add(particles);
|
|
||||||
}
|
|
||||||
return pointsGeometry;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 渲染星星的运动
|
|
||||||
public renderStarMove() {
|
|
||||||
const time = Date.now() * 0.00005;
|
|
||||||
this.zprogress += 1;
|
|
||||||
this.zprogress_second += 1;
|
|
||||||
|
|
||||||
if (this.zprogress >= this.viewer.zAxisNumber + this.viewer.depth / 2) {
|
|
||||||
this.zprogress = this.particles_init_position;
|
|
||||||
} else {
|
|
||||||
this.particles_first.forEach((item) => {
|
|
||||||
item.position.setZ(this.zprogress);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (this.zprogress_second >= this.viewer.zAxisNumber + this.viewer.depth / 2) {
|
|
||||||
this.zprogress_second = this.particles_init_position;
|
|
||||||
} else {
|
|
||||||
this.particles_second.forEach((item) => {
|
|
||||||
item.position.setZ(this.zprogress_second);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < this.materials.length; i++) {
|
|
||||||
const color = this.parameters[i][0];
|
|
||||||
|
|
||||||
const h = ((360 * (color[0] + time)) % 360) / 360;
|
|
||||||
this.materials[i].color.setHSL(color[0], color[1], parseFloat(h.toFixed(2)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化云的运动函数
|
|
||||||
public initCloudMove = (cloudParameter: any, speed: number, scaleSpeed = 0.0006, maxScale = 1, startScale = 0) => {
|
|
||||||
let cloudProgress = 0;
|
|
||||||
return () => {
|
|
||||||
if (startScale < maxScale) {
|
|
||||||
startScale += scaleSpeed;
|
|
||||||
cloudParameter.cloud.scale.setScalar(startScale);
|
|
||||||
}
|
|
||||||
if (cloudProgress > 1) {
|
|
||||||
cloudProgress = 0;
|
|
||||||
startScale = 0;
|
|
||||||
} else {
|
|
||||||
cloudProgress += speed;
|
|
||||||
if (cloudParameter.curve) {
|
|
||||||
const point = cloudParameter.curve.getPoint(cloudProgress);
|
|
||||||
if (point && point.x) {
|
|
||||||
cloudParameter.cloud.position.set(point.x, point.y, point.z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// 初始化流动路径
|
|
||||||
private initTubeRoute(route?: any, geometryWidth?: number, geometryHeigh?: number) {
|
|
||||||
const curve = new THREE.CatmullRomCurve3(route, false);
|
|
||||||
const tubeGeometry = new THREE.TubeGeometry(curve, 100, 2, 50, false);
|
|
||||||
const tubeMaterial = new THREE.MeshBasicMaterial({
|
|
||||||
// color: '0x4488ff',
|
|
||||||
opacity: 0,
|
|
||||||
transparent: true
|
|
||||||
});
|
|
||||||
const tube = new THREE.Mesh(tubeGeometry, tubeMaterial);
|
|
||||||
this.viewer.scene.add(tube);
|
|
||||||
|
|
||||||
const clondGeometry = new THREE.PlaneGeometry(geometryWidth, geometryHeigh);
|
|
||||||
const textureLoader = new THREE.TextureLoader();
|
|
||||||
const cloudTexture = textureLoader.load("/images/cloud.png");
|
|
||||||
const clondMaterial = new THREE.MeshBasicMaterial({
|
|
||||||
map: cloudTexture,
|
|
||||||
blending: THREE.AdditiveBlending,
|
|
||||||
depthTest: false,
|
|
||||||
transparent: true
|
|
||||||
});
|
|
||||||
const cloud = new THREE.Mesh(clondGeometry, clondMaterial);
|
|
||||||
this.viewer.scene.add(cloud);
|
|
||||||
return { cloud, curve };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
.threejs_container {
|
|
||||||
width: 100%;
|
|
||||||
height: 100vh;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global {
|
|
||||||
#DEMO_CONTAINER {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,135 +0,0 @@
|
||||||
import React, { FC, useEffect, useRef, useState } from "react";
|
|
||||||
import { observer } from "mobx-react";
|
|
||||||
import { useMobStore } from "@/hooks";
|
|
||||||
import * as THREE from "three";
|
|
||||||
import Viewer from "./modules/Viewer";
|
|
||||||
import ModelLoader from "./modules/ModelLoder";
|
|
||||||
import BoxHelperWrap from "./modules/BoxHelperWrap";
|
|
||||||
import { checkNameIncludes } from "@/utils/three";
|
|
||||||
import Floors from "./modules/Floors";
|
|
||||||
import { Object3DExtends } from "@/types";
|
|
||||||
import styles from "./index.less";
|
|
||||||
|
|
||||||
interface OwnProps {}
|
|
||||||
|
|
||||||
type Props = OwnProps;
|
|
||||||
|
|
||||||
const PAGE_ID = "DEMO_CONTAINER";
|
|
||||||
const ThreeJSIndex: FC<Props> = observer((props) => {
|
|
||||||
const store = useMobStore("threeStore");
|
|
||||||
const [rackList, setRackList] = useState<Object3DExtends[]>([]); //架子
|
|
||||||
const [chairList, setChairList] = useState<Object3DExtends[]>([]); //椅子
|
|
||||||
const viewerRef = useRef<Viewer>();
|
|
||||||
let modelLoader: ModelLoader;
|
|
||||||
let boxHelperWrap: BoxHelperWrap;
|
|
||||||
useEffect(() => {
|
|
||||||
init();
|
|
||||||
initModel();
|
|
||||||
return () => viewerRef.current?.destroy();
|
|
||||||
}, []);
|
|
||||||
/** 需要监听rackInfoList更新监听点击事件的函数 */
|
|
||||||
useEffect(() => {
|
|
||||||
if (!viewerRef.current) return;
|
|
||||||
if (_.isEmpty(store?.raycasterObjects)) return;
|
|
||||||
const viewer = viewerRef.current;
|
|
||||||
onMouseClick(store?.raycasterObjects);
|
|
||||||
}, [store?.raycasterObjects, viewerRef, viewerRef.current?.scene]);
|
|
||||||
// 加载
|
|
||||||
const init = () => {
|
|
||||||
viewerRef.current = new Viewer(PAGE_ID);
|
|
||||||
const viewer = viewerRef.current;
|
|
||||||
viewer.addAxis();
|
|
||||||
viewer.addStats();
|
|
||||||
viewer.initRaycaster();
|
|
||||||
|
|
||||||
modelLoader = new ModelLoader(viewer);
|
|
||||||
const floors = new Floors(viewer);
|
|
||||||
floors.addGird(8, 25, 0x004444, 0x004444);
|
|
||||||
// boxHelperWrap = new BoxHelperWrap(viewer);
|
|
||||||
};
|
|
||||||
// 加载模型
|
|
||||||
const initModel = () => {
|
|
||||||
modelLoader.loadModelToScene("/models/datacenter.glb", (baseModel) => {
|
|
||||||
// 设置基础模型的缩放比例
|
|
||||||
baseModel.setScalc(0.15);
|
|
||||||
// 暂时注释掉旋转代码
|
|
||||||
// baseModel.object.rotation.y = Math.PI / 2;
|
|
||||||
// 获取实际的模型对象
|
|
||||||
const model = baseModel.gltf.scene;
|
|
||||||
model.position.set(0, 0, 0.3);
|
|
||||||
// 为模型设置名称
|
|
||||||
model.name = "机房";
|
|
||||||
model.uuid = "机房";
|
|
||||||
// 启用基础模型的投射阴影功能
|
|
||||||
baseModel.openCastShadow();
|
|
||||||
let rackList: Object3DExtends[] = []; // 机架列表
|
|
||||||
let allList: Object3DExtends[] = []; // 所有的物体
|
|
||||||
let chairList: Object3DExtends[] = []; // 椅子列表
|
|
||||||
model.traverse((item) => {
|
|
||||||
if (checkIsRack(item)) {
|
|
||||||
rackList.push(item);
|
|
||||||
}
|
|
||||||
if (checkIsChair(item)) {
|
|
||||||
chairList.push(item);
|
|
||||||
}
|
|
||||||
allList.push(item);
|
|
||||||
if (item instanceof THREE.Mesh) {
|
|
||||||
// 保存原始颜色数据,以及警告颜色
|
|
||||||
if (item.isMesh) {
|
|
||||||
item.material.warningColor = { r: 1, g: 0, b: 0, isColor: true };
|
|
||||||
// 保存旧的材质
|
|
||||||
(item as Object3DExtends).oldMaterial = item.material;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// dispatchDeviceListData({ type: "INIT", initData: rackList });
|
|
||||||
setRackList(rackList);
|
|
||||||
setChairList(chairList);
|
|
||||||
const viewer = viewerRef.current;
|
|
||||||
// 将 rackList 中的机架设置为 viewer 的射线检测对象
|
|
||||||
viewer?.setRaycasterObjects([...allList]);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const checkIsRack = (obj: THREE.Object3D): boolean => {
|
|
||||||
return checkNameIncludes(obj, "rack");
|
|
||||||
};
|
|
||||||
const checkIsChair = (obj: THREE.Object3D): boolean => {
|
|
||||||
return checkNameIncludes(obj, "chair");
|
|
||||||
};
|
|
||||||
const onClickChair = (selectedObject: THREE.Object3D) => {
|
|
||||||
if (!checkNameIncludes(selectedObject, "chair")) return;
|
|
||||||
const viewer = viewerRef.current;
|
|
||||||
|
|
||||||
viewer?.addCameraTween(new THREE.Vector3(0.05, 0.66, -2.54), 1000, () => {
|
|
||||||
console.log("动画完成");
|
|
||||||
console.log(viewer?.scene?.children);
|
|
||||||
const sceneModels = viewer?.scene?.children;
|
|
||||||
// const model = sceneModels?.find((model) => {
|
|
||||||
// return model.name === "机房";
|
|
||||||
// })!;
|
|
||||||
// const floorModel = sceneModels?.find((model) => {
|
|
||||||
// return model.name === "机房";
|
|
||||||
// })
|
|
||||||
viewer?.setRaycasterObjects([]); //
|
|
||||||
sceneModels?.forEach((model) => {
|
|
||||||
viewer?.scene.remove(model);
|
|
||||||
});
|
|
||||||
|
|
||||||
// createRoom("room", new THREE.Vector3(0, 0, 0));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const onMouseClick = (intersects: THREE.Intersection[]) => {
|
|
||||||
if (!intersects.length) return;
|
|
||||||
const selectedObject = intersects?.[0].object || {};
|
|
||||||
// onClickRack(selectedObject);
|
|
||||||
onClickChair(selectedObject);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.threejs_container}>
|
|
||||||
<div id={PAGE_ID} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default ThreeJSIndex;
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
import type { Material } from "three";
|
|
||||||
import * as THREE from "three";
|
|
||||||
import type Viewer from "../Viewer";
|
|
||||||
import type { GLTF } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
||||||
import { Object3DExtends } from "@/types";
|
|
||||||
|
|
||||||
export default class BaseModel {
|
|
||||||
protected viewer: Viewer;
|
|
||||||
public gltf: GLTF; // 模型
|
|
||||||
public object: THREE.Group;
|
|
||||||
/**模型原始材质 */
|
|
||||||
public originMaterials: Material[] = [];
|
|
||||||
public isSaveMaterial = false;
|
|
||||||
public animaIndex = -1;
|
|
||||||
public mixer!: THREE.AnimationMixer;
|
|
||||||
public clock: THREE.Clock;
|
|
||||||
|
|
||||||
constructor(gltf: GLTF, viewer: Viewer) {
|
|
||||||
this.gltf = gltf;
|
|
||||||
this.viewer = viewer;
|
|
||||||
this.object = gltf.scene || gltf;
|
|
||||||
|
|
||||||
this.clock = new THREE.Clock(); // 时钟
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置模型比例
|
|
||||||
* @param x 可以只填写一个参数
|
|
||||||
* @param y 纵轴缩放
|
|
||||||
* @param z 横轴缩放
|
|
||||||
*/
|
|
||||||
public setScalc(x: number, y?: number, z?: number) {
|
|
||||||
this.object.scale.set(x, y || x, z || x);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 开启模型阴影 数组中移除阴影
|
|
||||||
*/
|
|
||||||
public openCastShadow(names = []) {
|
|
||||||
this.gltf.scene.traverse((model: Object3DExtends) => {
|
|
||||||
if (model.isMesh && !names.includes(model.name as never)) {
|
|
||||||
//它会在渲染对象之前检查每一帧对象是否位于相机的视锥体中。 如果设置为 false,则即使对象不在相机的视锥体中,也会在每一帧进行渲染。
|
|
||||||
model.frustumCulled = false;
|
|
||||||
model.castShadow = true; //对象是否渲染成阴影贴图。
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import { BoxHelper, Color, Object3D } from "three";
|
|
||||||
import type Viewer from "../Viewer";
|
|
||||||
// 通过 BoxHelper 可以实现简单的鼠标选中的特效。
|
|
||||||
// 也可以通过 OutlinePass 实现发光的特效。
|
|
||||||
export default class BoxHelperWrap {
|
|
||||||
protected viewer: Viewer;
|
|
||||||
public boxHelper: BoxHelper;
|
|
||||||
|
|
||||||
constructor(viewer: Viewer, color?: number) {
|
|
||||||
this.viewer = viewer;
|
|
||||||
const boxColor = color === undefined ? 0x00ffff : color;
|
|
||||||
this.boxHelper = new BoxHelper(new Object3D(), new Color(boxColor));
|
|
||||||
|
|
||||||
this.initBoxHelperWrap(); // 初始化
|
|
||||||
}
|
|
||||||
|
|
||||||
private initBoxHelperWrap() {
|
|
||||||
this.viewer.scene.add(this.boxHelper);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import * as THREE from "three";
|
|
||||||
import type Viewer from "../Viewer";
|
|
||||||
|
|
||||||
export default class Floors {
|
|
||||||
protected viewer: Viewer;
|
|
||||||
public planeWidth = 1000;
|
|
||||||
public planeHeight = 1000;
|
|
||||||
|
|
||||||
constructor(viewer: Viewer) {
|
|
||||||
this.viewer = viewer;
|
|
||||||
this.initFlooer();
|
|
||||||
}
|
|
||||||
|
|
||||||
private initFlooer() {
|
|
||||||
const ground = new THREE.Mesh(new THREE.PlaneGeometry(this.planeWidth, this.planeHeight), new THREE.MeshPhongMaterial({ color: 0xbbbbbb, depthWrite: false }));
|
|
||||||
ground.rotation.x = -Math.PI / 2;
|
|
||||||
ground.receiveShadow = true;
|
|
||||||
this.viewer.scene.add(ground);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**网格辅助线 */
|
|
||||||
public addGird(size = 1000, divisions = 20, colorCenterLine = 0x888888, colorGrid = 0x888888) {
|
|
||||||
const grid = new THREE.GridHelper(size, divisions, colorCenterLine, colorGrid);
|
|
||||||
this.viewer.scene.add(grid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
import type Viewer from "../Viewer";
|
|
||||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
||||||
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
|
|
||||||
import BaseModel from "../BaseModel";
|
|
||||||
|
|
||||||
type LoadModelCallbackFn<T = any> = (arg: T) => any;
|
|
||||||
|
|
||||||
/**模型加载器 */
|
|
||||||
export default class ModelLoder {
|
|
||||||
protected viewer: Viewer;
|
|
||||||
private gltfLoader: GLTFLoader;
|
|
||||||
private readonly dracoLoader: DRACOLoader;
|
|
||||||
|
|
||||||
constructor(viewer: Viewer, dracolPath = "/draco/") {
|
|
||||||
this.viewer = viewer;
|
|
||||||
this.gltfLoader = new GLTFLoader();
|
|
||||||
this.dracoLoader = new DRACOLoader();
|
|
||||||
// 提供一个DracLoader实例来解码压缩网格数据
|
|
||||||
// 没有这个会报错 dracolPath 默认放在public文件夹当中
|
|
||||||
this.dracoLoader.setDecoderPath(dracolPath);
|
|
||||||
this.gltfLoader.setDRACOLoader(this.dracoLoader);
|
|
||||||
}
|
|
||||||
|
|
||||||
private loadModel(url: string, callback: LoadModelCallbackFn<BaseModel>) {
|
|
||||||
this.gltfLoader.load(url, (gltf) => {
|
|
||||||
const baseModel = new BaseModel(gltf, this.viewer);
|
|
||||||
callback && callback(baseModel);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**模型加载到场景 */
|
|
||||||
public loadModelToScene(url: string, callback: LoadModelCallbackFn<BaseModel>) {
|
|
||||||
this.loadModel(url, (model) => {
|
|
||||||
this.viewer.scene.add(model.object);
|
|
||||||
callback && callback(model);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
export default {
|
|
||||||
animate: "animate",
|
|
||||||
dispose: "dispose",
|
|
||||||
orbitChange: "orbitChange",
|
|
||||||
load: {
|
|
||||||
start: "load:start",
|
|
||||||
processing: "load:processing",
|
|
||||||
finish: "load:finish",
|
|
||||||
},
|
|
||||||
click: {
|
|
||||||
raycaster: "click:raycaster",
|
|
||||||
},
|
|
||||||
dblclick: {
|
|
||||||
raycaster: "dblclick:raycaster",
|
|
||||||
},
|
|
||||||
mousemove: {
|
|
||||||
raycaster: "mousemove:raycaster",
|
|
||||||
},
|
|
||||||
resize: "resize",
|
|
||||||
};
|
|
||||||
|
|
@ -1,247 +0,0 @@
|
||||||
import * as THREE from "three";
|
|
||||||
import { AmbientLight, AxesHelper, Camera, PerspectiveCamera, Raycaster, Scene, SRGBColorSpace, Vector2, WebGLRenderer } from "three";
|
|
||||||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
|
|
||||||
import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer.js";
|
|
||||||
import { Tween } from "three/examples/jsm/libs/tween.module.js";
|
|
||||||
import Stats from "three/examples/jsm/libs/stats.module.js";
|
|
||||||
import Events from "./Events";
|
|
||||||
import { threeStore } from "@/store";
|
|
||||||
|
|
||||||
export type Animate = {
|
|
||||||
fun: (arg: any) => any;
|
|
||||||
content: any;
|
|
||||||
};
|
|
||||||
export default class Viewer {
|
|
||||||
public id: string;
|
|
||||||
public viewerDom!: HTMLElement;
|
|
||||||
public renderer!: WebGLRenderer;
|
|
||||||
public scene!: Scene;
|
|
||||||
public camera!: PerspectiveCamera;
|
|
||||||
public controls!: OrbitControls;
|
|
||||||
public css2Renderer: CSS2DRenderer | undefined;
|
|
||||||
public raycaster!: Raycaster;
|
|
||||||
public mouse!: Vector2;
|
|
||||||
public isDestroy = false; // 是否销毁
|
|
||||||
public animateEventList = new Map();
|
|
||||||
public statsControls!: Stats; // 性能监测
|
|
||||||
public raycasterObjects: THREE.Object3D[] = [];
|
|
||||||
public tween!: Tween<THREE.Vector3>;
|
|
||||||
public mouseEvent: MouseEvent | undefined;
|
|
||||||
|
|
||||||
constructor(id: string) {
|
|
||||||
this.id = id;
|
|
||||||
this.initViewer();
|
|
||||||
}
|
|
||||||
|
|
||||||
private initViewer() {
|
|
||||||
this.initRenderer(); //创建渲染器
|
|
||||||
this.initScene(); //创建场景
|
|
||||||
this.initLight(); // 创建光源
|
|
||||||
this.initCamera(); // 创建相机
|
|
||||||
this.initControl(); // 创建控制器
|
|
||||||
// this.initCss2Renderer(); // 创建css2渲染器
|
|
||||||
|
|
||||||
this.raycaster = new Raycaster(); // 创建射线
|
|
||||||
this.mouse = new Vector2(); // 创建鼠标位置
|
|
||||||
|
|
||||||
const animate = () => {
|
|
||||||
if (this.isDestroy) return;
|
|
||||||
requestAnimationFrame(animate);
|
|
||||||
// TWEEN.update(); // 更新动画
|
|
||||||
// this.css2Renderer?.render(this.scene, this.camera);
|
|
||||||
this.updateDom();
|
|
||||||
this.renderDom();
|
|
||||||
|
|
||||||
// 全局的公共动画函数,添加函数可同步执行
|
|
||||||
this.animateEventList.forEach((event) => {
|
|
||||||
// console.log("animateEventList");
|
|
||||||
if (event.fun && event.content) event.fun(event.content);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
animate();
|
|
||||||
}
|
|
||||||
|
|
||||||
private initRenderer() {
|
|
||||||
// 获取画布dom
|
|
||||||
this.viewerDom = document.getElementById(this.id) as HTMLElement;
|
|
||||||
// 初始化渲染器
|
|
||||||
this.renderer = new WebGLRenderer({
|
|
||||||
logarithmicDepthBuffer: true, //true/false用于启用或禁用对数深度缓冲区。
|
|
||||||
antialias: true, // true/false表示是否开启反锯齿
|
|
||||||
alpha: true, // true/false 表示是否可以设置背景色透明
|
|
||||||
precision: "mediump", // highp/mediump/lowp 表示着色精度选择
|
|
||||||
premultipliedAlpha: true // true/false 表示是否可以设置像素深度(用来度量图像的分辨率)
|
|
||||||
// preserveDrawingBuffer: false, // true/false 表示是否保存绘图缓冲
|
|
||||||
// physicallyCorrectLights: true, // true/false 表示是否开启物理光照
|
|
||||||
});
|
|
||||||
this.renderer.clearDepth(); //清除深度缓冲区。在渲染之前,这通常用于重置深度缓冲区,以确保正确的深度测试
|
|
||||||
|
|
||||||
// 开启模型对象的局部剪裁平面功能
|
|
||||||
// 如果不设置为true,设置剪裁平面的模型不会被剪裁
|
|
||||||
this.renderer.localClippingEnabled = true;
|
|
||||||
|
|
||||||
this.renderer.shadowMap.enabled = true;
|
|
||||||
this.renderer.outputColorSpace = SRGBColorSpace; // 可以看到更亮的材质,同时这也影响到环境贴图。
|
|
||||||
this.viewerDom.appendChild(this.renderer.domElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
private initScene() {
|
|
||||||
this.scene = new Scene();
|
|
||||||
}
|
|
||||||
|
|
||||||
private initLight() {
|
|
||||||
const ambient = new AmbientLight(0xffffff, 0.6);
|
|
||||||
this.scene.add(ambient);
|
|
||||||
|
|
||||||
const light = new THREE.DirectionalLight(0xffffff);
|
|
||||||
light.position.set(0, 200, 100);
|
|
||||||
light.castShadow = true;
|
|
||||||
|
|
||||||
light.shadow.camera.top = 180;
|
|
||||||
light.shadow.camera.bottom = -100;
|
|
||||||
light.shadow.camera.left = -120;
|
|
||||||
light.shadow.camera.right = 400;
|
|
||||||
light.shadow.camera.near = 0.1;
|
|
||||||
light.shadow.camera.far = 400;
|
|
||||||
// 设置mapSize属性可以使阴影更清晰,不那么模糊
|
|
||||||
light.shadow.mapSize.set(1024, 1024);
|
|
||||||
light.name = "initLight";
|
|
||||||
|
|
||||||
this.scene.add(light);
|
|
||||||
}
|
|
||||||
|
|
||||||
private initCamera() {
|
|
||||||
// 渲染相机
|
|
||||||
this.camera = new PerspectiveCamera(25, this.viewerDom.clientWidth / this.viewerDom.clientHeight, 1, 2000);
|
|
||||||
//设置相机位置
|
|
||||||
// this.camera.position.set(5, 1, -5);
|
|
||||||
this.camera.position.set(4, 2, -3);
|
|
||||||
//设置相机方向
|
|
||||||
this.camera.lookAt(0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private initControl() {
|
|
||||||
this.controls = new OrbitControls(this.camera as Camera, this.renderer?.domElement);
|
|
||||||
this.controls.enabled = true; // 控制器关
|
|
||||||
this.controls.enableDamping = false;
|
|
||||||
this.controls.screenSpacePanning = false; // 定义平移时如何平移相机的位置 控制不上下移动
|
|
||||||
this.controls.minDistance = 2;
|
|
||||||
this.controls.maxDistance = 1000;
|
|
||||||
this.controls.addEventListener("change", () => {
|
|
||||||
this.renderer.render(this.scene, this.camera);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private initCss2Renderer() {
|
|
||||||
this.css2Renderer = new CSS2DRenderer();
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateDom() {
|
|
||||||
this.controls.update();
|
|
||||||
// // 更新参数
|
|
||||||
this.camera.aspect = this.viewerDom.clientWidth / this.viewerDom.clientHeight; // 摄像机视锥体的长宽比,通常是使用画布的宽/画布的高
|
|
||||||
this.camera.updateProjectionMatrix(); // 更新摄像机投影矩阵。在任何参数被改变以后必须被调用,来使得这些改变生效
|
|
||||||
this.renderer.setSize(this.viewerDom.clientWidth, this.viewerDom.clientHeight);
|
|
||||||
this.renderer.setPixelRatio(window.devicePixelRatio); // 设置设备像素比
|
|
||||||
}
|
|
||||||
|
|
||||||
// 渲染dom
|
|
||||||
private renderDom() {
|
|
||||||
this.renderer?.render(this.scene as Scene, this.camera as Camera);
|
|
||||||
}
|
|
||||||
|
|
||||||
private statsUpdate(statsControls: any) {
|
|
||||||
statsControls.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**坐标轴辅助 */
|
|
||||||
public addAxis() {
|
|
||||||
const axis = new AxesHelper(1000); //创建坐标轴辅助对象
|
|
||||||
this.scene?.add(axis);
|
|
||||||
}
|
|
||||||
|
|
||||||
public addAnimate(id: string, animate: Animate) {
|
|
||||||
this.animateEventList.set(id, animate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public addStats() {
|
|
||||||
if (!this.statsControls) this.statsControls = new Stats();
|
|
||||||
this.statsControls.dom.style.position = "absolute";
|
|
||||||
this.viewerDom.appendChild(this.statsControls.dom);
|
|
||||||
|
|
||||||
// 添加到动画
|
|
||||||
this.addAnimate("stats", { fun: this.statsUpdate, content: this.statsControls });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**自定义鼠标事件触发的范围,给定一个模型组,对给定的模型组鼠标事件才生效 */
|
|
||||||
public setRaycasterObjects(objList: THREE.Object3D[]): void {
|
|
||||||
this.raycasterObjects = objList;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**注册鼠标事件监听 */
|
|
||||||
public initRaycaster() {
|
|
||||||
this.raycaster = new Raycaster();
|
|
||||||
|
|
||||||
const initRaycasterEvent: Function = (eventName: keyof HTMLElementEventMap): void => {
|
|
||||||
//这里的container就是画布所在的div,也就是说,这个是要拿整个scene所在的容器来界定的
|
|
||||||
let getBoundingClientRect = this.viewerDom.getBoundingClientRect();
|
|
||||||
let offsetWidth = this.viewerDom.offsetWidth;
|
|
||||||
let offsetHeight = this.viewerDom.offsetHeight;
|
|
||||||
const funWrap = _.throttle((event: any) => {
|
|
||||||
this.mouseEvent = {
|
|
||||||
...event, //真正的鼠标相对于画布的位置
|
|
||||||
x: event.clientX - getBoundingClientRect.left,
|
|
||||||
y: event.clientY - getBoundingClientRect.top
|
|
||||||
};
|
|
||||||
this.mouse.x = ((event.clientX - getBoundingClientRect.left) / offsetWidth) * 2 - 1;
|
|
||||||
this.mouse.y = -((event.clientY - getBoundingClientRect.top) / offsetHeight) * 2 + 1;
|
|
||||||
threeStore.setRaycasterIntersectObjects(this.getRaycasterIntersectObjects());
|
|
||||||
}, 100);
|
|
||||||
this.viewerDom.addEventListener(eventName, funWrap, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
initRaycasterEvent("click");
|
|
||||||
// initRaycasterEvent("dblclick");
|
|
||||||
// initRaycasterEvent("mousemove");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取射线与物体相交的物体
|
|
||||||
private getRaycasterIntersectObjects(): THREE.Intersection[] {
|
|
||||||
// 如果射线与物体相交的数组为空,则返回空数组
|
|
||||||
if (!this.raycasterObjects.length) return [];
|
|
||||||
// 设置射线从相机发出,经过鼠标位置
|
|
||||||
this.raycaster.setFromCamera(this.mouse, this.camera);
|
|
||||||
// 返回射线与物体相交的数组
|
|
||||||
return this.raycaster.intersectObjects(this.raycasterObjects, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public addCameraTween(targetPosition = new THREE.Vector3(1, 1, 1), duration = 1000, onComplete: () => void) {
|
|
||||||
this.initCameraTween();
|
|
||||||
this.tween.to(targetPosition, duration).start().onComplete(onComplete);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化补间动画库tween
|
|
||||||
*/
|
|
||||||
public initCameraTween() {
|
|
||||||
if (!this.camera) return;
|
|
||||||
this.tween = new Tween(this.camera.position);
|
|
||||||
}
|
|
||||||
|
|
||||||
public destroy() {
|
|
||||||
this.scene.traverse((child: any) => {
|
|
||||||
if (child.material) {
|
|
||||||
child.material.dispose();
|
|
||||||
}
|
|
||||||
if (child.geometry) {
|
|
||||||
child.geometry.dispose();
|
|
||||||
}
|
|
||||||
child = null;
|
|
||||||
});
|
|
||||||
this.renderer.forceContextLoss();
|
|
||||||
this.renderer.dispose();
|
|
||||||
this.scene.clear();
|
|
||||||
|
|
||||||
this.isDestroy = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
import React, { useEffect, useRef } from "react";
|
||||||
|
import styles from "../../index.less";
|
||||||
|
|
||||||
|
interface OwnProps {}
|
||||||
|
|
||||||
|
type Props = OwnProps;
|
||||||
|
|
||||||
|
const Index: React.FC<Props> = (props) => {
|
||||||
|
const itemRef = useRef<HTMLElement[]>([]);
|
||||||
|
const ob = new IntersectionObserver((entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
const img: any = entry.target;
|
||||||
|
img.src = img.dataset.src;
|
||||||
|
ob.unobserve(entry.target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
if (itemRef.current) {
|
||||||
|
_.forEach(itemRef.current, (item) => {
|
||||||
|
ob.observe(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (itemRef.current) {
|
||||||
|
_.forEach(itemRef.current, (item) => {
|
||||||
|
ob.unobserve(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [itemRef.current]);
|
||||||
|
return (
|
||||||
|
<ul className={styles.lazyloadImg}>
|
||||||
|
{_.map(
|
||||||
|
Array.from({ length: 200 }, () => Math.floor(Math.random() * 100)),
|
||||||
|
(item, index) => (
|
||||||
|
<li key={index}>
|
||||||
|
<img data-src={`https://picsum.photos/300/300?random=${index}`} alt="" src={require("../default.jpeg")} ref={(el) => (itemRef.current[index] = el as HTMLElement)} />
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Index;
|
||||||
|
After Width: | Height: | Size: 8.0 KiB |
|
|
@ -0,0 +1,43 @@
|
||||||
|
import React from "react";
|
||||||
|
import SlideInItem from "./slideInItem";
|
||||||
|
|
||||||
|
interface OwnProps {}
|
||||||
|
|
||||||
|
type Props = OwnProps;
|
||||||
|
|
||||||
|
const list = [
|
||||||
|
{ id: 1, bg: "red" },
|
||||||
|
{ id: 2, bg: "blue" },
|
||||||
|
{ id: 3, bg: "green" },
|
||||||
|
{ id: 4, bg: "yellowgreen" },
|
||||||
|
{ id: 5, bg: "orange" },
|
||||||
|
{ id: 6, bg: "pink" },
|
||||||
|
{ id: 7, bg: "antiquewhite" },
|
||||||
|
{ id: 8, bg: "darkseagreen" },
|
||||||
|
{ id: 9, bg: "purple" },
|
||||||
|
{ id: 10, bg: "red" },
|
||||||
|
{ id: 11, bg: "black" }
|
||||||
|
];
|
||||||
|
const Index: React.FC<Props> = (props) => {
|
||||||
|
const map = new WeakMap();
|
||||||
|
const ob = new IntersectionObserver((entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
const animation = map.get(entry.target);
|
||||||
|
if (animation) {
|
||||||
|
animation.play();
|
||||||
|
ob.unobserve(entry.target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{list.map((item, index) => {
|
||||||
|
return <SlideInItem key={index} item={item} ob={ob} map={map}></SlideInItem>;
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Index;
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
import React, { useEffect, useRef } from "react";
|
||||||
|
import styles from "../index.less";
|
||||||
|
|
||||||
|
interface OwnProps {
|
||||||
|
item: any;
|
||||||
|
ob: any;
|
||||||
|
map: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = OwnProps;
|
||||||
|
const SLIDE_FADE_DISTANCE = 50;
|
||||||
|
const SLIDE_FADE_DURATION = 1000;
|
||||||
|
|
||||||
|
const isBelowViewport = (el: HTMLDivElement) => {
|
||||||
|
const rect = el.getBoundingClientRect();
|
||||||
|
return rect.top - SLIDE_FADE_DISTANCE > window.innerHeight;
|
||||||
|
};
|
||||||
|
const slideInItem: React.FC<Props> = (props) => {
|
||||||
|
const { item, ob, map } = props;
|
||||||
|
const itemRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (itemRef.current && ob) {
|
||||||
|
if (!isBelowViewport(itemRef.current)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const animation = itemRef.current.animate(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
transform: `translateY(${SLIDE_FADE_DISTANCE}px)`,
|
||||||
|
opacity: 0.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
transform: `translateY(0)`,
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
{
|
||||||
|
duration: SLIDE_FADE_DURATION,
|
||||||
|
easing: "ease-in-out",
|
||||||
|
fill: "forwards" // 当动画完成后,保留最后一个关键帧的样式
|
||||||
|
}
|
||||||
|
);
|
||||||
|
animation.pause();
|
||||||
|
map.set(itemRef.current, animation);
|
||||||
|
ob.observe(itemRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ob.unobserve(itemRef.current);
|
||||||
|
};
|
||||||
|
}, [itemRef.current]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.block} ref={itemRef} style={{ backgroundColor: item.bg }}>
|
||||||
|
<h3>The most popular component library</h3>
|
||||||
|
|
||||||
|
<h5>for Tailwind CSS</h5>
|
||||||
|
<p>daisyUI adds component class names to Tailwind CSS so you can make beautiful websites faster than ever.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default slideInItem;
|
||||||
|
|
@ -46,7 +46,7 @@ export function renderCols(initialState: any[], type: string, i18n?: AnyObject,
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case "paymentOrganization":
|
case "paymentOrganization":
|
||||||
col = { ...col, width: 200 };
|
col = { ...col, width: g.width || 200 };
|
||||||
break;
|
break;
|
||||||
case "socialNum":
|
case "socialNum":
|
||||||
case "otherNum":
|
case "otherNum":
|
||||||
|
|
@ -434,6 +434,72 @@ export function renderCols(initialState: any[], type: string, i18n?: AnyObject,
|
||||||
return col;
|
return col;
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
} else if (type === "attendanceView") {
|
||||||
|
return [
|
||||||
|
..._.map(initialState, (g) => {
|
||||||
|
let col = { ...g, ellipsis: true };
|
||||||
|
switch (g.dataIndex) {
|
||||||
|
case "operate":
|
||||||
|
col = {
|
||||||
|
...col,
|
||||||
|
ellipsis: false,
|
||||||
|
width: 120,
|
||||||
|
render: (__: string, record: any) => {
|
||||||
|
return (
|
||||||
|
<Space>
|
||||||
|
{_.map(col?.operateType, (o) => (
|
||||||
|
<Button key={o.key} type="link" onClick={() => postMessageToParent(o.key, record)}>
|
||||||
|
{o.label}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
col = { ...col };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return col;
|
||||||
|
})
|
||||||
|
];
|
||||||
|
} else if (type === "declare") {
|
||||||
|
return [
|
||||||
|
..._.map(initialState, (g) => {
|
||||||
|
let col = { ...g, ellipsis: true };
|
||||||
|
switch (g.dataIndex) {
|
||||||
|
case "operate":
|
||||||
|
col = {
|
||||||
|
...col,
|
||||||
|
ellipsis: false,
|
||||||
|
width: 120,
|
||||||
|
render: (__: string, record: any) => {
|
||||||
|
return (
|
||||||
|
<Space>
|
||||||
|
{_.map(col?.operateType, (o) => (
|
||||||
|
<Button key={o.key} type="link" onClick={() => postMessageToParent(o.key, record)}>
|
||||||
|
{o.label}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
col = { ...col };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return col;
|
||||||
|
})
|
||||||
|
];
|
||||||
|
} else if (type === "cusTitle") {
|
||||||
|
return [
|
||||||
|
..._.map(initialState, (g) => {
|
||||||
|
return { ...g, title: <span dangerouslySetInnerHTML={{ __html: g.title }} /> };
|
||||||
|
})
|
||||||
|
];
|
||||||
}
|
}
|
||||||
return initialState;
|
return initialState;
|
||||||
}, [initialState, type, i18n, extraParams]);
|
}, [initialState, type, i18n, extraParams]);
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,12 @@ import { appStore } from "./AppStore";
|
||||||
import { calculateStore } from "./CalculateStore";
|
import { calculateStore } from "./CalculateStore";
|
||||||
import { baseLayoutStore } from "@/layouts/BaseLayout/Store";
|
import { baseLayoutStore } from "@/layouts/BaseLayout/Store";
|
||||||
import { hiprintStore } from "@/store/HiprintStore";
|
import { hiprintStore } from "@/store/HiprintStore";
|
||||||
import { threeStore } from "./threeStore";
|
|
||||||
|
|
||||||
export { baseLayoutStore, appStore, calculateStore, hiprintStore, threeStore };
|
export { baseLayoutStore, appStore, calculateStore, hiprintStore };
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
baseLayoutStore,
|
baseLayoutStore,
|
||||||
calculateStore,
|
calculateStore,
|
||||||
appStore,
|
appStore,
|
||||||
hiprintStore,
|
hiprintStore
|
||||||
threeStore
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
import { action, makeObservable, observable } from "mobx";
|
|
||||||
import { Intersection } from "three";
|
|
||||||
|
|
||||||
export class ThreeStore {
|
|
||||||
constructor() {
|
|
||||||
makeObservable(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@observable raycasterObjects: Intersection[] = [];
|
|
||||||
@action setRaycasterIntersectObjects = (objects: Intersection[]) => (this.raycasterObjects = objects);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const threeStore = new ThreeStore();
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
import type { Object3D, Material, Color } from "three";
|
|
||||||
export interface MaterialExtends extends Material {
|
|
||||||
color?: Color;
|
|
||||||
warningColor?: Color;
|
|
||||||
}
|
|
||||||
export type ModelExtendsData = {
|
|
||||||
warn?: boolean;
|
|
||||||
name: string;
|
|
||||||
[key: string]: any;
|
|
||||||
};
|
|
||||||
export interface Object3DExtends extends Object3D {
|
|
||||||
isGroup?: boolean;
|
|
||||||
isMesh?: boolean;
|
|
||||||
material?: MaterialExtends;
|
|
||||||
oldMaterial?: MaterialExtends;
|
|
||||||
addData?: ModelExtendsData;
|
|
||||||
dom?: HTMLElement;
|
|
||||||
}
|
|
||||||
|
|
@ -71,6 +71,7 @@ export const exceptStr = (str) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const paginationFun = (tableListPageObj, sizeChange, onChange, i18n = {}) => {
|
export const paginationFun = (tableListPageObj, sizeChange, onChange, i18n = {}) => {
|
||||||
|
const pageSizeOptions= tableListPageObj?.pageSizeOptions || ["10", "20", "50", "100"];
|
||||||
return {
|
return {
|
||||||
current: tableListPageObj.pageNum || tableListPageObj.current,
|
current: tableListPageObj.pageNum || tableListPageObj.current,
|
||||||
pageSize: tableListPageObj.size || tableListPageObj.pageSize,
|
pageSize: tableListPageObj.size || tableListPageObj.pageSize,
|
||||||
|
|
@ -78,7 +79,7 @@ export const paginationFun = (tableListPageObj, sizeChange, onChange, i18n = {})
|
||||||
showTotal: total => `${i18n["共"] ? i18n["共"] : "共"} ${total} ${i18n["条"] ? i18n["条"] : "条"}`,
|
showTotal: total => `${i18n["共"] ? i18n["共"] : "共"} ${total} ${i18n["条"] ? i18n["条"] : "条"}`,
|
||||||
showQuickJumper: true,
|
showQuickJumper: true,
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
pageSizeOptions: ["10", "20", "50", "100"],
|
pageSizeOptions,
|
||||||
onShowSizeChange: (page, size) => {
|
onShowSizeChange: (page, size) => {
|
||||||
const { total } = tableListPageObj;
|
const { total } = tableListPageObj;
|
||||||
sizeChange({
|
sizeChange({
|
||||||
|
|
@ -122,4 +123,4 @@ export const toDecimal_n = (num, decimalPlaces) => {
|
||||||
const multiplier = Math.pow(10, decimalPlaces);
|
const multiplier = Math.pow(10, decimalPlaces);
|
||||||
const roundedNum = Math.round(num * multiplier) / multiplier;
|
const roundedNum = Math.round(num * multiplier) / multiplier;
|
||||||
return roundedNum.toFixed(decimalPlaces);
|
return roundedNum.toFixed(decimalPlaces);
|
||||||
};
|
};
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
import type { Object3D } from "three";
|
|
||||||
|
|
||||||
export function checkNameIncludes(obj: Object3D, str: string): boolean {
|
|
||||||
return obj.name.includes(str);
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
declare module "lodash";
|
declare module 'lodash';
|
||||||
declare module "*.json";
|
declare module '*.json';
|
||||||
declare module "*.css";
|
declare module '*.css';
|
||||||
declare module "*.less";
|
declare module '*.less';
|
||||||
declare module "*.jpg";
|
declare module '*.jpg';
|
||||||
declare module "*.gif";
|
declare module '*.gif';
|
||||||
declare module "*.png";
|
declare module '*.png';
|
||||||
declare module "*.svg" {
|
declare module '*.svg' {
|
||||||
export function ReactComponent(props: React.SVGProps<SVGSVGElement>): React.ReactElement;
|
export function ReactComponent(
|
||||||
|
props: React.SVGProps<SVGSVGElement>,
|
||||||
|
): React.ReactElement;
|
||||||
const url: string;
|
const url: string;
|
||||||
export default url;
|
export default url;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare const _;
|
declare const _;
|
||||||
declare var AMap: any;
|
|
||||||
|
|
||||||
declare var $: JQueryStatic;
|
declare var $: JQueryStatic;
|
||||||
|
|
|
||||||