feat(hand-factory): 新增收货页面及发货计划API

新增收货页面功能,包含收货计划选择、钢卷号筛选、收货表单提交等
添加发货计划相关API接口,包括查询、新增、修改、删除和报表统计
This commit is contained in:
砂糖
2025-12-03 10:01:37 +08:00
parent 9221aa6276
commit 041ac52600
12 changed files with 826 additions and 293 deletions

View File

@@ -0,0 +1,59 @@
import request from '@/utils/request'
// 查询发货计划列表
export function listDeliveryPlan(query) {
return request({
url: '/wms/deliveryPlan/list',
method: 'get',
params: query
})
}
// 查询发货计划详细
export function getDeliveryPlan(planId) {
return request({
url: '/wms/deliveryPlan/' + planId,
method: 'get'
})
}
// 新增发货计划
export function addDeliveryPlan(data) {
return request({
url: '/wms/deliveryPlan',
method: 'post',
data: data
})
}
// 修改发货计划
export function updateDeliveryPlan(data) {
return request({
url: '/wms/deliveryPlan',
method: 'put',
data: data
})
}
// 删除发货计划
export function delDeliveryPlan(planId) {
return request({
url: '/wms/deliveryPlan/' + planId,
method: 'delete'
})
}
// 获取发货报表统计信息
/**
*
* @param {*} query.startTime 开始时间
* @param {*} query.endTime 结束时间
* @returns
*/
export function getDeliveryReport(query) {
return request({
url: '/wms/deliveryPlan/report',
method: 'get',
params: query
})
}

View File

@@ -0,0 +1,8 @@
<template>
</template>
<script>
</script>
<style>
</style>

View File

@@ -69,6 +69,12 @@
{
"navigationBarTitleText" : "查看钢卷"
}
},
{
"path": "pages/receive/receive",
"style": {
"navigationBarTitleText": "钢卷收货"
}
}
// {
// "path": "pages/register",
@@ -129,6 +135,7 @@
"navigationBarBackgroundColor": "#FFFFFF"
},
"tabBar": {
"selectedColor": "#17abe3",
"list": [
{
"text": "产线",
@@ -136,25 +143,30 @@
"selectedIconPath": "/static/images/tabbar/home_.png",
"iconPath": "/static/images/tabbar/home.png"
},
// {
// "text": "扫码",
// "pagePath": "pages/code/code",
// "selectedIconPath": "/static/images/tabbar/work_.png",
// "iconPath": "/static/images/tabbar/work.png"
// },
{
"text": "扫码",
"pagePath": "pages/code/code",
"pagePath": "pages/easycode/easycode",
"selectedIconPath": "/static/images/tabbar/work_.png",
"iconPath": "/static/images/tabbar/work.png"
},
{
"text": "收货",
"pagePath": "pages/receive/receive",
"selectedIconPath": "/static/images/tabbar/receive_.png",
"iconPath": "/static/images/tabbar/receive.png"
},
{
"text": "我的",
"pagePath": "pages/mine/index",
"selectedIconPath": "/static/images/tabbar/mine_.png",
"iconPath": "/static/images/tabbar/mine.png"
}
// {
// "text": "扫码2",
// "pagePath": "pages/easycode/easycode",
// "selectedIconPath": "/static/images/tabbar/work_.png",
// "iconPath": "/static/images/tabbar/work.png"
// }
]
},
"easycom": {

View File

@@ -104,13 +104,6 @@
</view>
</view>
</uni-popup>
<!-- 退出登录按钮固定在底部 -->
<view class="logout-container">
<button @click='handleLogout' class="logout-btn">
退出登录
</button>
</view>
</view>
</template>

View File

@@ -1,5 +1,4 @@
<template>
<!-- 模板内容保持不变注释部分可根据需求保留 -->
</template>
<script>
@@ -13,12 +12,6 @@ export default {
// 已跳转过则不再执行逻辑
if (this.hasJumped) return;
// 显示加载状态,提升用户感知
// uni.showLoading({
// title: '验证身份中...',
// mask: true // 防止用户重复操作
// });
// 检查用户角色
this.$store.dispatch('GetInfo')
.then(res => {
@@ -32,6 +25,10 @@ export default {
const roles = res.data.roles;
if (roles.includes('admin')) {
uni.setTabBarItem({
index: 0,
visible: true
});
// 管理员角色跳转
uni.switchTab({
url: '/pages/line/line',
@@ -49,7 +46,13 @@ export default {
});
} else if (roles.includes('worker')) {
// 工人角色跳转
uni.navigateTo({
// 设置tab内容
// 设置产线和复杂扫码不可见
uni.setTabBarItem({
index: 0,
visible: false
});
uni.switchTab({
url: '/pages/easycode/easycode',
success: () => {
this.hasJumped = true; // 标记已跳转
@@ -70,10 +73,13 @@ export default {
icon: 'none',
duration: 2000
});
uni.setTabBarItem({
index: 0,
visible: false
});
// 延迟跳转,确保提示被用户看到
setTimeout(() => {
uni.navigateTo({
uni.switchTab({
url: '/pages/easycode/easycode',
success: () => {
this.hasJumped = true;

View File

@@ -0,0 +1,708 @@
<template>
<view class="container">
<!-- 筛选栏 -->
<view class="filter-bar">
<!-- 左侧收货计划选择 -->
<view class="plan-select" @click="openPlanPopup">
<uni-icons type="shop" size="20" color="#666"></uni-icons>
<text class="plan-text">{{ currentPlan.planNo || '选择收货计划' }}</text>
<uni-icons type="arrowright" size="16" color="#999"></uni-icons>
</view>
<!-- 右侧钢卷号筛选 -->
<view class="coil-filter">
<input v-model="currentCoilNo" placeholder="请输入钢卷号筛选" clearable @confirm="fetchList" class="coil-input"></input>
<uni-icons type="search" size="18" color="#666" @click="fetchList(true)"></uni-icons>
</view>
</view>
<!-- 收货计划选择悬浮窗 -->
<uni-popup ref="planPopup" type="bottom" :mask-click="true" height="70%">
<view class="popup-container">
<!-- 弹窗头部 -->
<view class="popup-header">
<text class="popup-title">选择收货计划</text>
<uni-icons type="close" size="20" @click="closePlanPopup"></uni-icons>
</view>
<!-- 计划筛选输入框 -->
<view class="plan-search">
<input v-model="planKeyword" placeholder="请输入计划编号筛选" clearable class="plan-search-input"
@confirm="fetchDeliveryPlan"></input>
<uni-icons type="search" size="18" color="#666" @click="fetchDeliveryPlan(true)"></uni-icons>
</view>
<!-- 收货计划列表 -->
<view class="plan-list">
<uni-list v-if="planList.length">
<uni-list-item v-for="(item, index) in planList" :key="index" :title="item.planName" clickable
@click="selectPlan(item)"></uni-list-item>
</uni-list>
<view class="empty-tip" v-else>暂无收货计划数据</view>
<!-- 加载更多 -->
<uni-load-more :status="planHasMore ? 'more' : 'noMore'" @clickLoadMore="fetchDeliveryPlan(false)"
v-if="planList.length"></uni-load-more>
</view>
</view>
</uni-popup>
<uni-popup ref='receivePopup'>
<!-- 表单区域 -->
<view class="form-card" v-if="form.coilId">
<view class="card-title">
<text class="title-dot"></text>
<text class="title-text">录入信息</text>
</view>
<!-- 当前钢卷号 -->
<view class="form-item">
<text class="form-label">当前钢卷号</text>
<input v-model="form.currentCoilNo" placeholder="请输入当前钢卷号" class="form-input" />
</view>
<!-- 班组 -->
<view class="form-item">
<text class="form-label">班组</text>
<input v-model="form.team" placeholder="请输入班组名称" class="form-input" :disabled="form.dataType === 0"
:class="{ 'form-input-disabled': form.dataType === 0 }" />
</view>
<!-- 库区选择 -->
<view class="form-item form-item-optional">
<text class="form-label-optional">目标库位</text>
<klp-warehouse-picker v-model="form.warehouseId" :disabled="form.dataType === 0" placeholder="请选择目标库区" />
</view>
<!-- <view class="form-item form-item-optional">
<text class="form-label-optional">真实库区</text>
<klp-warehouse-picker v-model="form.actualWarehouseId" :disabled="form.dataType === 0"
placeholder="请选择目标库区" ware-type="actual" />
</view> -->
<!-- 物品类型产品原材料选择使用封装组件 -->
<klp-material-picker
:item-type.sync="form.itemType"
:item-id.sync="form.itemId"
:disabled="form.dataType === 0"
:page-size="2000"
/>
<!-- 毛重 -->
<view class="form-item form-item-optional">
<text class="form-label-optional">毛重 ()</text>
<input v-model="form.grossWeight" type="digit" placeholder="请输入毛重(选填)" class="form-input"
:disabled="form.dataType === 0" :class="{ 'form-input-disabled': form.dataType === 0 }" />
</view>
<!-- 净重 -->
<view class="form-item form-item-optional">
<text class="form-label-optional">净重 ()</text>
<input v-model="form.netWeight" type="digit" placeholder="请输入净重(选填)" class="form-input"
:disabled="form.dataType === 0" :class="{ 'form-input-disabled': form.dataType === 0 }" />
</view>
<!-- 操作者信息 -->
<view class="operator-info">
<text class="operator-label">操作人</text>
<text class="operator-name">{{ operatorName }}</text>
</view>
<!-- 操作按钮 -->
<view class="action-buttons">
<button v-if="form.dataType === 1" class="btn btn-primary" @click="confirmReceive" :disabled="loading">
{{ loading ? '提交中...' : '确认收货' }}
</button>
</view>
</view>
</uni-popup>
<!-- 主列表区域 -->
<view class="main-list">
<scroll-view class="list-scroll" scroll-y>
<uni-list v-if="list.length">
<uni-list-item clickable v-for="(item, index) in list" :key="index" @click="openReceivePopup(item)"
:title="`钢卷号:${item.currentCoilNo || '-'}`" :note="`操作时间:${item.updateTime || '-'}`">
</uni-list-item>
</uni-list>
<view class="empty-tip" v-else>暂无待操作数据</view>
<!-- 加载更多 -->
<uni-load-more :status="hasMore ? 'more' : 'noMore'" @clickLoadMore="fetchList(false)"
v-if="list.length"></uni-load-more>
</scroll-view>
</view>
</view>
</template>
<script>
import {
listPendingAction,
updatePendingAction
} from '@/api/wms/pendingAction';
import {
listDeliveryPlan
} from '@/api/wms/deliveryPlan.js';
import {
updateMaterialCoilSimple,
getMaterialCoil
} from '@/api/wms/coil.js'
// 入库操作编码
const ACTION_TYPE = 401;
// 收货计划类型编码
const RECEIVE_PLAN_TYPE = 1;
// 未开始操作状态
const ACTION_STATUS = 0;
export default {
data() {
return {
// 主列表数据
list: [],
currentPlan: {}, // 当前选中的收货计划
currentCoilNo: undefined, // 当前输入的钢卷号
hasMore: false, // 是否有更多主列表数据
pageNum: 1, // 主列表页码
pageSize: 10, // 每页条数
refreshing: false, // 下拉刷新状态
// 收货计划弹窗相关
planKeyword: '', // 计划筛选关键词
planList: [], // 收货计划列表
planHasMore: false, // 计划列表是否有更多
planPageNum: 1, // 计划列表页码
popupShow: false, // 弹窗显示状态
form: {},
loading: false,
currentAction: {},
};
},
computed: {
// 获取当前操作者昵称
operatorName() {
return this.$store.state.user.nickName || this.$store.state.user.name || '未知'
}
},
onShow() {
// 页面显示时初始化加载数据
this.fetchList(true);
},
methods: {
/**
* 获取待操作列表数据
* @param {Boolean} isRefresh 是否刷新(重置页码)
*/
async fetchList(isRefresh = false) {
try {
// 刷新时重置页码和加载状态
if (isRefresh) {
this.pageNum = 1;
this.refreshing = true;
}
// 构造请求参数
const params = {
actionStatus: ACTION_STATUS,
actionType: ACTION_TYPE,
warehouseId: this.currentPlan.planId || '', // 选中的计划ID
currentCoilNo: this.currentCoilNo || '', // 钢卷号
pageNum: this.pageNum,
pageSize: this.pageSize
};
// 请求接口
const res = await listPendingAction(params);
if (res.code === 200) {
const list = res.rows || [];
// 刷新时替换数据,加载更多时追加数据
this.list = isRefresh ? list : [...this.list, ...list];
// 判断是否有更多数据
this.hasMore = this.pageNum * this.pageSize < res.total;
}
console.log(this.list, '需要渲染的数据')
} catch (err) {
console.error('获取待操作列表失败:', err);
uni.showToast({
title: '数据加载失败',
icon: 'none'
});
} finally {
// 结束下拉刷新状态
this.refreshing = false;
// 加载更多时页码+1
if (!isRefresh) this.pageNum++;
}
},
/**
* 获取收货计划列表
* @param {Boolean} isRefresh 是否刷新(重置页码)
*/
async fetchDeliveryPlan(isRefresh = false) {
try {
if (isRefresh) {
this.planPageNum = 1;
}
// 构造请求参数
const params = {
planType: RECEIVE_PLAN_TYPE,
planName: this.planKeyword || '', // 计划编号筛选
pageNum: this.planPageNum,
pageSize: this.pageSize
};
const res = await listDeliveryPlan(params);
if (res.code === 200) {
const list = res.rows || [];
this.planList = isRefresh ? list : [...this.planList, ...list];
this.planHasMore = this.planPageNum * this.pageSize < res.total;
}
} catch (err) {
console.error('获取收货计划失败:', err);
uni.showToast({
title: '计划加载失败',
icon: 'none'
});
} finally {
if (!isRefresh) this.planPageNum++;
}
},
/**
* 打开收货计划弹窗
*/
openPlanPopup() {
this.$refs.planPopup.open();
// 打开弹窗时加载计划数据
this.fetchDeliveryPlan(true);
},
/**
* 关闭收货计划弹窗
*/
closePlanPopup() {
this.$refs.planPopup.close();
},
/**
* 打开收货弹窗
*/
openReceivePopup(row) {
this.$refs.receivePopup.open('bottom')
getMaterialCoil(row.coilId).then(res => {
this.form = res.data;
this.currentAction = row;
})
},
/**
* 确认收货
*/
confirmReceive(row) {
const currentAction = this.currentAction;
const form = this.form;
const that = this;
uni.showModal({
title: '确定要收货吗?',
success() {
// console.log(currentAction, form)
that.loading = true;
Promise.all([
updatePendingAction({
...currentAction,
actionStatus: 2
}),
updateMaterialCoilSimple({
...form,
dataType: 1
})
]).then(_ => {
uni.showToast({
title: '钢卷已收货'
});
that.fetchList(true);
that.loading = false;
that.$refs.receivePopup.close()
})
}
})
},
/**
* 选择收货计划
* @param {Object} plan 选中的计划对象
*/
selectPlan(plan) {
this.currentPlan = plan;
this.closePlanPopup();
// 选择计划后重新加载主列表
this.fetchList(true);
},
/**
* 下拉刷新触发(修正后事件可正常绑定)
*/
onPullDownRefresh() {
this.fetchList(true);
},
/**
* 页面上拉加载(可选:补充页面级上拉加载)
*/
onReachBottom() {
if (this.hasMore) {
this.fetchList(false);
}
}
},
// 补充页面级上拉加载钩子(可选,增强体验)
onReachBottom() {
this.onReachBottom();
}
};
</script>
<style scoped lang="scss">
.container {
width: 100%;
height: 100vh;
background-color: #f5f5f5;
display: flex;
flex-direction: column;
}
/* 筛选栏样式 */
.filter-bar {
display: flex;
align-items: center;
padding: 10rpx 20rpx;
background-color: #fff;
border-bottom: 1px solid #eee;
}
.plan-select {
display: flex;
align-items: center;
gap: 8rpx;
padding: 10rpx 15rpx;
background-color: #f8f8f8;
border-radius: 6rpx;
margin-right: 20rpx;
min-width: 200rpx;
}
.plan-text {
font-size: 28rpx;
color: #333;
flex: 1;
}
.coil-filter {
display: flex;
align-items: center;
flex: 1;
background-color: #f8f8f8;
border-radius: 6rpx;
padding: 0 15rpx;
}
.coil-input {
flex: 1;
font-size: 28rpx;
}
/* 弹窗样式 */
.popup-container {
width: 100%;
height: 100%;
background-color: #fff;
display: flex;
flex-direction: column;
}
.popup-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx;
border-bottom: 1px solid #eee;
}
.popup-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.plan-search {
display: flex;
align-items: center;
gap: 10rpx;
padding: 15rpx 20rpx;
border-bottom: 1px solid #eee;
}
.plan-search-input {
flex: 1;
font-size: 28rpx;
}
.plan-list {
flex: 1;
overflow-y: auto;
padding: 10rpx;
}
/* 列表样式 */
.main-list {
flex: 1;
overflow: hidden;
}
.list-scroll {
width: 100%;
height: 100%;
}
.uni-list {
background-color: #fff;
margin: 10rpx;
border-radius: 8rpx;
}
.uni-list-item {
font-size: 28rpx;
}
/* 空数据提示 */
.empty-tip {
text-align: center;
padding: 50rpx 0;
font-size: 28rpx;
color: #999;
}
/* 加载更多样式 */
.uni-load-more {
margin: 20rpx 0;
}
.form-card {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
overflow: scroll;
height: 70vh;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
}
.card-title {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 25rpx;
padding-bottom: 20rpx;
border-bottom: 1rpx solid #f0f0f0;
.title-dot {
width: 8rpx;
height: 28rpx;
background: #007aff;
border-radius: 4rpx;
margin-right: 12rpx;
}
.title-text {
flex: 1;
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.status-badge {
font-size: 22rpx;
padding: 4rpx 12rpx;
border-radius: 12rpx;
margin-right: 10rpx;
&.status-1 {
background: #d1f2eb;
color: #0c6957;
}
&.status-0 {
background: #f8d7da;
color: #721c24;
}
}
.more-btn {
display: flex;
align-items: center;
gap: 8rpx;
padding: 8rpx 16rpx;
background: #f0f7ff;
border-radius: 20rpx;
border: 1rpx solid #007aff;
.icon-more {
font-size: 24rpx;
color: #007aff;
}
.more-text {
font-size: 24rpx;
color: #007aff;
}
}
}
/* 信息网格 */
.info-grid {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
.info-item {
flex: 1;
min-width: 45%;
background: #f8f9fa;
padding: 20rpx;
border-radius: 12rpx;
&.full-width {
flex: 0 0 100%;
}
.item-label {
display: block;
font-size: 24rpx;
color: #999;
margin-bottom: 10rpx;
}
.item-value {
display: block;
font-size: 28rpx;
color: #333;
font-weight: 500;
}
}
}
/* 表单项 */
.form-item {
margin-bottom: 30rpx;
&:last-of-type {
margin-bottom: 0;
}
.form-label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 15rpx;
font-weight: 500;
&::before {
content: '*';
color: #ff4d4f;
margin-right: 6rpx;
}
}
.form-label-optional {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 15rpx;
font-weight: 500;
}
.form-input {
width: 100%;
height: 88rpx;
padding: 0 24rpx;
background: #f8f9fa;
border: 2rpx solid #e8e8e8;
border-radius: 12rpx;
font-size: 28rpx;
color: #333;
box-sizing: border-box;
transition: all 0.3s;
&:focus {
background: #fff;
border-color: #007aff;
}
&.form-input-disabled {
background: #f5f5f5;
color: #999;
cursor: not-allowed;
}
}
}
/* 操作者信息 */
.operator-info {
display: flex;
align-items: center;
justify-content: center;
padding: 20rpx 0;
margin-top: 20rpx;
.operator-label {
font-size: 26rpx;
color: #999;
}
.operator-name {
font-size: 26rpx;
color: #007aff;
font-weight: 500;
}
}
/* 操作按钮 */
.action-buttons {
display: flex;
gap: 20rpx;
margin-top: 30rpx;
.btn {
flex: 1;
height: 88rpx;
border-radius: 12rpx;
font-size: 30rpx;
font-weight: 500;
border: none;
transition: all 0.2s;
&:active {
transform: scale(0.98);
}
&[disabled] {
opacity: 0.6;
}
}
.btn-secondary {
background: #f5f5f5;
color: #666;
}
.btn-primary {
background: linear-gradient(135deg, #007aff 0%, #0051d5 100%);
color: #fff;
box-shadow: 0 4rpx 16rpx rgba(0, 122, 255, 0.3);
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -0,0 +1,14 @@
<template>
<div id="model-preview" ref="viewport"></div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
const props = defineExpose({
modelName: {
type: String,
default: '',
},
});
</script>

View File

@@ -231,9 +231,6 @@
<template #right v-if="showSider">
<SceneRight />
</template>
<template #main>
<Scene ref="sceneRef" />
</template>
</LayoutScreen>
</template>
<script setup lang="ts">
@@ -241,7 +238,6 @@ import LayoutScreen from '@/pages/components/LayoutScreen.vue';
import Header from '@/pages/components/Header.vue';
import Status from './components/Status.vue';
import SceneRight from './SceneRight.vue';
import Scene from './Scene.vue';
import { onMounted, provide, ref } from 'vue';
import { injectContextKey } from './context';
import signals from 'signals';

View File

@@ -1,267 +1,8 @@
<template>
<div id="popover-viewport" ref="viewport" :style="{ width: `${width}px`, height: `${height}px` }"></div>
<div id="popover-viewport" ref="viewport"></div>
</template>
<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, watchEffect } from 'vue';
import { Player } from '@/three';
import * as THREE from 'three';
import {
// CSS2DObject,
EffectComposer,
OutputPass,
RenderPass,
SMAAPass,
RoomEnvironment,
// ShaderPass,
// GammaCorrectionShader,
} from 'three/examples/jsm/Addons.js';
import equips from '../data/steelmill';
import { useElementSize } from '@vueuse/core';
const equipmentInfos = ref([
equips.Bianqieji,
equips.Cuzhaji,
equips.Dianhulu,
equips.Duanqieji,
// equips.Gaolu,
equips.Jiarelu,
equips.Jingzhaji,
equips.Lianzhu,
equips.Panjuanji,
equips.Tieshuiguan,
equips.Zhengpingji,
equips.Zhongzhaji,
equips.Zhuanlu,
equips.Zhuzhaji,
]);
const modelNames = computed(() => {
return equipmentInfos.value.map((v) => v.key).filter((v) => v !== 'GaoLu');
});
let models: THREE.Group<THREE.Object3DEventMap>[] = [];
const player = new Player();
const viewport = ref<HTMLElement>();
let composer: EffectComposer;
let pmremGenerator: THREE.PMREMGenerator;
const props = defineProps<{
modelName?: string;
width?: number;
height?: number;
visible?: boolean;
// models: THREE.Group<THREE.Object3DEventMap>[];
}>();
const { width: offsetWidth, height: offsetHeight } = useElementSize(viewport);
watchEffect(() => {
if (offsetWidth.value > 0 && offsetHeight.value > 0) {
// console.log('setSize', offsetWidth.value, offsetHeight.value);
// player.renderer?.clear();
player.setSize(offsetWidth.value, offsetHeight.value);
}
});
watchEffect(
() => {
if (props.visible) {
player.controls?.reset(false);
visibleModel(props.modelName);
player.play();
} else {
player.stop();
}
},
{ flush: 'post' }
);
async function loadModels() {
const tasks = modelNames.value.map((name) => async () => {
const glb = await player.loader.loadFile(`/models/steelmill2/${name}.glb`);
glb.name = name;
glb.userData.name = name;
return glb;
});
models = await Promise.all(tasks.map((task) => task()));
}
function visibleModel(name?: string) {
models.forEach((model) => {
if (model.name === name) {
// console.log('visible', name, model);
model.visible = true;
const focusObject = player.scene.getObjectByName(props.modelName || '');
let scalar = 1.5;
// if (name === 'Dianhulu') {
// scalar = 1.5;
// }
// if (name === 'Lianzhu') {
// scalar = 1.5;
// }
// if (name === 'Zhongzhaji') {
// scalar = 1.2;
// }
// if (name === 'Bianqieji') {
// scalar = 1.6;
// }
// if (name === 'Zhengpingji') {
// scalar = 1.5;
// }
// if (name === 'Cuzhaji') {
// scalar = 1.2;
// }
// if (name === 'Jiarelu') {
// scalar = 1.5;
// }
// if (name === 'zhuan_lu') {
// scalar = 1.2;
// }
// if (name === 'Zhuzhaji') {
// scalar = 1.2;
// }
// if (name === 'Jingzhaji') {
// scalar = 1.4;
// }
// if (name === 'Duanqieji') {
// scalar = 1.2;
// }
if (focusObject) {
player.controls?.focus(focusObject, { scalar });
}
// player.controls?.reset();
// player.controls?.focus2(model);
} else {
model.visible = false;
}
});
}
onMounted(async () => {
// console.log('viewport', viewport.value?.offsetHeight);
player.setSize(props.width || viewport.value!.offsetWidth, props.height || viewport.value!.offsetHeight);
player.setPixelRatio(window.devicePixelRatio);
player.scene.fog = new THREE.Fog(0xcccccc, 2, 450);
viewport.value!.appendChild(player.dom);
player.addControls();
// const grid = new THREE.GridHelper(30, 15, 0xcccccc, 0x999999);
// player.scene.add(grid);
// ground
// {
// const ground = new THREE.Mesh(
// new THREE.PlaneGeometry(30, 30, 1, 1),
// new THREE.MeshPhongMaterial({ color: 0xa0adaf, shininess: 150 })
// );
// ground.rotation.x = -Math.PI / 2; // rotates X/Y to X/Z
// ground.receiveShadow = true;
// player.scene.add(ground);
// }
// Lights
{
const ambientLight = new THREE.AmbientLight(0xffffff, 2);
// ambientLight.layers.set(1);
player.scene.add(ambientLight);
const spotLight = new THREE.SpotLight(0xffffff, 80);
spotLight.angle = Math.PI / 4;
spotLight.penumbra = 0.2;
spotLight.position.set(0, 8, 3);
spotLight.castShadow = false;
spotLight.shadow.camera.near = 3;
spotLight.shadow.camera.far = 30;
spotLight.shadow.mapSize.width = 1024;
spotLight.shadow.mapSize.height = 1024;
// spotLight.layers.set(1);
player.scene.add(spotLight);
// player.addObject(new THREE.SpotLightHelper(spotLight));
const dirLight = new THREE.DirectionalLight(0x55505a, 4);
dirLight.position.set(0, 8, 3);
dirLight.castShadow = false;
dirLight.shadow.camera.near = 1;
dirLight.shadow.camera.far = 10;
dirLight.shadow.camera.right = 1;
dirLight.shadow.camera.left = -1;
dirLight.shadow.camera.top = 1;
dirLight.shadow.camera.bottom = -1;
dirLight.shadow.mapSize.width = 1024;
dirLight.shadow.mapSize.height = 1024;
// dirLight.layers.set(1);
player.scene.add(dirLight);
}
// composer
{
composer = new EffectComposer(player.renderer!);
const renderPass = new RenderPass(player.scene!, player.camera!);
composer.addPass(renderPass);
const { offsetWidth: width, offsetHeight: height } = player.cavans;
const pixelRatio = player.renderer!.getPixelRatio();
const smaaPass = new SMAAPass(width * pixelRatio, height * pixelRatio);
composer.addPass(smaaPass);
// // 创建伽马校正通道
// const gammaPass = new ShaderPass(GammaCorrectionShader);
// composer.addPass(gammaPass);
const outputPass = new OutputPass();
composer.addPass(outputPass);
}
await loadModels();
models.forEach((model) => {
model.scale.multiplyScalar(0.4);
player.addObject(model);
if (['zhuan_lu', 'Jingzhaji'].includes(model.name)) {
const animations = model.animations;
for (const ani of animations) {
player.addAnimation(animations, ani.name, model);
}
}
// console.log('model', model.name, model);
});
// player.scene.traverse((item) => {
// if (item.type === 'Group') {
// console.log(item.name, item);
// }
// });
// player.controls?.addEventListener('change', (ev) => {
// console.log('camera', ev.target.target, ev.target.object.position, player.camera!.position);
// });
player.controls!.autoRotate = true;
player?.camera?.position.set(8.434432723983832, 9.526646692338861, 6.038920583677303);
player.controls?.saveState();
player.enableShadows();
visibleModel(props.modelName);
// const focusObject = player.scene.getObjectByName(props.modelName || '');
// if (focusObject) {
// player.controls?.focus2(focusObject,);
// }
pmremGenerator = new THREE.PMREMGenerator(player.renderer!);
pmremGenerator.compileEquirectangularShader();
player.scene.environment = pmremGenerator.fromScene(new RoomEnvironment(), 0.04, 0.01, 20).texture;
player.events.resize.add((width, height) => {
composer.setSize(width, height);
});
player.events.update.add((_timer) => {
composer.render();
});
player.events.stop.add(() => {
// console.log('player.events.stop in scene2.vue');
});
player.events.start.add(() => {
// console.log('player.events.start in scene2.vue');
});
// player.play();
});
onUnmounted(() => {
pmremGenerator?.dispose();
composer?.dispose();
player.dispose();
});
</script>
<style lang="scss" scoped>
#popover-viewport {

View File

@@ -1,8 +1,5 @@
<template>
<div id="viewport">
<div class="canvas">
<scene2 :model-name="selectedObjectInfo?.key" :visible="showPopover"></scene2>
</div>
<div class="detail" v-if="selectedObjectInfo">
<div v-for="prop in Object.keys(selectedObjectInfo)" :key="prop">
<div v-if="prop !== 'name' && prop != 'key'">
@@ -28,7 +25,6 @@ import {
ProgressiveLightMap,
} from 'three/examples/jsm/Addons.js';
import { useContext } from './context';
import Scene2 from './Scene2.vue';
const player = new Player();
const shadowMapRes = 512,
lightMapRes = 1024,