添加 手势识别demo完成

This commit is contained in:
rucky 2021-12-24 19:18:22 +08:00
parent a8cca89108
commit 56107508f9
16 changed files with 939 additions and 228 deletions

View File

@ -18,10 +18,10 @@ module.exports = {
// 环境对象 // 环境对象
name: 'szxgl测试环境', // 环境名称 name: 'szxgl测试环境', // 环境名称
script: 'npm run build:dev', // 打包命令 script: 'npm run build:dev', // 打包命令
host: '120.25.121.117', // 服务器地址 host: '39.108.110.167', // 服务器地址
port: 22, // 服务器端口号 port: 22, // 服务器端口号
username: 'front', // 服务器登录用户名 username: 'front', // 服务器登录用户名
password: 'Szxgl#3210', // 服务器登录密码 password: 'XfhdFront', // 服务器登录密码
distPath: 'dist', // 本地打包生成目录 distPath: 'dist', // 本地打包生成目录
webDir: '/mnt/cdn/scl/webglToy', webDir: '/mnt/cdn/scl/webglToy',
// webDir: "/mnt/services/tomcat-8090-test/webapps/test/front", // test替换自己实际项目目录 服务器部署路径(不可为空或'/' // webDir: "/mnt/services/tomcat-8090-test/webapps/test/front", // test替换自己实际项目目录 服务器部署路径(不可为空或'/'

View File

@ -47,6 +47,7 @@
"@babel/core": "^7.13.14", "@babel/core": "^7.13.14",
"@babel/plugin-transform-runtime": "^7.13.10", "@babel/plugin-transform-runtime": "^7.13.10",
"@babel/preset-env": "^7.13.12", "@babel/preset-env": "^7.13.12",
"@handtracking.io/yoha": "^0.0.7",
"@nutui/nutui": "^2.2.15", "@nutui/nutui": "^2.2.15",
"@vue/cli-plugin-babel": "~4.5.0", "@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0", "@vue/cli-plugin-eslint": "~4.5.0",
@ -56,10 +57,14 @@
"axios": "^0.20.0", "axios": "^0.20.0",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
"coi-serviceworker": "^0.1.6",
"copy-webpack-plugin": "^4.4.1",
"deploy-cli-service": "^1.2.0", "deploy-cli-service": "^1.2.0",
"eslint": "^6.7.2", "eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2", "eslint-plugin-vue": "^6.2.2",
"glslify": "^7.1.1", "glslify": "^7.1.1",
"handtrackjs": "^0.1.5",
"meshline": "^2.0.3",
"qrcode": "^1.4.4", "qrcode": "^1.4.4",
"stats.js": "^0.17.0", "stats.js": "^0.17.0",
"style-resources-loader": "^1.3.2", "style-resources-loader": "^1.3.2",

View File

@ -11,7 +11,7 @@ module.exports = {
'postcss-zindex': false 'postcss-zindex': false
}, },
"postcss-px2rem-exclude": { "postcss-px2rem-exclude": {
remUnit: 37.5,//转换为rem的基准px remUnit: 75,//转换为rem的基准px
remPrecision: 8, //转换精度,小数点后保留位数 remPrecision: 8, //转换精度,小数点后保留位数
// exclude: /node_modules|folder_name/i // exclude: /node_modules|folder_name/i
}, },

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 KiB

View File

@ -110,7 +110,7 @@ export default {
this.controller = this.renderer.xr.getController(0); this.controller = this.renderer.xr.getController(0);
this.controller.addEventListener("select", () => { this.controller.addEventListener("select", () => {
let moved = new Date().getTime() - that.startTime > 500; let moved = new Date().getTime() - that.startTime > 165;
if (!moved) { if (!moved) {
this.addPumpkin(); this.addPumpkin();
} }

View File

@ -7,34 +7,24 @@
* @FilePath: /xfhd-vue-scaffold/src/page/index/Home/Index.vue * @FilePath: /xfhd-vue-scaffold/src/page/index/Home/Index.vue
--> -->
<template> <template>
<div class="color"> <div class="color">
<AR></AR> <AR></AR>
</div> </div>
</template> </template>
<script> <script>
// @ is an alias to /src // @ is an alias to /src
import AR from "@/components/ar/index.vue"; import AR from "@/components/ar/index.vue";
export default { export default {
name: "ar", name: "ar",
components: { AR }, components: { AR },
mounted() {}, mounted() {},
methods: { methods: {},
showTips() {
this.$weui.topTips("正在努力开发中……敬请期待", {
duration: 1500,
className: "custom-classname",
callback: function () {
// console.log('close');
},
});
},
},
}; };
</script> </script>
<!-- Add "scoped" attribute to limit CSS to this component only --> <!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="less"> <style scoped lang="less">
.color { .color {
min-height: 100vh; min-height: 100vh;
} }
</style> </style>

View File

@ -7,34 +7,24 @@
* @FilePath: /xfhd-vue-scaffold/src/page/index/Home/Index.vue * @FilePath: /xfhd-vue-scaffold/src/page/index/Home/Index.vue
--> -->
<template> <template>
<div class="color"> <div class="color">
<AR></AR> <AR></AR>
</div> </div>
</template> </template>
<script> <script>
// @ is an alias to /src // @ is an alias to /src
import AR from "@/components/ar/hit.vue"; import AR from "@/components/ar/hit.vue";
export default { export default {
name: "arHit", name: "arHit",
components: { AR }, components: { AR },
mounted() {}, mounted() {},
methods: { methods: {},
showTips() {
this.$weui.topTips("正在努力开发中……敬请期待", {
duration: 1500,
className: "custom-classname",
callback: function () {
// console.log('close');
},
});
},
},
}; };
</script> </script>
<!-- Add "scoped" attribute to limit CSS to this component only --> <!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="less"> <style scoped lang="less">
.color { .color {
min-height: 100vh; min-height: 100vh;
} }
</style> </style>

View File

@ -1,13 +1,16 @@
<template> <template>
<div id="app"> <div id="app">
<!-- 需要缓存的 --> <!-- 需要缓存的 -->
<keep-alive> <keep-alive>
<router-view v-if="$route.meta.keepAlive" /> <router-view v-if="$route.meta.keepAlive" />
</keep-alive> </keep-alive>
<!-- 不需要缓存的 --> <!-- 不需要缓存的 -->
<router-view v-if="!$route.meta.keepAlive" /> <router-view v-if="!$route.meta.keepAlive" />
</div> </div>
</template> </template>
<style> <style>
#app {
min-height: 100%;
}
</style> </style>

View File

@ -0,0 +1,237 @@
<!--
* @Author: your name
* @Date: 2020-08-28 15:20:15
* @LastEditTime: 2020-10-10 17:19:06
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: /xfhd-vue-scaffold/src/page/index/Home/Index.vue
-->
<template>
<div class="camera">
<div class="video-con">
<div class="kuang" ref="kuang"></div>
<video
id="gum-local"
class="video"
width="100%"
height="100%"
autoplay
playsinline
ref="myVideo"
></video>
</div>
<!-- 获取权限 -->
<button
id="showVideo"
class="btn"
ref="showVideoBtn"
@click="initVideo($event)"
>
开启摄像头
</button>
<button class="btn2" ref="takePhotoBtn" @click="takePhoto($event)">
拍照
</button>
<img ref="photo" class="photo" :src="imgsrc" />
</div>
</template>
<script>
// @ is an alias to /src
import gsap from "gsap";
export default {
name: "Index",
components: {},
data() {
return {
imgsrc: "",
};
},
mounted() {
this.initCanvas();
// this.requestPermission().then((res) => {
// if (res === true) {
// //
// }
// });
},
methods: {
//
getDevicePermission(constraints) {
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = function (constraints) {
var getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia ||
navigator.oGetUserMedia;
if (!getUserMedia) {
return Promise.reject(
new Error(
"getUserMedia is not implemented in this browser"
)
);
}
return new Promise(function (resolve, reject) {
getUserMedia.call(
navigator,
constraints,
resolve,
reject
);
});
};
}
return global.navigator.mediaDevices
.getUserMedia(constraints)
.then((stream) => {
if (stream) {
stopStreamTracks(stream);
return true;
}
return Promise.reject(new Error("EmptyStreamError"));
})
.catch((errMsg) => {
if (errMsg && "NotAllowedError" === errMsg.name) {
return false;
}
return Promise.reject(errMsg);
});
},
//
requestPermission() {
return this.getDevicePermission({ video: true, audio: true })
.catch(() =>
this.getDevicePermission({ video: false, audio: true })
)
.catch(() =>
this.getDevicePermission({ video: true, audio: false })
)
.catch(() => true);
},
// video
async initVideo(e) {
const constraints = {
video: {
facingMode: "user",
},
};
try {
this.stream = await navigator.mediaDevices.getUserMedia(
constraints
);
this.handleSuccess(this.stream, constraints);
e.target.disabled = true;
} catch (e) {
this.handleError(e);
}
},
handleSuccess(stream, constraints) {
this.video = this.$refs.myVideo;
const videoTracks = stream.getVideoTracks();
gsap.to(this.$refs.kuang, { autoAlpha: 1 });
gsap.to(this.$refs.showVideoBtn, { autoAlpha: 0 });
gsap.to(this.$refs.takePhotoBtn, { autoAlpha: 1 });
console.log("Got stream with constraints:", constraints);
console.log(`Using video device: ${videoTracks[0].label}`);
window.stream = stream; // make variable available to browser console
this.video.srcObject = stream;
},
handleError(error) {
console.error(error);
},
initCanvas() {
this.canvas = document.createElement("canvas");
this.ctx = this.canvas.getContext("2d");
this.canvas.width = 650 * 1.2;
this.canvas.height = 900 * 1.2;
},
async takePhoto() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// this.ctx.fillStyle = "#fff";
// this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.drawImage(
this.$refs.myVideo,
0,
0,
650,
900,
650 * 0.14,
900 * 0.1,
650,
900
);
let img = await this.loadImage(
require("@/assets/images/overlay.png")
);
this.ctx.drawImage(img, 0, 0, 650 * 1.2, 900 * 1.2);
this.imgsrc = this.canvas.toDataURL("image/jpeg");
gsap.to(this.$refs.photo, { autoAlpha: 1 });
},
loadImage(src) {
return new Promise((resolve, reject) => {
let img = new Image();
img.onload = (res) => {
resolve(img);
};
img.onerror = (res) => {
reject(img);
};
img.src = src;
});
},
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="less">
.camera {
// min-height: 100vh;
padding: 100px 0;
display: flex;
flex-direction: column;
justify-content: center;
align-content: center;
align-items: center;
.kuang {
visibility: hidden;
.paLayout(-12.5%,-10%,120%,120%,1);
.bg-norepeat("overlay","png");
pointer-events: none;
}
.video-con {
.prLayout(650px,900px);
// border: 1px solid #fff;
.video {
position: relative;
z-index: 0;
// width: 700px;
// height: 700px;
}
}
.btn,
.btn2 {
margin: 50px;
background-color: #fff;
.prLayout(200px,50px);
line-height: 40px;
text-align: center;
border-radius: 4px;
font-size: 14px;
}
.btn2 {
visibility: hidden;
}
.photo {
visibility: hidden;
.prLayout(750px,1000px);
}
}
</style>

View File

@ -7,34 +7,24 @@
* @FilePath: /xfhd-vue-scaffold/src/page/index/Home/Index.vue * @FilePath: /xfhd-vue-scaffold/src/page/index/Home/Index.vue
--> -->
<template> <template>
<div class="color"> <div class="color">
<Color></Color> <Color></Color>
</div> </div>
</template> </template>
<script> <script>
// @ is an alias to /src // @ is an alias to /src
import Color from "@/components/color/index.vue"; import Color from "@/components/color/index.vue";
export default { export default {
name: "Index", name: "Index",
components: { Color }, components: { Color },
mounted() {}, mounted() {},
methods: { methods: {},
showTips() {
this.$weui.topTips("正在努力开发中……敬请期待", {
duration: 1500,
className: "custom-classname",
callback: function () {
// console.log('close');
},
});
},
},
}; };
</script> </script>
<!-- Add "scoped" attribute to limit CSS to this component only --> <!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="less"> <style scoped lang="less">
.color { .color {
min-height: 100vh; min-height: 100vh;
} }
</style> </style>

View File

@ -0,0 +1,405 @@
<template>
<div class="hand-track">
<video
id="gum-local"
class="video"
width="100%"
height="100%"
autoplay
playsinline
webkit-playsinline
x5-playsinline
ref="myVideo"
></video>
<!-- 获取权限 -->
<button
id="showVideo"
class="btn"
ref="showVideoBtn"
@click="initVideo($event)"
>
开启摄像头
</button>
<!-- info -->
<div class="info-dialog">
<div class="progress" ref="progress">
当前进度: {{ progressText }}..
</div>
<div class="status">
当前手势: <span>{{ handStatus }}</span>
</div>
</div>
<!-- unsuit-dialog -->
<div class="unsuit-dialog" ref="unsuit">请在浏览器中打开</div>
<div class="unsuit-dialog" ref="unsuit2">您的系统版本过低</div>
</div>
</template>
<script>
// @ is an alias to /src
import gsap from "gsap";
import {
IEngine,
Load,
IHandTrackingApi,
MediaStreamErrorEnum,
EventEnum,
} from "@handtracking.io/yoha";
export default {
name: "handTrack",
components: {},
data() {
return {
progressText: "0%", //
BORDER_PADDING_FACTOR: 0.01, //
VIDEO_WIDTH_FACTOR: 0.8, //
videoRealSize: {},
handStatus: "未检测到手",
};
},
mounted() {
this.video = this.$refs.myVideo;
this.checkUserDeviceSuitable();
},
methods: {
//
checkUserDeviceSuitable() {
if (
window.deviceInfo.system === "IOS" &&
window.deviceInfo.app === "WX"
) {
let iosV = this.getIOSVersion();
console.log(
!this.versionStringCompare(iosV, 14.5),
this.versionStringCompare(iosV, 11)
);
if (
!this.versionStringCompare(iosV, 14.5) &&
this.versionStringCompare(iosV, 11)
) {
gsap.to(this.$refs.unsuit, { autoAlpha: 1 });
} else if (!this.versionStringCompare(iosV, 11)) {
gsap.to(this.$refs.unsuit2, { autoAlpha: 1 });
}
}
},
//
versionStringCompare(preVersion = "", lastVersion = "") {
var sources = preVersion.split(".");
var dests = lastVersion.split(".");
var maxL = Math.max(sources.length, dests.length);
var result = 0;
for (let i = 0; i < maxL; i++) {
let preValue = sources.length > i ? sources[i] : 0;
let preNum = isNaN(Number(preValue))
? preValue.charCodeAt()
: Number(preValue);
let lastValue = dests.length > i ? dests[i] : 0;
let lastNum = isNaN(Number(lastValue))
? lastValue.charCodeAt()
: Number(lastValue);
if (preNum < lastNum) {
result = -1;
break;
} else if (preNum > lastNum) {
result = 1;
break;
}
}
return result;
},
//
getDevicePermission(constraints) {
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = function (constraints) {
var getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia ||
navigator.oGetUserMedia;
if (!getUserMedia) {
return Promise.reject(
new Error(
"getUserMedia is not implemented in this browser"
)
);
}
return new Promise(function (resolve, reject) {
getUserMedia.call(
navigator,
constraints,
resolve,
reject
);
});
};
}
return global.navigator.mediaDevices
.getUserMedia(constraints)
.then((stream) => {
if (stream) {
stopStreamTracks(stream);
return true;
}
return Promise.reject(new Error("EmptyStreamError"));
})
.catch((errMsg) => {
if (errMsg && "NotAllowedError" === errMsg.name) {
return false;
}
return Promise.reject(errMsg);
});
},
//
requestPermission() {
return this.getDevicePermission({ video: true, audio: true })
.catch(() =>
this.getDevicePermission({ video: false, audio: true })
)
.catch(() =>
this.getDevicePermission({ video: true, audio: false })
)
.catch(() => true);
},
// id
async getBackCameraDeviceId() {
let source = new Array();
let devices = await navigator.mediaDevices.enumerateDevices();
if (devices.length > 0) {
devices.forEach(function (device) {
if (device.kind == "videoinput") {
source.push(device);
}
});
}
return source[source.length - 1].deviceId;
},
// video
async initVideo(e) {
const constraints = {
video: {
deviceId: { exact: await this.getBackCameraDeviceId() },
facingMode: "environment",
},
// video: {
// facingMode: { exact: "environment" },
// width: { min: 375, ideal: 375, max: 750 },
// height: { min: 590, ideal: 590, max: 1624 },
// },
};
this.video.play();
try {
this.stream = await navigator.mediaDevices.getUserMedia(
constraints
);
gsap.to(e.target, { autoAlpha: 0 });
e.target.disabled = true;
//
this.handleSuccess(this.stream, constraints);
} catch (e) {
this.handleError(e);
}
},
// stream
async handleSuccess(stream, constraints) {
const videoTracks = stream.getVideoTracks();
this.stream = stream; // make variable available to browser console
let streamSize = this.GetStreamDimensions(stream);
let handledSize = this.ScaleResolutionToWidth(
streamSize,
window.innerWidth
);
this.videoRealSize = handledSize;
console.log("Got stream with constraints:", constraints);
console.log(`Using video device: ${videoTracks[0].label}`);
console.log("Got stream size:", streamSize);
console.log(
"Got streamHandled size:",
window.innerWidth,
handledSize
);
this.responseVideo(handledSize);
this.video.srcObject = stream;
//
this.startDraw();
},
// stream
handleError(error) {
console.error(error);
},
//
responseVideo(size) {
this.video.width = size.width;
this.video.height = size.height;
},
// stream
GetStreamDimensions(stream) {
return {
width: stream.getVideoTracks()[0].getSettings().width,
height: stream.getVideoTracks()[0].getSettings().height,
};
},
//
ScaleResolutionToWidth(resolution, width) {
const cw = resolution.width;
const ch = resolution.height;
const tw = width;
return {
width: tw,
height: ch / (cw / tw),
};
},
//
async CreateEngine() {
let API = IHandTrackingApi;
if (API) {
return API.CreateEngine();
}
API = await Load("./yoha");
return API.CreateEngine();
},
//
async startDraw() {
this.engine = await this.CreateEngine();
await this.engine.DownloadModel((progress) => {
this.progressText = `${Math.round(progress * 100)}%`;
});
this.progressText = "引擎准备中…";
this.engine.Configure({
// Webcam video is usually flipped so we want the coordinates to be flipped as well.
flipX: false,
// Crop away a small area at the border to prevent the user to move out of view
// when reaching for border areas on the canvas.
padding: this.BORDER_PADDING_FACTOR,
});
this.engine.SetUpCustomTrackSource(this.video);
// await this.engine.SetUpCameraTrackSource();
// ios
if (window.deviceInfo.system === "IOS") {
await this.engine.Warmup();
}
this.progressText = "引擎已就绪";
gsap.to(this.$refs.progress, {
autoAlpha: 0,
delay: 0.5,
onComplete: () => {
this.$refs.progress.style.display = "none";
},
});
this.engine.Start((e) => {
if (e.type === EventEnum.RESULT) {
if (e.poses.fist) {
this.handStatus = "握拳";
// document.getElementById("status").innerText = "";
} else if (e.poses.pinch) {
this.handStatus = "捏合";
// document.getElementById("status").innerText = "";
} else {
this.handStatus = "张开";
// document.getElementById("status").innerText = "";
}
} else if (e.type === EventEnum.LOST) {
this.handStatus = "未检测到手";
// document.getElementById("status").innerText = "";
}
});
},
// ios
getIOSVersion() {
let str = navigator.userAgent.toLowerCase();
let ver = str.match(/cpu iphone os (.*?) like mac os/);
if (!ver) {
console.log("请在Ios系统中打开");
} else {
console.log(
"你当前的Ios系统版本为" + ver[1].replace(/_/g, ".")
);
return ver[1].replace(/_/g, ".");
}
},
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="less">
.hand-track {
.prLayout(100%,100vh);
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: center;
align-content: center;
align-items: center;
.video {
position: relative;
z-index: 0;
// transform: scaleX(-1);
width: 750px;
height: auto;
// height: 700px;
}
.info-dialog {
.paLayout(0,0, 50%,130px,2);
background-color: rgba(255, 255, 255, 0.25);
border-radius: 0 0 30px 0;
padding: 20px;
font-size: 28px;
display: flex;
flex-direction: column;
align-content: center;
align-items: center;
text-align: left;
text-indent: 10px;
justify-content: center;
div {
width: 100%;
line-height: 40px;
color: #fff;
span {
font-weight: bold;
color: #f00;
}
}
.progress {
}
}
.btn {
.paCenterBottom(200px,280px,80px,2);
background-color: #fff;
line-height: 40px;
text-align: center;
border-radius: 8px;
font-size: 25px;
}
.unsuit-dialog {
visibility: hidden;
.paCenterBottom(0, 100%,80px,10);
position: fixed;
background-color: rgba(255, 255, 255, 0.35);
color: #fff;
line-height: 80px;
text-align: center;
font-size: 40px;
}
}
</style>

View File

@ -7,168 +7,178 @@
* @FilePath: /xfhd-vue-scaffold/src/page/index/Home/Index.vue * @FilePath: /xfhd-vue-scaffold/src/page/index/Home/Index.vue
--> -->
<template> <template>
<div class="home"> <div class="home">
<Shader></Shader> <Shader></Shader>
<div class="overlay"></div> <div class="overlay"></div>
<div class="page grid js_show dialog"> <div class="page grid js_show dialog">
<div class="page__hd"> <div class="page__hd">
<h1 class="page__title" style="font-size: 20px"> <h1 class="page__title" style="font-size: 20px">
大板栗的玩具屋 大板栗的玩具屋
</h1> </h1>
<p class="page__desc" style="font-size: 18px">玩具列表</p> <p class="page__desc" style="font-size: 18px">玩具列表</p>
<p class="page__desc">have fun</p> <p class="page__desc">have fun</p>
</div> </div>
<div class="weui-grids" style="margin-top: 100px"> <div class="weui-grids" style="margin-top: 100px">
<div <div
style="opacity: 0.5" style="opacity: 0.5"
@click="jump(1)" @click="jump(1)"
class="weui-grid link" class="weui-grid link"
> >
<div class="weui-grid__icon"> <div class="weui-grid__icon">
<span class="iconfont">&#xe614;</span> <span class="iconfont">&#xe614;</span>
</div> </div>
<p class="weui-grid__label">Color</p> <p class="weui-grid__label">Color</p>
</div> </div>
<div <div
style="opacity: 0.5" style="opacity: 0.5"
@click="jump(2)" @click="jump(2)"
class="weui-grid link" class="weui-grid link"
> >
<div class="weui-grid__icon"> <div class="weui-grid__icon">
<span class="iconfont">&#xe614;</span> <span class="iconfont">&#xe614;</span>
</div> </div>
<p class="weui-grid__label">AR Paint</p> <p class="weui-grid__label">AR Paint</p>
</div> </div>
<div style="opacity: 0.5" @click="jump(3)" class="weui-grid"> <div style="opacity: 0.5" @click="jump(3)" class="weui-grid">
<div class="weui-grid__icon"> <div class="weui-grid__icon">
<span class="iconfont">&#xe614;</span> <span class="iconfont">&#xe614;</span>
</div> </div>
<p class="weui-grid__label">AR HIT</p> <p class="weui-grid__label">AR HIT</p>
</div> </div>
<a <div
href="#" style="opacity: 0.5"
class="weui-grid" @click="jump(4)"
style="opacity: 0.5" class="weui-grid link"
@click="showTips" >
> <div class="weui-grid__icon">
<div class="weui-grid__icon"> <span class="iconfont">&#xe614;</span>
<span class="iconfont">&#xe614;</span> </div>
</div> <p class="weui-grid__label">摄像机</p>
<p class="weui-grid__label">...</p> </div>
</a> <div class="weui-grid" style="opacity: 0.5" @click="jump(5)">
<a <div class="weui-grid__icon">
href="#" <span class="iconfont">&#xe614;</span>
class="weui-grid" </div>
style="opacity: 0.5" <p class="weui-grid__label">手势检测</p>
@click="showTips" </div>
> <div class="weui-grid" style="opacity: 0.5">
<div class="weui-grid__icon"> <div class="weui-grid__icon">
<span class="iconfont">&#xe614;</span> <span class="iconfont">&#xe614;</span>
</div> </div>
<p class="weui-grid__label">预留</p> <p class="weui-grid__label">...</p>
</a> </div>
<a </div>
href="#" </div>
class="weui-grid" </div>
style="opacity: 0.5"
@click="showTips"
>
<div class="weui-grid__icon">
<span class="iconfont">&#xe614;</span>
</div>
<p class="weui-grid__label">...</p>
</a>
</div>
</div>
</div>
</template> </template>
<script> <script>
// @ is an alias to /src // @ is an alias to /src
import Shader from "@/components/shader/index.vue"; import Shader from "@/components/shader/index.vue";
export default { export default {
name: "Index", name: "Index",
components: { components: {
Shader, Shader,
}, },
mounted() { mounted() {
// this.$weui.toast('', { // this.$weui.toast('', {
// duration: 3500, // duration: 3500,
// className: "toast-warn", // className: "toast-warn",
// }); // });
// setTimeout(() => { // setTimeout(() => {
// location.href = './project-list.html' // location.href = './project-list.html'
// }, 3500); // }, 3500);
}, },
methods: { methods: {
// //
jump(id) { jump(id) {
// //
if (id == 1) { if (id == 1) {
this.$router.push({ this.$router.push({
name: "Color", name: "Color",
params: {}, params: {},
}); });
} else if (id == 2) { } else if (id == 2) {
this.$router.push({ this.$router.push({
name: "AR", name: "AR",
params: {}, params: {},
}); });
} else if (id == 3) { } else if (id == 3) {
this.$router.push({ this.$router.push({
name: "ARHit", name: "ARHit",
params: {}, params: {},
}); });
} } else if (id == 4) {
}, this.$router.push({
showTips() { name: "Camera",
this.$weui.topTips("正在努力开发中……敬请期待", { params: {},
duration: 1500, });
className: "custom-classname", } else if (id == 5) {
callback: function () { this.$router.push({
// console.log('close'); name: "HandTrack",
}, });
}); }
}, },
}, showTips() {},
},
}; };
</script> </script>
<!-- Add "scoped" attribute to limit CSS to this component only --> <!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="less"> <style scoped lang="less">
.home { .home {
min-height: 100vh; min-height: 100vh;
.overlay { .overlay {
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(0, 0, 0, 0.5);
.paLayout(0,0,100%,100%,101); .paLayout(0,0,100%,100%,101);
pointer-events: none; pointer-events: none;
} }
.link { .link {
cursor: pointer; cursor: pointer;
} }
.dialog { .dialog {
.paLayout(0,0,100%,50%,102); .paLayout(0,0,100%,50%,102);
color: #fff; color: #fff;
.weui-grids::before, .weui-grids {
.weui-grids::after, display: flex;
.weui-grid:before, flex-direction: row;
.weui-grid:after { justify-content: center;
display: none; flex-wrap: wrap;
} font-size: 30px;
.weui-grid__label { .weui-grid {
color: #fff; display: inline-block;
} width: 33.33%;
.weui-grid__icon { height: 150px;
display: flex; margin-top: 10px;
flex-direction: column; .iconfont {
justify-content: center; font-size: 40px;
align-items: center; }
.iconfont { .weui-grid__label {
font-size: 30px; line-height: 80px;
} text-align: center;
} }
.page__hd { }
padding: 40px; }
} .weui-grids::before,
} .weui-grids::after,
.weui-grid:before,
.weui-grid:after {
display: none;
}
.weui-grid__label {
color: #fff;
}
.weui-grid__icon {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.iconfont {
font-size: 30px;
}
}
.page__hd {
padding: 40px;
}
}
} }
</style> </style>

View File

@ -20,9 +20,9 @@ import {
} from 'gsap' } from 'gsap'
import '@/assets/css/reset.less' import '@/assets/css/reset.less'
// import imgList from '@/preload/imgList.js' // 预加载资源图 // import imgList from '@/preload/imgList.js' // 预加载资源图
import weui from 'weui' // import weui from 'weui'
import '@/assets/css/weui-2.0.1.css'; // import '@/assets/css/weui-2.0.1.css';
import '@/assets/css/weui-for-work.css' // import '@/assets/css/weui-for-work.css'
// import VueLazyload from 'vue-lazyload' lazyload 懒加载 // import VueLazyload from 'vue-lazyload' lazyload 懒加载
// fix swiper6 with vue-awesome-swiper bug // fix swiper6 with vue-awesome-swiper bug
@ -40,7 +40,7 @@ Vue.config.productionTip = false
// 挂载工具集 // 挂载工具集
Vue.prototype.$Utils = utils; Vue.prototype.$Utils = utils;
// 引入 weui // 引入 weui
Vue.prototype.$weui = weui // Vue.prototype.$weui = weui
// init xgl plugins // init xgl plugins
utils.initial({ utils.initial({

View File

@ -12,6 +12,9 @@ import Home from '../Home/index.vue'
import Color from '../Color/index.vue' import Color from '../Color/index.vue'
import AR from '../AR/index.vue' import AR from '../AR/index.vue'
import ARHit from '../ARHit/index.vue' import ARHit from '../ARHit/index.vue'
import Camera from '../Camera/index.vue'
import HandTrack from '../HandTrack/index.vue'
Vue.use(VueRouter) Vue.use(VueRouter)
@ -62,6 +65,26 @@ const routes = [
keepAlive: false, // 是否保持活跃 keepAlive: false, // 是否保持活跃
}, },
}, },
{
path: "/camera",
name: "Camera",
component: Camera,
meta: {
title: "camera test", // 标题
keepAlive: false, // 是否保持活跃
},
},
{
path: "/track",
name: "HandTrack",
component: HandTrack,
meta: {
title: "Hand Track", // 标题
keepAlive: false, // 是否保持活跃
},
},
// { // {
// path: "/example", // path: "/example",
// name: "example", // name: "example",

15
tsconfig.json Normal file
View File

@ -0,0 +1,15 @@
{
"compileOnSave": false,
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"strictNullChecks": true,
"esModuleInterop": true,
"experimentalDecorators": true
},
"exclude": [
"node_modules",
"dist"
]
}

View File

@ -11,6 +11,7 @@ const path = require('path');
const CompressionPlugin = require('compression-webpack-plugin'); const CompressionPlugin = require('compression-webpack-plugin');
const vConsolePlugin = require("vconsole-webpack-plugin"); const vConsolePlugin = require("vconsole-webpack-plugin");
const TinyimgPlugin = require("tinyimg-webpack-plugin"); const TinyimgPlugin = require("tinyimg-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
function resolve(dir) { function resolve(dir) {
return path.join(__dirname, dir); return path.join(__dirname, dir);
@ -83,7 +84,54 @@ module.exports = {
logged: true logged: true
})] })]
// 配置 copy plguins
let copyImg = [
new CopyWebpackPlugin([
// This is required so that yoha can load model files etc.
{ from: 'node_modules/@handtracking.io/yoha/', to: 'yoha/' },
// Required for github pages...
{ from: 'node_modules/coi-serviceworker/coi-serviceworker.min.js', to: './' },
])
]
config.plugins = [...config.plugins,
...pluginsDev,
...tinyimg,
...copyImg,
];
config.module.rules.push( config.module.rules.push(
// ts加载
{
test: /\.tsx?$/,
use: [
{
loader: 'ts-loader',
options: {
transpileOnly: true,
appendTsSuffixTo: [/\.vue$/],
happyPackMode: true // 这个改为true
}
}
]
},
//wasm加载
{
test: /\.wasm$/i,
use: [
{
loader: 'file-loader', // 解决wasm加载问题
options: {
name: './static/wasm/[name].[ext]'
}
}
]
},
//模型加载 //模型加载
{ {
test: /\.(stl|obj|fbx|mtl|glb|gltf)?$/, test: /\.(stl|obj|fbx|mtl|glb|gltf)?$/,
@ -102,11 +150,6 @@ module.exports = {
}] }]
}) })
config.plugins = [...config.plugins,
...pluginsDev,
...tinyimg
];
// 配置gzip压缩 // 配置gzip压缩
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
return { return {