Commit 481607b1 authored by hucy's avatar hucy

fix:调研-konvajs

parent f2dd54ce
......@@ -30,7 +30,7 @@ module.exports = configure(function (ctx) {
// app boot file (/src/boot)
// --> boot files are part of "main.js"
// https://v2.quasar.dev/quasar-cli-webpack/boot-files
boot: ['store', 'vue-i18n'],
boot: ['store', 'vue-i18n', 'vue-konva'],
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-css
css: ['app.scss'],
......
import { boot } from 'quasar/wrappers';
import VueKonva from 'vue-konva';
export default boot(({ app }) => {
// Tell app to use the I18n instance
app.use(VueKonva);
});
......@@ -129,4 +129,11 @@ export const MenuList = [
link: '/vue-study',
active: false,
},
{
title: 'Vue Konva',
caption: '前端Canvas库-Konva',
icon: require('./menuListIcons/amis.svg'),
link: '/vue-konva',
active: false,
},
];
<!--
* @FileDescription: vue-konva
* @Author: hcy
* @Date: 2023-03-29
-->
<script setup lang="ts">
import { reactive, onMounted } from 'vue';
import Konva from 'konva';
const state = reactive({
stage: null as any,
layer: null as any,
group: null as any,
shape: null as any,
boundingBox: null as any,
box: null as any,
});
const stageSize = reactive({
width: 1200,
height: 800,
});
// const rectConfig1 = reactive({
// x: 0,
// y: 0,
// width: 100,
// height: 100,
// fill: 'red',
// draggable: true,
// dragBoundFunc: function (pos: any) {
// let newY = pos.y < 0 ? 0 : pos.y;
// let newX = pos.x < 0 ? 0 : pos.x;
// if (newX >= 1200 - 100) {
// newX = 1200 - 100;
// }
// if (newY >= 800 - 100) {
// newY = 800 - 100;
// }
// // if (newX <= 300 && newY > 0 && newY <= 200) {
// // newX = 300;
// // }
// return {
// x: newX,
// y: newY,
// };
// },
// });
// const rectConfig2 = reactive({
// x: 100,
// y: 100,
// width: 200,
// height: 100,
// fill: '#204969',
// draggable: true,
// });
// const rectConfig3 = reactive({
// x: 200,
// y: 200,
// width: 100,
// height: 200,
// fill: '#b689b0',
// });
onMounted(() => {
state.stage = new Konva.Stage({
container: 'stage-container',
width: stageSize.width,
height: stageSize.height,
});
state.layer = new Konva.Layer();
state.stage.add(state.layer);
for (var i = 0; i < 3; i++) {
state.layer.add(createShape());
}
state.layer.on('dragmove', layerDragmove);
});
function createShape() {
state.group = new Konva.Group({
x: Math.random() * stageSize.width,
y: Math.random() * stageSize.height,
draggable: true,
});
state.shape = new Konva.Rect({
width: 30 + Math.random() * 30,
height: 30 + Math.random() * 30,
fill: 'grey',
rotation: 360 * Math.random(),
name: 'fillShape',
});
state.group.add(state.shape);
state.boundingBox = state.shape.getClientRect({ relativeTo: state.group });
state.box = new Konva.Rect({
x: state.boundingBox.x,
y: state.boundingBox.y,
width: state.boundingBox.width,
height: state.boundingBox.height,
stroke: 'red',
strokeWidth: 1,
});
state.group.add(state.box);
return state.group;
}
// 是否相交
function haveIntersection(r1: any, r2: any) {
// r1当前循环的可视矩形区域 r2当前移动目标的可视矩形区域
let obj: any = {
a: false,
b: false,
c: false,
d: false,
};
let pos: any = {
x: r2.x + r2.width,
y: r2.y + r2.height,
};
if (r2.x > r1.x + r1.width) {
obj.a = true;
}
if (r2.x + r2.width < r1.x) {
obj.b = true;
}
if (r2.y > r1.y + r1.height) {
obj.c = true;
}
if (r2.y + r2.height < r1.y) {
obj.d = true;
}
console.log('obj', obj, obj.a || obj.b || obj.c || obj.d);
return {
value: !(obj.a || obj.b || obj.c || obj.d),
pos,
};
// return !(
// r2.x > r1.x + r1.width ||
// r2.x + r2.width < r1.x ||
// r2.y > r1.y + r1.height ||
// r2.y + r2.height < r1.y
// );
}
function layerDragmove(e: any) {
let target = e.target;
let targetRect = e.target.getClientRect();
state.layer.children.forEach(function (group: any) {
// do not check intersection with itself
// 不检查与自身相交
if (group._id == target._id) {
return;
}
let r1 = group.getClientRect();
let r2 = targetRect;
let result = haveIntersection(r1, r2);
if (result.value) {
console.log('1', target._lastPos);
// target.x(result.pos.x);
// target.y(result.pos.y);
group.findOne('.fillShape').fill('red');
} else {
group.findOne('.fillShape').fill('grey');
}
});
}
</script>
<template>
<div class="konva-main-page container-height center">
<div class="canvas-box">
<div id="stage-container"></div>
</div>
</div>
</template>
<style lang="scss" scoped>
.konva-main-page {
}
.canvas-box {
box-sizing: border-box;
width: 1200px;
height: 800px;
border: 1px solid #000;
background: pink;
}
</style>
export default [
{
path: 'vue-konva',
name: 'VUE_KONVA',
component: () => import('./IndexPage.vue'),
meta: {
title: 'Vue Konva',
permission: ['*'],
keepalive: true,
},
},
];
......@@ -15,6 +15,7 @@ import { ChartOption, Series_A, Series_B, Series_D } from './config';
import DateDetail from './element/DateDetail.vue';
import ImageScale from './element/ImageScale.vue';
import CropperImg from './element/CropperImg.vue';
import InsertCoin from './element/InsertCoin.vue';
// const { info } = useMessage();
......@@ -22,6 +23,7 @@ const citySelectRef = ref<any>(null);
const regionSelectRef = ref<any>(null);
const chartRef = ref<any>(null);
const CropperImgRef = ref<any>(null);
const InsertCoinRef = ref<any>(null);
const showContent = ref(false);
const showContent2 = ref(false);
......@@ -38,6 +40,8 @@ const state = reactive({
region: null as any,
regionOpt: [] as any[],
myChart: null as any,
uploadImgUrl: 'https://w.wallhaven.cc/full/kx/wallhaven-kx98xd.jpg',
insertCoin: false, // 投币
});
watch(
......@@ -117,6 +121,12 @@ function clickLegend(type: string) {
function openCropperModel() {
CropperImgRef.value.openModel();
}
function uploadImgOk(data: any) {
state.uploadImgUrl = data;
}
function insertCoinClick() {
InsertCoinRef.value.openModel();
}
</script>
<template>
<div>
......@@ -234,12 +244,33 @@ function openCropperModel() {
<ImageScale />
<div class="upload-img-action q-ma-md">
<q-btn color="primary" label="上传头像" @click="openCropperModel" />
<div class="upload-img-content">
<q-btn color="primary" label="上传头像" @click="openCropperModel" />
<div class="upload-img-box">
<img
:src="state.uploadImgUrl"
alt="图片"
v-if="state.uploadImgUrl"
/>
</div>
</div>
</div>
<div class="q-ma-md">
<q-btn
round
flat
:color="state.insertCoin ? 'primary' : 'grey-7'"
icon="fa-brands fa-ello"
@click="insertCoinClick"
/>
</div>
</div>
</div>
<!-- 上传头像/修改头像 弹框 -->
<CropperImg ref="CropperImgRef" />
<CropperImg ref="CropperImgRef" @onOk="uploadImgOk" />
<!-- 投币弹框 -->
<InsertCoin ref="InsertCoinRef" />
</template>
<style lang="scss" scoped>
......@@ -383,4 +414,21 @@ function openCropperModel() {
height: 660px;
border-radius: 12px;
}
.upload-img-content {
display: flex;
align-items: center;
column-gap: 5px;
}
.upload-img-box {
display: flex;
align-items: center;
justify-content: center;
> img {
width: 42px;
height: 42px;
object-fit: contain;
border: 1px solid rgba(180, 180, 180, 0.3);
border-radius: 50%;
}
}
</style>
......@@ -4,15 +4,29 @@
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { VueCropper } from 'vue-cropper';
import { useMessage } from 'src/common/hooks';
defineExpose({
openModel,
});
const emit = defineEmits<{
(e: 'onOk', value: any): void;
}>();
const { warn } = useMessage();
const cropperRef = ref<any>(null);
const show = ref(false);
const previewStyle1 = ref({});
const previews = ref<any>({
div: '',
url: '',
img: '',
});
const option = reactive({
img: 'https://w.wallhaven.cc/full/kx/wallhaven-kx98xd.jpg',
outputType: '',
img: '',
});
function openModel() {
......@@ -20,13 +34,69 @@ function openModel() {
}
function onClose() {
show.value = false;
previewStyle1.value = {};
previews.value = {
div: '',
url: '',
img: '',
};
option.img = '';
}
// 实时预览
function realTime(data: any) {
previews.value = data;
previewStyle1.value = {
width: previews.value.w + 'px',
height: previews.value.h + 'px',
overflow: 'hidden',
margin: '0',
zoom: 140 / previews.value.w,
};
}
// 提交
function uploadImg() {
if (option.img) {
// 获取截图的 base64 数据
cropperRef.value.getCropData((data: any) => {
emit('onOk', data);
onClose();
});
} else {
warn('请选择图片');
}
}
// 选择上传图片
function selectImg() {
let _input: HTMLInputElement = document.createElement('input');
_input.setAttribute('type', 'file');
_input.setAttribute('accept', 'image/*'); // 上传类型
_input.onchange = function (e: any) {
let myFiles = e.target.files;
let myFile = myFiles[0];
//准备读取图片
if (window.FileReader && myFile) {
// 调用图片读取方法
let reader = new FileReader();
// 读取图片
reader.readAsDataURL(myFile);
// 监听图片读取进度
reader.onloadend = function (e: any) {
// 读取成功,拿到base64编码
let imgBase64 = e.target.result;
option.img = imgBase64;
};
}
};
_input.click();
}
</script>
<template>
<q-dialog v-model="show" persistent>
<q-card style="width: 700px; max-width: 700px">
<q-card-section class="row items-center q-pb-none">
<div class="text-h6">修改图片</div>
<div class="text-h6">上传头像</div>
<q-space />
<q-btn icon="close" flat round dense v-close-popup @click="onClose" />
</q-card-section>
......@@ -36,22 +106,41 @@ function onClose() {
<div class="left content-item">
<div class="left-content">
<VueCropper
ref="cropper"
ref="cropperRef"
:img="option.img"
autoCrop
fixed
@realTime="realTime"
></VueCropper>
</div>
<div class="left-action">
<q-btn color="primary" label="上传" outline icon="cloud_upload" />
<q-btn
color="primary"
label="上传"
outline
icon="cloud_upload"
@click="selectImg"
/>
</div>
</div>
<div class="right content-item">
<div class="right-content">
<img :src="option.img" alt="图片" />
<div class="previews-box">
<div :style="previewStyle1">
<div :style="previews.div">
<img :src="previews.url" :style="previews.img" />
</div>
</div>
</div>
</div>
<div class="right-action">
<q-btn color="primary" label="提交" unelevated />
<q-btn
color="primary"
label="提交"
unelevated
@click="uploadImg"
/>
</div>
</div>
</div>
......@@ -75,10 +164,18 @@ function onClose() {
display: flex;
align-items: center;
justify-content: center;
> img {
// > img {
// width: 140px;
// height: 140px;
// object-fit: contain;
// border: 1px solid rgba(180, 180, 180, 0.3);
// border-radius: 50%;
// }
.previews-box {
box-sizing: border-box;
overflow: hidden;
width: 140px;
height: 140px;
object-fit: contain;
border: 1px solid rgba(180, 180, 180, 0.3);
border-radius: 50%;
}
......
<!--
* 投币弹框
-->
<script setup lang="ts">
import { ref } from 'vue';
defineExpose({
openModel,
});
const show = ref(false);
const amount = ref(1);
const giveLike = ref(true);
function openModel() {
show.value = true;
}
function onClose() {
show.value = false;
}
</script>
<template>
<q-dialog v-model="show" persistent>
<q-card style="width: 500px; max-width: 500px">
<q-card-section class="row items-center q-pb-none">
<q-space />
<q-btn icon="close" flat round dense v-close-popup @click="onClose" />
</q-card-section>
<div class="amount-title row justify-center">
给UP主投上<span class="amount-box">{{ amount }}</span
>枚硬币
</div>
<q-card-section style="padding-bottom: 8px">
<div class="content" style="width: 100%">
<div class="img-content" style="height: 300px; width: 100%">1</div>
<div>
<q-checkbox v-model="giveLike" label="同时点赞内容" />
</div>
<div class="row justify-center">
<q-btn unelevated color="primary" label="确定" />
</div>
<div class="experience-box row justify-center">
经验值+10(今日20/50)
</div>
</div>
</q-card-section>
</q-card>
</q-dialog>
</template>
<style lang="scss" scoped>
.content {
// background-color: pink;
}
.amount-title {
font-size: 16px;
}
.amount-box {
color: $primary;
font-size: 32px;
margin: 0 4px;
transform: translateY(-18px);
}
.img-content {
// background-color: bisque;
}
.experience-box {
font-size: 12px;
color: rgba(0, 0, 0, 0.5);
margin-top: 6px;
margin-bottom: 16px;
}
</style>
......@@ -4,6 +4,7 @@ import FORM_TEST from '../modules/form-test/route';
import TREE from '../modules/tree/route';
import AMIS from '../modules/amis/route';
import VUE_STUDY from '../modules/vue-study/route';
import VUE_KONVA from '../modules/vue-konva/route';
const routes: RouteRecordRaw[] = [
{
......@@ -131,6 +132,7 @@ const routes: RouteRecordRaw[] = [
...TREE,
...AMIS,
...VUE_STUDY,
...VUE_KONVA,
],
},
],
......
......@@ -4068,6 +4068,11 @@ klona@^2.0.4, klona@^2.0.5:
resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22"
integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==
konva@^8.4.3:
version "8.4.3"
resolved "https://registry.yarnpkg.com/konva/-/konva-8.4.3.tgz#d6754fdb53e69295c24dbdfe967a27f92da51e47"
integrity sha512-ARqdgAbdNIougRlOKvkQwHlGhXPRBV4KvhCP+qoPpGoVQwwiJe4Hkdu4HHdRPb9rGUp04jDTAxBzEwBsE272pg==
launch-editor-middleware@2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/launch-editor-middleware/-/launch-editor-middleware-2.6.0.tgz#2ba4fe4b695d7fe3d44dee86b6d46d57b8332dfd"
......@@ -5990,6 +5995,11 @@ vue-i18n@^9.2.2:
"@intlify/vue-devtools" "9.2.2"
"@vue/devtools-api" "^6.2.1"
vue-konva@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/vue-konva/-/vue-konva-3.0.2.tgz#72393d5cd3b9ac4f798e5d381de5ef451456d810"
integrity sha512-FNWKtPPVDihNuQcq7F4GzuVPkPaEfg8lnWezpRqgiC2VetdvONOiYOFD800jNL5kt/lEYnSOYvhqqdTPbv2c8w==
vue-loader@17.0.1:
version "17.0.1"
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-17.0.1.tgz#c0ee8875e0610a0c2d13ba9b4d50a9c8442e7a3a"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment