全部提交一波

This commit is contained in:
aixianling
2024-06-27 18:01:47 +08:00
parent f667bf2d69
commit f76e14a974
5 changed files with 2040 additions and 2 deletions

1531
src/assets/OrbitControls.js Normal file

File diff suppressed because it is too large Load Diff

View 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>

View File

@@ -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
View 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
View 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>