feat: first commit
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div :class="'angle ' + identity + ' ' + direction">
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
// 箭头方向
|
||||
export enum EDirection {
|
||||
LeftTop = "left-top",
|
||||
LeftBottom = "left-bottom",
|
||||
RightTop = "right-top",
|
||||
RightBottom = "right-bottom"
|
||||
}
|
||||
|
||||
// 身份类型
|
||||
export enum EIdentity {
|
||||
// 重点人员
|
||||
Suspect = "suspect",
|
||||
// 陌生人
|
||||
Stranger = "stranger",
|
||||
// 居民
|
||||
Resident = "resident",
|
||||
// 未归档
|
||||
Unknow = "unknow",
|
||||
// 其它
|
||||
Other = "other"
|
||||
}
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ identity: EIdentity, direction: EDirection }>(), {
|
||||
identity: EIdentity.Stranger,
|
||||
direction: EDirection.LeftTop
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
$suspect-color:#ff5757;
|
||||
$stranger-color:#f9db12;
|
||||
$residents-color:#12DB68;
|
||||
$unknow-color:#989898;
|
||||
$primary1_color:#00C9DE;
|
||||
|
||||
|
||||
@mixin borderStyle($color) {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
border-top: 3px solid $color;
|
||||
border-left: 3px solid $color;
|
||||
}
|
||||
|
||||
@mixin angle-block($color) {
|
||||
&.left-top {
|
||||
@include borderStyle($color);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&.left-bottom {
|
||||
@include borderStyle($color);
|
||||
transform: rotate(270deg);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&.right-top {
|
||||
@include borderStyle($color);
|
||||
transform: rotate(90deg);
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&.right-bottom {
|
||||
@include borderStyle($color);
|
||||
transform: rotate(180deg);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.angle {
|
||||
|
||||
// 重点人员
|
||||
&.suspect {
|
||||
@include angle-block($suspect-color);
|
||||
}
|
||||
|
||||
// 陌生人
|
||||
&.stranger {
|
||||
@include angle-block($stranger-color);
|
||||
}
|
||||
|
||||
// 居民
|
||||
&.resident {
|
||||
@include angle-block($residents-color);
|
||||
}
|
||||
|
||||
// 未归档
|
||||
&.unknow {
|
||||
@include angle-block($unknow-color);
|
||||
}
|
||||
|
||||
// 其它
|
||||
&.other {
|
||||
@include angle-block($primary1_color);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<object-rect :top="top" :left="left" :width="-(left - right)" :height="-(top - bottom)"></object-rect>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
// import { EIdentity } from './Angle.vue';
|
||||
import { computed } from 'vue';
|
||||
const props = defineProps<{
|
||||
top: number,
|
||||
right: number,
|
||||
bottom: number,
|
||||
left: number
|
||||
}>()
|
||||
</script>
|
||||
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<codemirror v-model="value" placeholder="Code goes here..." :style="{ height: '400px' }" :autofocus="true"
|
||||
:indent-with-tab="true" :tab-size="2" :extensions="extensions" @ready="handleReady" @change="log('change', $event)"
|
||||
@focus="log('focus', $event)" @blur="log('blur', $event)" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from 'vue'
|
||||
import { Codemirror } from 'vue-codemirror'
|
||||
import { javascript } from '@codemirror/lang-javascript'
|
||||
// import { oneDark } from '@codemirror/theme-one-dark'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
Codemirror
|
||||
},
|
||||
setup() {
|
||||
const code = ref(`console.log('Hello, world!')`)
|
||||
const extensions = [javascript()]
|
||||
|
||||
// Codemirror EditorView instance ref
|
||||
const view = shallowRef()
|
||||
const handleReady = (payload) => {
|
||||
view.value = payload.view
|
||||
}
|
||||
|
||||
// Status is available at all times via Codemirror EditorView
|
||||
const getCodemirrorStates = () => {
|
||||
const state = view.value.state
|
||||
const ranges = state.selection.ranges
|
||||
const selected = ranges.reduce((r, range) => r + range.to - range.from, 0)
|
||||
const cursor = ranges[0].anchor
|
||||
const length = state.doc.length
|
||||
const lines = state.doc.lines
|
||||
// more state info ...
|
||||
// return ...
|
||||
}
|
||||
|
||||
return {
|
||||
value,
|
||||
extensions,
|
||||
handleReady,
|
||||
log: console.log
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<p class="el-message__content">{{ getMsgTempalte ? getMsgTempalte(countDownTime) : countDownTime }}</p>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { onBeforeMount, ref, onBeforeUnmount } from "vue";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
timespan?: number,
|
||||
getMsgTempalte: (value: number) => string
|
||||
}>(), {
|
||||
timespan: 3
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(n: 'next'): void
|
||||
}>()
|
||||
|
||||
const countDownTime = ref(props.timespan);
|
||||
const timeout = ref();
|
||||
|
||||
onBeforeMount(() => {
|
||||
timeout.value = setInterval(() => {
|
||||
if (countDownTime.value >= 1) {
|
||||
countDownTime.value -= 1;
|
||||
}
|
||||
else {
|
||||
clearInterval(timeout.value);
|
||||
emit('next');
|
||||
}
|
||||
}, 1000);
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(timeout.value);
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<el-tag v-for="tag in dynamicTags" :key="tag" class="mx-1" closable :disable-transitions="false"
|
||||
@close="handleClose(tag)">
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
<el-input v-if="inputVisible" ref="InputRef" v-model="inputValue" class="ml-1 w-20" size="small"
|
||||
@keyup.enter="handleInputConfirm" @blur="handleInputConfirm" />
|
||||
<el-button v-else class="button-new-tag ml-1" size="small" @click="showInput">
|
||||
+ New Tag
|
||||
</el-button>
|
||||
</template>
|
||||
<style scoped>
|
||||
.mx-1 {
|
||||
margin-left: 0.25rem;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, ref } from 'vue'
|
||||
import { ElInput } from 'element-plus'
|
||||
|
||||
const props = defineProps<{ modelValue: [] }>();
|
||||
const emits = defineEmits<{
|
||||
(
|
||||
e: "update:modelValue",
|
||||
val: string | number | (string | number)[] | undefined
|
||||
): void;
|
||||
}>();
|
||||
|
||||
const inputValue = ref('')
|
||||
const dynamicTags = ref(props.modelValue)
|
||||
const inputVisible = ref(false)
|
||||
const InputRef = ref<InstanceType<typeof ElInput>>()
|
||||
|
||||
const handleClose = (tag: string) => {
|
||||
dynamicTags.value.splice(dynamicTags.value.indexOf(tag), 1)
|
||||
emits('update:modelValue', dynamicTags.value)
|
||||
}
|
||||
|
||||
const showInput = () => {
|
||||
inputVisible.value = true
|
||||
nextTick(() => {
|
||||
InputRef.value!.input!.focus()
|
||||
})
|
||||
}
|
||||
|
||||
const handleInputConfirm = () => {
|
||||
if (inputValue.value) {
|
||||
dynamicTags.value.push(inputValue.value)
|
||||
emits('update:modelValue', dynamicTags.value)
|
||||
}
|
||||
inputVisible.value = false
|
||||
inputValue.value = ''
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<div :class="$style['img-preview']">
|
||||
<vue-cropper :key="id" ref="cropper" :src="imgUrl" alt="" :autoCrop="false" dragMode="move" :background="false"
|
||||
:viewMode="2" :toggleDragModeOnDblclick="false" @ready="onReady" @crop="onCrop" @zoom="onZoom" />
|
||||
<div :class="$style['mask']">
|
||||
<calc-rect-dock :top="rectPosition.top" :right="rectPosition.right" :bottom="rectPosition.bottom"
|
||||
:left="rectPosition.left"></calc-rect-dock>
|
||||
</div>
|
||||
<div :class="$style['prev']" @click="$emit('imgIdxChange', -1)">
|
||||
<el-icon>
|
||||
<d-arrow-left />
|
||||
</el-icon>
|
||||
</div>
|
||||
<div :class="$style['next']" @click="$emit('imgIdxChange', 1)">
|
||||
<el-icon>
|
||||
<d-arrow-right />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import VueCropper from 'vue-cropperjs';
|
||||
import 'cropperjs/dist/cropper.css';
|
||||
import { reactive, ref, onBeforeMount, watch } from "vue";
|
||||
import { computed } from '@vue/reactivity';
|
||||
import { merge, cloneDeep } from "lodash-es";
|
||||
import {
|
||||
DArrowLeft,
|
||||
DArrowRight,
|
||||
} from "@element-plus/icons-vue";
|
||||
|
||||
const props = defineProps<{
|
||||
id: "",
|
||||
imgUrl: "",
|
||||
position: "",
|
||||
}>();
|
||||
|
||||
defineEmits<{
|
||||
(n: 'imgIdxChange', value: TImgPreviewIdx): void
|
||||
}>();
|
||||
|
||||
const cropper = ref();
|
||||
const cropOption = reactive({
|
||||
ratio: 1,
|
||||
delta: { deltaX: 0, deltaY: 0 }
|
||||
});
|
||||
const rectPosition = reactive({
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0
|
||||
});
|
||||
const defRatio = ref(0);
|
||||
|
||||
onBeforeMount(() => {
|
||||
initRectPosition();
|
||||
})
|
||||
|
||||
watch([cropOption, () => props.id], () => {
|
||||
initRectPosition();
|
||||
})
|
||||
|
||||
const initRectPosition = () => {
|
||||
const rectOption: any = formatRectPosition(cloneDeep(props.position)) || {};
|
||||
merge(rectPosition, getStyle(rectOption));
|
||||
}
|
||||
|
||||
const initCropOption = (e) => {
|
||||
const canvasData: any = e.target?.cropper?.canvasData;
|
||||
if (canvasData) {
|
||||
if (e?.detail?.ratio && e.detail.ratio >= defRatio.value) {
|
||||
merge(cropOption, {
|
||||
ratio: e.detail.ratio,
|
||||
delta: {
|
||||
deltaX: canvasData.left,
|
||||
deltaY: canvasData.top
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
merge(cropOption, {
|
||||
delta: {
|
||||
deltaX: canvasData.left,
|
||||
deltaY: canvasData.top
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片渲染完成事件
|
||||
* @param e
|
||||
*/
|
||||
const onReady = (e: any) => {
|
||||
const canvasData: any = e.target?.cropper?.canvasData;
|
||||
if (canvasData) {
|
||||
defRatio.value = canvasData.width / e.target.width;
|
||||
merge(cropOption, {
|
||||
ratio: defRatio.value,
|
||||
delta: { deltaX: 0, deltaY: 0 }
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
const onCrop = (e) => {
|
||||
initCropOption(e);
|
||||
}
|
||||
|
||||
const onZoom = (e) => {
|
||||
initCropOption(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化矩形框位置信息
|
||||
* @param position json字符串
|
||||
*/
|
||||
const formatRectPosition = (position: any) => {
|
||||
const fpm: any = {};
|
||||
let rectInfo: any = {};
|
||||
if (typeof (position) === "string") {
|
||||
try {
|
||||
rectInfo = JSON.parse(position);
|
||||
const { face_rect, object_rect } = rectInfo;
|
||||
if (face_rect) {
|
||||
fpm.top = face_rect.top;
|
||||
fpm.right = face_rect.right;
|
||||
fpm.bottom = face_rect.bottom;
|
||||
fpm.left = face_rect.left;
|
||||
}
|
||||
else if (object_rect) {
|
||||
fpm.top = object_rect.top;
|
||||
fpm.right = object_rect.right;
|
||||
fpm.bottom = object_rect.bottom;
|
||||
fpm.left = object_rect.left;
|
||||
fpm.type = 2;//ERectType.Body;
|
||||
}
|
||||
return fpm;
|
||||
} catch (error) {
|
||||
console.warn("小图位置信息有误", position);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (position.face_rect) {
|
||||
return position.face_rect;
|
||||
}
|
||||
else if (position.object_rect) {
|
||||
return position.object_rect;
|
||||
}
|
||||
}
|
||||
console.warn("小图位置信息有误", position);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 定位矩形框
|
||||
*/
|
||||
const getStyle = (position: any) => {
|
||||
const faceRect = position;
|
||||
const { ratio, delta } = cropOption;
|
||||
if (ratio != -1) {
|
||||
for (const item in faceRect) {
|
||||
if (["top", "right", "bottom", "left"].includes(item))
|
||||
faceRect[item] = parseFloat(faceRect[item]) * ratio
|
||||
}
|
||||
}
|
||||
return {
|
||||
top: faceRect.top + delta.deltaY,
|
||||
left: faceRect.left + delta.deltaX,
|
||||
bottom: faceRect.bottom + delta.deltaY,
|
||||
right: faceRect.right + delta.deltaX,
|
||||
};
|
||||
}
|
||||
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
:global {
|
||||
:local(.img-preview) {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
:local(.mask) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
||||
:local(.prev),
|
||||
:local(.next) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
:local(.prev),
|
||||
:local(.next) {
|
||||
position: absolute;
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
background-color: rgb(10 10 10 / 20%);
|
||||
line-height: 100px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
pointer-events: painted;
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
:local(.prev) {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
:local(.next) {
|
||||
right: 0;
|
||||
transform: translate(0, -50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<system-layout>
|
||||
<template #sidebar>
|
||||
<system-menu menuIdx="1-1" :parentIdxList="[1]" />
|
||||
</template>
|
||||
<template #content>
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
</system-layout>
|
||||
</template>
|
||||
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div :class="['face-identity-rect', getRootClassName(), identity].join(' ')"
|
||||
:style="{ width: width + 'px', height: height + 'px' }">
|
||||
<angle :identity="identity" :direction="EDirection.LeftTop"></angle>
|
||||
<angle :identity="identity" :direction="EDirection.LeftBottom"></angle>
|
||||
<angle :identity="identity" :direction="EDirection.RightTop"></angle>
|
||||
<angle :identity="identity" :direction="EDirection.RightBottom"></angle>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { EDirection, EIdentity } from "./Angle.vue";
|
||||
import { computed } from "vue";
|
||||
|
||||
const props: any = withDefaults(defineProps<{
|
||||
identity: EIdentity, type: number,
|
||||
width: number, height: number,
|
||||
top: number,
|
||||
left: number
|
||||
}>(), {
|
||||
identity: EIdentity.Resident,
|
||||
type: 1
|
||||
})
|
||||
|
||||
const topVar = computed(() => props.top + 'px')
|
||||
const leftVar = computed(() => props.left + 'px')
|
||||
|
||||
|
||||
const getRootClassName = () => {
|
||||
// if (props.type == 1)
|
||||
// return "face-rect";
|
||||
// else
|
||||
return "body-rect";
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.face-identity-rect {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
top: v-bind(topVar);
|
||||
left: v-bind(leftVar);
|
||||
|
||||
&.body-rect {
|
||||
//.face-rect {
|
||||
// background-color: rgba(255, 140, 0, .19);
|
||||
border: solid 2px #ff8c00;
|
||||
// }
|
||||
|
||||
.angle {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div :class="$style['upload']" @change="onFileChange">
|
||||
<input ref="fileRef" :accept="accept" v-show="false" type="file" />
|
||||
<el-input :model-value="fileName" :disabled="true" :style="{ width: '200px' }" />
|
||||
<el-button :class="$style['scan']" @click="onPreUpload">浏览</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
|
||||
withDefaults(defineProps<{
|
||||
accept?: any
|
||||
}>(), {
|
||||
accept: '*'
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(n: "fileChange", file: File): void
|
||||
}>();
|
||||
|
||||
const fileRef = ref();
|
||||
const fileName = ref();
|
||||
|
||||
const onFileChange = (e: any) => {
|
||||
const files = e.target.files;
|
||||
fileName.value = files?.[0]?.name;
|
||||
emit('fileChange', files?.[0]);
|
||||
}
|
||||
|
||||
const onPreUpload = () => {
|
||||
fileRef.value.value = "";
|
||||
fileRef.value?.click();
|
||||
}
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
:global {
|
||||
:local(.upload) {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
:local(.scan) {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<slot name="header" :style="{ height: '80px' }">
|
||||
<home-header></home-header>
|
||||
</slot>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :style="{
|
||||
height: 'calc(100% - 80px)'
|
||||
}">
|
||||
<el-col :span="3">
|
||||
<slot name="sidebar"></slot>
|
||||
</el-col>
|
||||
<el-col :span="21" :style="{
|
||||
background: '#f0f2f5', padding: '20px', overflow: 'auto',
|
||||
height: 'calc(100vh - 80px)'
|
||||
}">
|
||||
<slot name="content"></slot>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import HomeHeader from "@/containers/HomeHeader.vue"
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
/* .el-menu-vertical-demo:not(.el-menu--collapse) {
|
||||
width: 200px;
|
||||
min-height: 400px;
|
||||
} */
|
||||
|
||||
:global {
|
||||
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:local(.layout) {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<!-- <el-radio-group v-model="isCollapse" style="margin-bottom: 20px">
|
||||
<el-radio-button :label="false">expand</el-radio-button>
|
||||
<el-radio-button :label="true">collapse</el-radio-button>
|
||||
</el-radio-group> -->
|
||||
<div :class="$style['system-menu']">
|
||||
<el-menu :default-active="activeIndex" :default-openeds="openeds" :router="true" class="el-menu-vertical-demo"
|
||||
:collapse="isCollapse" active-text-color="#409eff" background-color="#001529" text-color="#fff"
|
||||
:style="{ border: 0 }" @open="handleOpen" @close="handleClose">
|
||||
<template v-if="menuJson.length == 0">
|
||||
<el-menu-item index="/app/Employee">
|
||||
用戶列表
|
||||
</el-menu-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-menu-item v-for="(item, index) in menuJson" :key="item.name" :index="item.route">
|
||||
{{ item.name }}
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</el-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onBeforeMount } from 'vue'
|
||||
import {
|
||||
Menu as IconMenu,
|
||||
Setting,
|
||||
} from '@element-plus/icons-vue'
|
||||
import { useRoute } from "vue-router";
|
||||
import * as api from "@/common/api";
|
||||
import { MENU_JSON } from "@/consts/dataDict";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
menuIdx: string,
|
||||
parentIdxList: string[]
|
||||
}>(), {
|
||||
menuIdx: "/app/system/info",
|
||||
parentIdxList: () => ['system']
|
||||
})
|
||||
|
||||
const activeIndex = ref();
|
||||
const openeds = ref<string[]>([]);
|
||||
const router = useRoute();
|
||||
const menuJson = ref<any>([]);
|
||||
|
||||
onBeforeMount(() => {
|
||||
activeIndex.value = router.path;
|
||||
switch (true) {
|
||||
case router.path.includes('/app/device'):
|
||||
openeds.value = ['/app/device']
|
||||
break;
|
||||
case router.path.includes('/app/system'):
|
||||
default:
|
||||
openeds.value = ['/app/system/info'];
|
||||
break;
|
||||
}
|
||||
getMenuJson();
|
||||
})
|
||||
|
||||
const isCollapse = ref(false)
|
||||
const handleOpen = (key: string, keyPath: string[]) => {
|
||||
}
|
||||
const handleClose = (key: string, keyPath: string[]) => {
|
||||
}
|
||||
|
||||
const goTo = (url) => {
|
||||
document.location.href = url;
|
||||
}
|
||||
|
||||
const getMenuJson = async () => {
|
||||
const { data: dataDict } = await api.getDynamic({ id: MENU_JSON }, "dataDict");
|
||||
|
||||
if (dataDict?.value) {
|
||||
try {
|
||||
menuJson.value = JSON.parse(dataDict.value);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style module lang="scss">
|
||||
/* .el-menu-vertical-demo:not(.el-menu--collapse) {
|
||||
width: 200px;
|
||||
min-height: 400px;
|
||||
} */
|
||||
|
||||
:global {
|
||||
:local(.system-menu) {
|
||||
height: 100%;
|
||||
|
||||
.el-menu-vertical-demo {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,83 @@
|
||||
'use strict';
|
||||
|
||||
import ExecutionEnvironment from 'exenv';
|
||||
|
||||
function transitionEnd() {
|
||||
const transitionEndEventNames = {
|
||||
WebkitTransition: 'webkitTransitionEnd',
|
||||
MozTransition: 'transitionend',
|
||||
OTransition: 'oTransitionEnd otransitionend',
|
||||
transition: 'transitionend'
|
||||
};
|
||||
if (!ExecutionEnvironment.canUseDOM) {
|
||||
return transitionEndEventNames;
|
||||
}
|
||||
const el = document.createElement('pin');
|
||||
|
||||
for (const name in transitionEndEventNames) {
|
||||
if (el.style[name] !== undefined) {
|
||||
return transitionEndEventNames[name];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const ifHasTransitionEnd = transitionEnd();
|
||||
|
||||
const prefixes = ['Webkit', 'Moz', 'ms', 'O', ''];
|
||||
|
||||
class AnimationManager {
|
||||
constructor() {
|
||||
this.animationHandle = `css${ifHasTransitionEnd ? 3 : 2}Animation`;
|
||||
}
|
||||
|
||||
generate(options) {
|
||||
Object.assign(this, options);
|
||||
return this[this.animationHandle]();
|
||||
}
|
||||
|
||||
css2Animation() {
|
||||
const style = {};
|
||||
style[this.horizontalDirection] = `${this.position[0]}px`;
|
||||
style[this.verticalDirection] = `${this.position[1]}px`;
|
||||
|
||||
this.mixAnimation(style);
|
||||
return style;
|
||||
}
|
||||
|
||||
css3Animation() {
|
||||
const style = {};
|
||||
|
||||
prefixes.map(prefix => {
|
||||
let x, y;
|
||||
|
||||
if (this.horizontalDirection === 'right') {
|
||||
x = this.containerWidth - this.size.width - this.position[0];
|
||||
} else {
|
||||
x = this.position[0];
|
||||
}
|
||||
|
||||
if (this.verticalDirection === 'bottom') {
|
||||
y = this.containerHeight - this.size.height - this.position[1];
|
||||
} else {
|
||||
y = this.position[1];
|
||||
}
|
||||
|
||||
style[`${prefix}Transform`] = `translate3d(${x}px, ${y}px, 0)`;
|
||||
});
|
||||
|
||||
this.mixAnimation(style);
|
||||
return style;
|
||||
}
|
||||
|
||||
mixAnimation(style) {
|
||||
if (!this.closeAnimation) {
|
||||
prefixes.map(prefix => {
|
||||
style[`${prefix}TransitionDuration`] = `${this.transitionDuration}s`;
|
||||
style[`${prefix}TransitionTimingFunction`] = this.transitionTimingFunction;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default AnimationManager;
|
||||
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<div ref="container" :class="`${prefixClassName}-container`" :style="containerStyle">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
GridSort
|
||||
} from 'autoresponsive-core3';
|
||||
import pkg from '../../package';
|
||||
import AnimationManager from './animation';
|
||||
import {onBeforeMount, onMounted, onUpdated, ref} from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
containerWidth: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
containerHeight: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
gridWidth: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
prefixClassName: {
|
||||
type: String,
|
||||
default: pkg.name
|
||||
},
|
||||
itemClassName: {
|
||||
type: String,
|
||||
default: 'item'
|
||||
},
|
||||
itemMargin: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
horizontalDirection: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
transitionDuration: {
|
||||
type: [String, Number],
|
||||
default: 1
|
||||
},
|
||||
transitionTimingFunction: {
|
||||
type: String,
|
||||
default: 'linear'
|
||||
},
|
||||
verticalDirection: {
|
||||
type: String,
|
||||
default: 'top'
|
||||
},
|
||||
closeAnimation: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
onItemDidLayout: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
},
|
||||
onContainerDidLayout: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
}
|
||||
});
|
||||
|
||||
const containerStyle = {
|
||||
position: 'relative'
|
||||
};
|
||||
|
||||
let animationManager, fixedContainerHeight;
|
||||
onBeforeMount(() => {
|
||||
animationManager = new AnimationManager();
|
||||
fixedContainerHeight = typeof props.containerHeight === 'number';
|
||||
})
|
||||
|
||||
const mixItemInlineStyle = (s) => {
|
||||
const itemMargin = props.itemMargin;
|
||||
let style = {
|
||||
display: 'block',
|
||||
float: 'left',
|
||||
margin: `0 ${itemMargin}px ${itemMargin}px 0`
|
||||
};
|
||||
|
||||
if (props.containerWidth) {
|
||||
style = {
|
||||
position: 'absolute'
|
||||
};
|
||||
}
|
||||
Object.assign(s, style);
|
||||
}
|
||||
|
||||
const container = ref(null);
|
||||
|
||||
const updateChildren = () => {
|
||||
const sortManager = new GridSort({
|
||||
containerWidth: props.containerWidth,
|
||||
gridWidth: props.gridWidth
|
||||
});
|
||||
sortManager.init();
|
||||
|
||||
let containerHeight = props.verticalDirection === 'bottom' || fixedContainerHeight ? props.containerHeight : 0;
|
||||
|
||||
const children = container.value.children;
|
||||
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const node = children[i];
|
||||
const canvas = node.__vnode.el;
|
||||
|
||||
let style = {};
|
||||
switch (canvas.style.constructor.name) {
|
||||
case 'CSS2Properties':
|
||||
Object.values(canvas.style).forEach((prop) => {
|
||||
style[prop] = canvas.style[prop];
|
||||
});
|
||||
break;
|
||||
case 'CSSStyleDeclaration':
|
||||
style = canvas.style;
|
||||
break;
|
||||
}
|
||||
|
||||
if (node.className &&
|
||||
props.itemClassName &&
|
||||
!~node.className.indexOf(props.itemClassName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const childWidth = parseInt(style.width, 10) + props.itemMargin;
|
||||
const childHeight = parseInt(style.height, 10) + props.itemMargin;
|
||||
const calculatedPosition = sortManager.getPosition(childWidth, childHeight);
|
||||
|
||||
if (fixedContainerHeight) {
|
||||
container.value.style.height = `${containerHeight}px`;
|
||||
} else {
|
||||
if (calculatedPosition[1] + childHeight > containerHeight) {
|
||||
containerHeight = calculatedPosition[1] + childHeight;
|
||||
container.value.style.height = `${containerHeight}px`;
|
||||
}
|
||||
}
|
||||
|
||||
const options = Object.assign({}, props, {
|
||||
position: calculatedPosition,
|
||||
size: {
|
||||
width: childWidth,
|
||||
height: childHeight
|
||||
},
|
||||
containerHeight: containerHeight
|
||||
});
|
||||
|
||||
const calculatedStyle = animationManager.generate(options);
|
||||
|
||||
mixItemInlineStyle(calculatedStyle);
|
||||
|
||||
Object.assign(node.style, calculatedStyle);
|
||||
|
||||
props.onItemDidLayout(node);
|
||||
|
||||
if (i + 1 === children.length) {
|
||||
props.onContainerDidLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateChildren();
|
||||
})
|
||||
|
||||
onUpdated(() => {
|
||||
updateChildren();
|
||||
})
|
||||
|
||||
</script>
|
||||
Reference in New Issue
Block a user