全部提交一波
This commit is contained in:
1531
src/assets/OrbitControls.js
Normal file
1531
src/assets/OrbitControls.js
Normal file
File diff suppressed because it is too large
Load Diff
239
src/views/AppCarouselList.vue
Normal file
239
src/views/AppCarouselList.vue
Normal file
@@ -0,0 +1,239 @@
|
||||
<script>
|
||||
export default {
|
||||
name: "AppCarouselList",
|
||||
label: "分柜监控",
|
||||
components: {
|
||||
HlsPlayer: {
|
||||
render: (h) => h('div', {style: {width: '100%', height: '100%'}}),
|
||||
props: {
|
||||
url: {default: "https://open.ys7.com/v3/openlive/155715496_1_1.m3u8?expire=1747359002&id=712960386311127040&t=c9c6ad362940b1fb4ea7a736cec78980aa9ad1d27d6e3eddf75788c0564e9d7b&ev=100"}
|
||||
},
|
||||
mounted() {
|
||||
const {Clappr} = window
|
||||
if (Clappr && this.url) {
|
||||
const player = new Clappr.Player({
|
||||
source: this.url,
|
||||
mute: true, //静音为true
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
// poster:'http://clappr.io/poster.png', //设置封面图
|
||||
autoPlay: true,
|
||||
disableCanAutoPlay: true, //禁用检测浏览器是否可以自动播放视频
|
||||
hideMediaControl: true, //禁用媒体控制自动隐藏
|
||||
hideMediaControlDelay: 100, //更改默认的媒体控件自动隐藏超时值
|
||||
hideVolumeBar: true, //当嵌入的宽度小于320时,音量条将被隐藏
|
||||
exitFullscreenOnEnd: false, //禁用播放器将在媒体结束时自动退出全屏显示,即播放结束后不会退出全屏
|
||||
mediacontrol: {seekbar: "#000", buttons: "#FFF"}, //定义进度条和底部暂停等图标的颜色
|
||||
events: {
|
||||
onError: function () { //当播放出错时
|
||||
alert("播放出错!")
|
||||
},
|
||||
}
|
||||
});
|
||||
player.attachTo(this.$el);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
height: '600px',
|
||||
search: {
|
||||
type: '1',
|
||||
categoryId: '',
|
||||
hourNum: ""
|
||||
},
|
||||
stores: [],
|
||||
cameras: [],
|
||||
storeKeyGoods: [],
|
||||
categorySales: [],
|
||||
columns: {
|
||||
品类销售情况: [
|
||||
{label: "品类", prop: "secondCategoryName"},
|
||||
{label: "销售额", prop: "currentSaleAmt", width: '70px'},
|
||||
{label: "库存金额", prop: "currentStockAmt", width: '80px'},
|
||||
{label: "同/环比销售额", prop: "compareSaleAmt", width: '70px'},
|
||||
{label: "同/环比库存金额", prop: "compareStockAmt", width: '70px'},
|
||||
{label: "前四周日军销售额", prop: "avg4WeekSaleAmt", width: '70px'},
|
||||
],
|
||||
重点单品情况: [
|
||||
{label: "重点单品", prop: "name"},
|
||||
{label: "当日目标", prop: "targetNum", width: "70px"},
|
||||
{label: "销售数量", prop: "saleNum", width: "70px"},
|
||||
{label: "库存数量", prop: "stockNum", width: "70px"},
|
||||
{label: "剩余时间预计销售数量", prop: "preSaleNum"},
|
||||
{label: "提醒", custom: 1, width: 70, align: 'center', prop: "remind"},
|
||||
]
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
refs: v => v.$parent.getItemRefs(),
|
||||
storeList: v => {
|
||||
const list = []
|
||||
let group = []
|
||||
for (const e of v.stores) {
|
||||
if (group.length < 4) {
|
||||
group.push(e)
|
||||
} else {
|
||||
list.push(group.reverse())
|
||||
group = [e]
|
||||
}
|
||||
}
|
||||
if (group.length > 0) list.push(group.reverse())
|
||||
return list
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getData() {
|
||||
const {$http, $waitFor} = window
|
||||
console.log("筛选条件:", this.search)
|
||||
$waitFor($http).then(() => this.getStores())
|
||||
.then(() => Promise.all([this.getCameras(), this.getStoreKeyGoods(), this.getCategorySales()]))
|
||||
.then(() => {
|
||||
this.stores = this.stores?.map(storeCode => {
|
||||
const {storeCameraVOList = [], storeName} = this.cameras.find(e => e.storeCode == storeCode) || {}
|
||||
const keyGoods = this.storeKeyGoods.filter(e => e.storeCode == storeCode) || []
|
||||
const categorySale = this.categorySales.filter(e => e.storeCode == storeCode) || []
|
||||
return {storeCode, storeName, camera: storeCameraVOList.map(e => e.cameraUrl), keyGoods, categorySale}
|
||||
}) || []
|
||||
})
|
||||
},
|
||||
getStores() {
|
||||
return $http.get(`/data-boot/ca/screen/scStoreInfo/group/${this.search.groupCodeList}`).then(res => {
|
||||
if (res?.data) {
|
||||
this.stores = res.data || []
|
||||
}
|
||||
})
|
||||
},
|
||||
getCameras() {
|
||||
return $http.post("/data-boot/la/screen/multipleStoreBoard/storeCamera", {
|
||||
...this.search,
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
this.cameras = res.data?.records || []
|
||||
}
|
||||
})
|
||||
},
|
||||
getStoreKeyGoods() {
|
||||
return $http.post("/data-boot/la/screen/multipleStoreBoard/storeKeyGoods", {
|
||||
...this.search,
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
this.storeKeyGoods = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
getCategorySales() {
|
||||
return $http.post("/data-boot/la/screen/multipleStoreBoard/categorySale", {
|
||||
...this.search,
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
this.categorySales = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
gotoDetail(store) {
|
||||
$glob.query = {storeCode: store.storeCode}
|
||||
const sid = "9f299712-5549-413b-a93b-7c3e3b5bfadb"
|
||||
$glob.group = sid
|
||||
this.refs["ff6a5ca5-8cca-4fad-8058-a2ab2388236c"].$refs.main.groupId = sid
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.height = `${this.$el.clientHeight}px`
|
||||
this.getData()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="AppCarouselList">
|
||||
<div class="layout">
|
||||
<div class="store" v-for="store in storeList" :key="store.storeCode">
|
||||
<div class="header" v-text="store.storeName" @click="gotoDetail(store)"/>
|
||||
<el-carousel indicator-position="none" height="250px">
|
||||
<el-carousel-item v-for="(url,j) in store.camera" :key="[i,j].join('_')">
|
||||
<hls-player :url="url"/>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
<div class="subTitle" v-text="'品类销售情况'"/>
|
||||
<el-table :data="store.categorySale" size="mini" header-cell-class-name="tableHeader" cell-class-name="tableCell" max-height="275px">
|
||||
<el-table-column v-for="column in columns.品类销售情况" v-bind="column" :key="column.prop"/>
|
||||
</el-table>
|
||||
<div class="subTitle" v-text="'重点单品情况'"/>
|
||||
<el-table :data="store.keyGoods" size="mini" header-cell-class-name="tableHeader" cell-class-name="tableCell" max-height="275px">
|
||||
<el-table-column v-for="column in columns.重点单品情况" v-bind="column" :key="column.prop">
|
||||
<template v-slot="{row}">
|
||||
<div v-if="column.custom" :style="{color: row.preSaleNum > row.stockNum ? 'red' : '#fff'}" v-text="'周边库存情况'"/>
|
||||
<span v-else v-text="row[column.prop] || ''"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.AppCarouselList {
|
||||
color: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tableHeader {
|
||||
background: rgba(13, 48, 99, 0.6) !important;
|
||||
color: #fff;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.AppCarouselList .el-table tr {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.AppCarouselList .el-table {
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.AppCarouselList .el-table:before {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.tableCell {
|
||||
color: #fff;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.AppCarouselList .header {
|
||||
height: 48px;
|
||||
padding: 8px 0 8px 38px;
|
||||
margin-bottom: 24px;
|
||||
box-sizing: border-box;
|
||||
line-height: 32px;
|
||||
background-image: url("http://10.0.97.209/img/kengee/kengee4.png");
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.AppCarouselList .subTitle {
|
||||
line-height: 20px;
|
||||
width: fit-content;
|
||||
margin: 24px auto 12px;
|
||||
background-image: url("http://10.0.97.209/img/kengee/kengee5.png");
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 2px;
|
||||
background-position: center bottom;
|
||||
}
|
||||
|
||||
.AppCarouselList .layout {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.AppCarouselList .store {
|
||||
width: calc(33% - 16px);
|
||||
}
|
||||
</style>
|
||||
@@ -46,7 +46,7 @@ export default {
|
||||
|
||||
<template>
|
||||
<section class="AppKgTable">
|
||||
<div class="subTiltle" v-text="'表格标题'"/>
|
||||
<div class="subTitle" v-text="'表格标题'"/>
|
||||
<el-table :data="tableData" size="mini" header-cell-class-name="tableHeader" cell-class-name="tableCell" max-height="440px" show-summary stripe>
|
||||
<table-column v-for="(column,i) in columns" :key="i" :column="column"/>
|
||||
</el-table>
|
||||
@@ -110,7 +110,7 @@ export default {
|
||||
background-color: rgba(255, 255, 255, .1) !important;
|
||||
}
|
||||
|
||||
.AppKgTable .subTiltle {
|
||||
.AppKgTable .subTitle {
|
||||
line-height: 20px;
|
||||
width: fit-content;
|
||||
margin: 24px auto 12px;
|
||||
|
||||
63
src/views/AppTable.vue
Normal file
63
src/views/AppTable.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<script>
|
||||
export default {
|
||||
name: "AppTable",
|
||||
label: "原生表格",
|
||||
data() {
|
||||
return {
|
||||
tableData: [],
|
||||
columns: [
|
||||
{label: "", prop: ""}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getCellStyle(config) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="AppTable">
|
||||
<table cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="(cell,i) in columns" v-bind="cell.config" :key="i">
|
||||
<div v-html="cell.label"/>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(row,i) in tableData" :key="i">
|
||||
<td :style="getCellStyle(cell.config)" v-for="(cell,j) in row" v-bind="cell.config" :key="j">
|
||||
<slot v-if="cell.slot&&$slots[cell.slot]" :name="cell.slot"/>
|
||||
<div v-else style="font-size: 16px;" v-html="cell.label"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.AppTable table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.AppTable table tbody tr:nth-of-type(2n) {
|
||||
--odd-bg: rgba(13, 48, 99, 0.3);
|
||||
background-image: linear-gradient(
|
||||
-45deg, var(--odd-bg) 0, var(--odd-bg) 10%, transparent 10%, transparent 50%,
|
||||
var(--odd-bg) 50%, var(--odd-bg) 60%, transparent 60%, transparent);
|
||||
background-size: 20px 20px;
|
||||
}
|
||||
|
||||
|
||||
.AppTable table td {
|
||||
padding: 8px 16px;
|
||||
border: 1px solid black;
|
||||
box-sizing: border-box
|
||||
}
|
||||
</style>
|
||||
205
src/views/AppThreeMap.vue
Normal file
205
src/views/AppThreeMap.vue
Normal file
@@ -0,0 +1,205 @@
|
||||
<script>
|
||||
import * as THREE from "https://cdn.bootcdn.net/ajax/libs/three.js/0.165.0/three.module.js"
|
||||
|
||||
export default {
|
||||
name: "AppThreeMap",
|
||||
label: "3D地图",
|
||||
mounted() {
|
||||
const scene = new THREE.Scene()
|
||||
const ambientLight = new THREE.AmbientLight(0xd4e7fd, 4);
|
||||
scene.add(ambientLight);
|
||||
const directionalLight = new THREE.DirectionalLight(0xe8eaeb, 0.2);
|
||||
directionalLight.position.set(0, 10, 5);
|
||||
const directionalLight2 = directionalLight.clone();
|
||||
directionalLight2.position.set(0, 10, -5);
|
||||
const directionalLight3 = directionalLight.clone();
|
||||
directionalLight3.position.set(5, 10, 0);
|
||||
const directionalLight4 = directionalLight.clone();
|
||||
directionalLight4.position.set(-5, 10, 0);
|
||||
scene.add(directionalLight);
|
||||
scene.add(directionalLight2);
|
||||
scene.add(directionalLight3);
|
||||
scene.add(directionalLight4);
|
||||
|
||||
const camera = new THREE.PerspectiveCamera(
|
||||
75,
|
||||
window.innerWidth / window.innerHeight,
|
||||
0.1,
|
||||
1000
|
||||
);
|
||||
camera.position.y = 8;
|
||||
camera.position.z = 8;
|
||||
|
||||
|
||||
const renderer = new THREE.WebGLRenderer({alpha: true});
|
||||
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
this.$el.appendChild(renderer.domElement);
|
||||
// const controls = new OrbitControls(camera, renderer.domElement);
|
||||
// controls.update();
|
||||
const animate = () => {
|
||||
renderer.render(scene, camera);
|
||||
requestAnimationFrame(animate);
|
||||
// controls.update();
|
||||
};
|
||||
window.addEventListener("resize", () => {
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
});
|
||||
|
||||
const url = "https://geo.datav.aliyun.com/areas_v3/bound/330000_full.json";
|
||||
fetch(url).then((res) => res.json())
|
||||
.then((data) => {
|
||||
const map = createMap(data);
|
||||
scene.add(map);
|
||||
|
||||
let intersect = null;
|
||||
window.addEventListener("pointerdown", (event) => {
|
||||
const mouse = new THREE.Vector2();
|
||||
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
|
||||
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
||||
const raycaster = new THREE.Raycaster();
|
||||
raycaster.setFromCamera(mouse, camera);
|
||||
const intersects = raycaster
|
||||
.intersectObjects(map.children)
|
||||
.filter((item) => item.object.type !== "Line");
|
||||
if (intersects.length > 0) {
|
||||
if (intersects[0].object.type === "Mesh") {
|
||||
if (intersect) isAplha(intersect, 1);
|
||||
intersect = intersects[0].object.parent;
|
||||
isAplha(intersect, 0.4);
|
||||
}
|
||||
if (intersects[0].object.type === "Sprite") {
|
||||
console.log(intersects[0].object);
|
||||
}
|
||||
} else {
|
||||
if (intersect) isAplha(intersect, 1);
|
||||
}
|
||||
|
||||
function isAplha(intersect, opacity) {
|
||||
intersect.children.forEach((item) => {
|
||||
if (item.type === "Mesh") {
|
||||
item.material.opacity = opacity;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const createMap = (data) => {
|
||||
const map = new THREE.Object3D();
|
||||
const center = data.features[0].properties.centroid;
|
||||
data.features.forEach((feature) => {
|
||||
const unit = new THREE.Object3D();
|
||||
const {centroid, center, name} = feature.properties;
|
||||
const {coordinates, type} = feature.geometry;
|
||||
const point = centroid || center || [0, 0];
|
||||
|
||||
const color = new THREE.Color(`hsl(
|
||||
${233},
|
||||
${Math.random() * 30 + 55}%,
|
||||
${Math.random() * 30 + 55}%)`).getHex();
|
||||
const depth = Math.random() * 0.3 + 0.3;
|
||||
|
||||
// const label = createLabel(name, point, depth);
|
||||
// const icon = createIcon(center, depth);
|
||||
|
||||
coordinates.forEach((coordinate) => {
|
||||
if (type === "MultiPolygon") coordinate.forEach((item) => fn(item));
|
||||
if (type === "Polygon") fn(coordinate);
|
||||
|
||||
function fn(coordinate) {
|
||||
unit.name = name;
|
||||
const mesh = createMesh(coordinate, color, depth);
|
||||
const line = createLine(coordinate, depth);
|
||||
unit.add(mesh, ...line);
|
||||
}
|
||||
});
|
||||
map.add(unit);
|
||||
setCenter(map);
|
||||
});
|
||||
return map;
|
||||
};
|
||||
const createMesh = (data, color, depth) => {
|
||||
const shape = new THREE.Shape();
|
||||
|
||||
const extrudeSettings = {
|
||||
depth: depth,
|
||||
bevelEnabled: false,
|
||||
};
|
||||
const materialSettings = {
|
||||
color: color,
|
||||
emissive: 0x000000,
|
||||
roughness: 0.45,
|
||||
metalness: 0.8,
|
||||
transparent: true,
|
||||
side: THREE.DoubleSide,
|
||||
};
|
||||
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
|
||||
const material = new THREE.MeshStandardMaterial(materialSettings);
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
|
||||
return mesh;
|
||||
};
|
||||
const createLine = (data, depth) => {
|
||||
const points = [];
|
||||
const lineGeometry = new THREE.BufferGeometry().setFromPoints(points);
|
||||
const uplineMaterial = new THREE.LineBasicMaterial({color: 0xffffff});
|
||||
const downlineMaterial = new THREE.LineBasicMaterial({color: 0xffffff});
|
||||
|
||||
const upLine = new THREE.Line(lineGeometry, uplineMaterial);
|
||||
const downLine = new THREE.Line(lineGeometry, downlineMaterial);
|
||||
downLine.position.z = -0.0001;
|
||||
upLine.position.z = depth + 0.0001;
|
||||
return [upLine, downLine];
|
||||
};
|
||||
const createLabel = (name, point, depth) => {
|
||||
const div = document.createElement("div");
|
||||
div.style.color = "#fff";
|
||||
div.style.fontSize = "12px";
|
||||
div.style.textShadow = "1px 1px 2px #047cd6";
|
||||
div.textContent = name;
|
||||
const label = new CSS2DObject(div);
|
||||
label.scale.set(0.01, 0.01, 0.01);
|
||||
// const [x, y] = offsetXY(point);
|
||||
// label.position.set(x, -y, depth);
|
||||
return label;
|
||||
};
|
||||
const createIcon = (point, depth) => {
|
||||
const url = new URL("../assets/icon.png", import.meta.url).href;
|
||||
const map = new THREE.TextureLoader().load(url);
|
||||
const material = new THREE.SpriteMaterial({
|
||||
map: map,
|
||||
transparent: true,
|
||||
});
|
||||
const sprite = new THREE.Sprite(material);
|
||||
const [x, y] = offsetXY(point);
|
||||
sprite.scale.set(0.3, 0.3, 0.3);
|
||||
|
||||
sprite.position.set(x, -y, depth + 0.2);
|
||||
sprite.renderOrder = 1;
|
||||
|
||||
return sprite;
|
||||
};
|
||||
const setCenter = (map) => {
|
||||
map.rotation.x = -Math.PI / 2;
|
||||
const box = new THREE.Box3().setFromObject(map);
|
||||
const center = box.getCenter(new THREE.Vector3());
|
||||
|
||||
const offset = [0, 0];
|
||||
map.position.x = map.position.x - center.x - offset[0];
|
||||
map.position.z = map.position.z - center.z - offset[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="AppThreeMap"/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.AppThreeMap{
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user