修改登录页
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
# 页面标题
|
||||
VITE_APP_TITLE = 若依管理系统
|
||||
VITE_APP_TITLE = 报价管理系统
|
||||
|
||||
# 开发环境配置
|
||||
VITE_APP_ENV = 'development'
|
||||
|
||||
# 若依管理系统/开发环境
|
||||
# 报价管理系统/开发环境
|
||||
VITE_APP_BASE_API = '/dev-api'
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 页面标题
|
||||
VITE_APP_TITLE = 若依管理系统
|
||||
VITE_APP_TITLE = 报价管理系统
|
||||
|
||||
# 生产环境配置
|
||||
VITE_APP_ENV = 'production'
|
||||
|
||||
# 若依管理系统/生产环境
|
||||
# 报价管理系统/生产环境
|
||||
VITE_APP_BASE_API = '/prod-api'
|
||||
|
||||
# 是否在打包时开启压缩,支持 gzip 和 brotli
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 页面标题
|
||||
VITE_APP_TITLE = 若依管理系统
|
||||
VITE_APP_TITLE = 报价管理系统
|
||||
|
||||
# 生产环境配置
|
||||
VITE_APP_ENV = 'staging'
|
||||
|
||||
# 若依管理系统/生产环境
|
||||
# 报价管理系统/生产环境
|
||||
VITE_APP_BASE_API = '/stage-api'
|
||||
|
||||
# 是否在打包时开启压缩,支持 gzip 和 brotli
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"element-plus": "2.13.1",
|
||||
"file-saver": "2.0.5",
|
||||
"fuse.js": "7.1.0",
|
||||
"gsap": "^3.15.0",
|
||||
"js-beautify": "1.15.4",
|
||||
"js-cookie": "3.0.5",
|
||||
"jsencrypt": "3.3.2",
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<el-checkbox-group v-model="selectedItem" @change="handleChange">
|
||||
<el-checkbox v-for="item in props.data" :key="item.value" :label="item.label" border size="large">
|
||||
{{ item.label }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineEmits, defineProps } from "vue";
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
count: {
|
||||
type: Number,
|
||||
default: 1, // 默认只能选择一个
|
||||
},
|
||||
});
|
||||
const selectedItem = ref([]);
|
||||
const emit = defineEmits(["update:modelValue", "change"]);
|
||||
|
||||
const handleChange = (val) => {
|
||||
// 实现单选逻辑:如果选中多个,只保留最后一个
|
||||
if (val.length > props.count) {
|
||||
selectedItem.value = val.slice(-props.count);
|
||||
}
|
||||
emit("update:modelValue", selectedItem.value);
|
||||
emit("change", selectedItem.value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<div
|
||||
ref="eyeRef"
|
||||
class="eyeball"
|
||||
:style="{
|
||||
width: `${props.size}px`,
|
||||
height: props.isBlinking ? '2px' : `${props.size}px`,
|
||||
backgroundColor: props.eyeColor,
|
||||
}"
|
||||
>
|
||||
<template v-if="!props.isBlinking">
|
||||
<div
|
||||
class="eyeball-pupil"
|
||||
:style="{
|
||||
width: `${props.pupilSize}px`,
|
||||
height: `${props.pupilSize}px`,
|
||||
backgroundColor: props.pupilColor,
|
||||
transform: `translate(${pupilOffsetX.value}px, ${pupilOffsetY.value}px)`,
|
||||
}"
|
||||
></div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, watch, computed, onMounted, onUnmounted } from "vue";
|
||||
const props = defineProps({
|
||||
size: { type: Number, default: 48 },
|
||||
pupilSize: { type: Number, default: 16 },
|
||||
maxDistance: { type: Number, default: 10 },
|
||||
eyeColor: { type: String, default: "white" },
|
||||
pupilColor: { type: String, default: "black" },
|
||||
isBlinking: { type: Boolean, default: false },
|
||||
forceLookX: { type: Number, default: undefined },
|
||||
forceLookY: { type: Number, default: undefined },
|
||||
});
|
||||
|
||||
const eyeRef = ref();
|
||||
const pupilOffsetX = ref(0);
|
||||
const pupilOffsetY = ref(0);
|
||||
const mouseX = ref(0);
|
||||
const mouseY = ref(0);
|
||||
|
||||
const updateMousePosition = (e) => {
|
||||
mouseX.value = e.clientX;
|
||||
mouseY.value = e.clientY;
|
||||
updateOffset();
|
||||
};
|
||||
|
||||
const updateOffset = () => {
|
||||
if (!eyeRef.value) return;
|
||||
if (props.forceLookX !== undefined && props.forceLookY !== undefined) {
|
||||
pupilOffsetX.value = props.forceLookX;
|
||||
pupilOffsetY.value = props.forceLookY;
|
||||
return;
|
||||
}
|
||||
const rect = eyeRef.value.getBoundingClientRect();
|
||||
const centerX = rect.left + rect.width / 2;
|
||||
const centerY = rect.top + rect.height / 2;
|
||||
const deltaX = mouseX.value - centerX;
|
||||
const deltaY = mouseY.value - centerY;
|
||||
const distance = Math.min(Math.sqrt(deltaX ** 2 + deltaY ** 2), props.maxDistance);
|
||||
const angle = Math.atan2(deltaY, deltaX);
|
||||
pupilOffsetX.value = Math.cos(angle) * distance;
|
||||
pupilOffsetY.value = Math.sin(angle) * distance;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener("mousemove", updateMousePosition);
|
||||
updateOffset();
|
||||
});
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener("mousemove", updateMousePosition);
|
||||
});
|
||||
watch(
|
||||
() => [props.forceLookX, props.forceLookY],
|
||||
() => updateOffset(),
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 眼球组件 */
|
||||
.eyeball {
|
||||
border-radius: 9999px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition-property: all;
|
||||
transition-duration: 150ms;
|
||||
transition-timing-function: ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.eyeball-pupil {
|
||||
border-radius: 9999px;
|
||||
transition: transform 0.1s ease-out;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div class="eyeball-pupil" :style="pupilStyle"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
size?: string;
|
||||
pupilSize?: string;
|
||||
maxDistance?: number;
|
||||
eyeColor?: string;
|
||||
pupilColor?: string;
|
||||
}
|
||||
|
||||
const { size = "8px", pupilSize = "12px", maxDistance = 10, eyeColor = "white", pupilColor = "black" } = defineProps<Props>();
|
||||
|
||||
const eyeballStyle = {
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: "50%",
|
||||
backgroundColor: eyeColor,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
overflow: "hidden",
|
||||
willChange: "height",
|
||||
};
|
||||
|
||||
const pupilStyle = {
|
||||
width: pupilSize,
|
||||
height: pupilSize,
|
||||
borderRadius: "50%",
|
||||
backgroundColor: pupilColor,
|
||||
willChange: "transform",
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,571 @@
|
||||
<template>
|
||||
<div ref="containerRef" :style="containerStyle">
|
||||
<!-- 紫色角色 -->
|
||||
<div ref="purpleRef" :style="purpleBodyStyle">
|
||||
<div ref="purpleFaceRef" :style="purpleFaceStyle">
|
||||
<EyeBall :size="20" :pupil-size="7" :max-distance="5" eye-color="white" pupil-color="#2D2D2D" />
|
||||
<EyeBall :size="20" :pupil-size="7" :max-distance="5" eye-color="white" pupil-color="#2D2D2D" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 黑色角色 -->
|
||||
<div ref="blackRef" :style="blackBodyStyle">
|
||||
<div ref="blackFaceRef" :style="blackFaceStyle">
|
||||
<EyeBall :size="20" :pupil-size="7" :max-distance="5" eye-color="white" pupil-color="#2D2D2D" />
|
||||
<EyeBall :size="20" :pupil-size="7" :max-distance="5" eye-color="white" pupil-color="#2D2D2D" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 橘黄色角色 -->
|
||||
<div ref="orangeRef" :style="orangeBodyStyle">
|
||||
<div ref="orangeFaceRef" :style="orangeFaceStyle">
|
||||
<Pupil size="8px" :max-distance="5" pupil-color="#2D2D2D" />
|
||||
<Pupil size="8px" :max-distance="5" pupil-color="#2D2D2D" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 黄色角色 -->
|
||||
<div ref="yellowRef" :style="yellowBodyStyle">
|
||||
<div ref="yellowFaceRef" :style="yellowFaceStyle">
|
||||
<Pupil size="8px" :max-distance="5" pupil-color="#2D2D2D" />
|
||||
<Pupil size="8px" :max-distance="5" pupil-color="#2D2D2D" />
|
||||
</div>
|
||||
<div ref="yellowMouthRef" :style="yellowMouthStyle" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, onBeforeUnmount, watch, toRef } from "vue";
|
||||
import gsap from "gsap";
|
||||
import Pupil from "./Pupil.vue";
|
||||
import EyeBall from "./EyeBall.vue";
|
||||
|
||||
interface Props {
|
||||
isTyping?: boolean;
|
||||
showPassword?: boolean;
|
||||
passwordLength?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
isTyping: false,
|
||||
showPassword: false,
|
||||
passwordLength: 0,
|
||||
});
|
||||
|
||||
const containerRef = ref<HTMLElement | null>(null);
|
||||
const mouseRef = reactive({ x: 0, y: 0 });
|
||||
const rafIdRef = ref<number>(0);
|
||||
|
||||
const purpleRef = ref<HTMLElement | null>(null);
|
||||
const blackRef = ref<HTMLElement | null>(null);
|
||||
const yellowRef = ref<HTMLElement | null>(null);
|
||||
const orangeRef = ref<HTMLElement | null>(null);
|
||||
|
||||
const purpleFaceRef = ref<HTMLElement | null>(null);
|
||||
const blackFaceRef = ref<HTMLElement | null>(null);
|
||||
const yellowFaceRef = ref<HTMLElement | null>(null);
|
||||
const orangeFaceRef = ref<HTMLElement | null>(null);
|
||||
|
||||
const yellowMouthRef = ref<HTMLElement | null>(null);
|
||||
|
||||
const purpleBlinkTimerRef = ref<ReturnType<typeof setTimeout>>();
|
||||
const blackBlinkTimerRef = ref<ReturnType<typeof setTimeout>>();
|
||||
const purplePeekTimerRef = ref<ReturnType<typeof setTimeout>>();
|
||||
|
||||
const isHidingPassword = toRef(() => props.passwordLength > 0 && !props.showPassword);
|
||||
const isShowingPassword = toRef(() => props.passwordLength > 0 && props.showPassword);
|
||||
|
||||
const isLookingRef = ref(false);
|
||||
const lookingTimerRef = ref<ReturnType<typeof setTimeout>>();
|
||||
|
||||
const stateRef = reactive({
|
||||
isTyping: false,
|
||||
isHidingPassword: false,
|
||||
isShowingPassword: false,
|
||||
isLooking: false,
|
||||
});
|
||||
|
||||
watch(
|
||||
() => [props.isTyping, isHidingPassword.value, isShowingPassword.value, isLookingRef.value] as const,
|
||||
([isTyping, isHiding, isShowing, isLooking]) => {
|
||||
stateRef.isTyping = isTyping;
|
||||
stateRef.isHidingPassword = isHiding;
|
||||
stateRef.isShowingPassword = isShowing;
|
||||
stateRef.isLooking = isLooking;
|
||||
},
|
||||
);
|
||||
|
||||
// GSAP quickTo instances
|
||||
const quickToRef = ref<Record<string, any> | null>(null);
|
||||
|
||||
const containerStyle = {
|
||||
position: "relative" as const,
|
||||
width: "550px",
|
||||
height: "400px",
|
||||
};
|
||||
|
||||
const purpleBodyStyle = ref<any>({
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: "70px",
|
||||
width: "180px",
|
||||
height: "400px",
|
||||
backgroundColor: "#6C3FF5",
|
||||
borderRadius: "10px 10px 0 0",
|
||||
zIndex: 1,
|
||||
transformOrigin: "bottom center",
|
||||
willChange: "transform",
|
||||
});
|
||||
|
||||
const blackBodyStyle = ref<any>({
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: "240px",
|
||||
width: "120px",
|
||||
height: "310px",
|
||||
backgroundColor: "#2D2D2D",
|
||||
borderRadius: "8px 8px 0 0",
|
||||
zIndex: 2,
|
||||
transformOrigin: "bottom center",
|
||||
willChange: "transform",
|
||||
});
|
||||
|
||||
const orangeBodyStyle = ref<any>({
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
width: "240px",
|
||||
height: "200px",
|
||||
backgroundColor: "#FF9B6B",
|
||||
borderRadius: "120px 120px 0 0",
|
||||
zIndex: 3,
|
||||
transformOrigin: "bottom center",
|
||||
willChange: "transform",
|
||||
});
|
||||
|
||||
const yellowBodyStyle = ref<any>({
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: "310px",
|
||||
width: "140px",
|
||||
height: "230px",
|
||||
backgroundColor: "#E8D754",
|
||||
borderRadius: "70px 70px 0 0",
|
||||
zIndex: 4,
|
||||
transformOrigin: "bottom center",
|
||||
willChange: "transform",
|
||||
});
|
||||
|
||||
const purpleFaceStyle = ref<any>({
|
||||
position: "absolute",
|
||||
display: "flex",
|
||||
gap: "32px",
|
||||
left: "45px",
|
||||
top: "40px",
|
||||
});
|
||||
|
||||
const blackFaceStyle = ref<any>({
|
||||
position: "absolute",
|
||||
display: "flex",
|
||||
gap: "24px",
|
||||
left: "26px",
|
||||
top: "32px",
|
||||
});
|
||||
|
||||
const orangeFaceStyle = ref<any>({
|
||||
position: "absolute",
|
||||
display: "flex",
|
||||
gap: "32px",
|
||||
left: "82px",
|
||||
top: "90px",
|
||||
});
|
||||
|
||||
const yellowFaceStyle = ref<any>({
|
||||
position: "absolute",
|
||||
display: "flex",
|
||||
gap: "24px",
|
||||
left: "52px",
|
||||
top: "40px",
|
||||
});
|
||||
|
||||
const yellowMouthStyle = ref<any>({
|
||||
position: "absolute",
|
||||
width: "80px",
|
||||
height: "4px",
|
||||
backgroundColor: "#2D2D2D",
|
||||
borderRadius: "9999px",
|
||||
left: "40px",
|
||||
top: "88px",
|
||||
});
|
||||
|
||||
// Initialize GSAP
|
||||
onMounted(() => {
|
||||
gsap.set(".pupil", { x: 0, y: 0 });
|
||||
gsap.set(".eyeball-pupil", { x: 0, y: 0 });
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (!purpleRef.value || !blackRef.value || !orangeRef.value || !yellowRef.value || !purpleFaceRef.value || !blackFaceRef.value || !orangeFaceRef.value || !yellowFaceRef.value || !yellowMouthRef.value) return;
|
||||
|
||||
const qt = {
|
||||
purpleSkew: gsap.quickTo(purpleRef.value, "skewX", { duration: 0.3, ease: "power2.out" }),
|
||||
blackSkew: gsap.quickTo(blackRef.value, "skewX", { duration: 0.3, ease: "power2.out" }),
|
||||
orangeSkew: gsap.quickTo(orangeRef.value, "skewX", { duration: 0.3, ease: "power2.out" }),
|
||||
yellowSkew: gsap.quickTo(yellowRef.value, "skewX", { duration: 0.3, ease: "power2.out" }),
|
||||
purpleX: gsap.quickTo(purpleRef.value, "x", { duration: 0.3, ease: "power2.out" }),
|
||||
blackX: gsap.quickTo(blackRef.value, "x", { duration: 0.3, ease: "power2.out" }),
|
||||
purpleHeight: gsap.quickTo(purpleRef.value, "height", { duration: 0.3, ease: "power2.out" }),
|
||||
purpleFaceLeft: gsap.quickTo(purpleFaceRef.value, "left", { duration: 0.3, ease: "power2.out" }),
|
||||
purpleFaceTop: gsap.quickTo(purpleFaceRef.value, "top", { duration: 0.3, ease: "power2.out" }),
|
||||
blackFaceLeft: gsap.quickTo(blackFaceRef.value, "left", { duration: 0.3, ease: "power2.out" }),
|
||||
blackFaceTop: gsap.quickTo(blackFaceRef.value, "top", { duration: 0.3, ease: "power2.out" }),
|
||||
orangeFaceX: gsap.quickTo(orangeFaceRef.value, "x", { duration: 0.2, ease: "power2.out" }),
|
||||
orangeFaceY: gsap.quickTo(orangeFaceRef.value, "y", { duration: 0.2, ease: "power2.out" }),
|
||||
yellowFaceX: gsap.quickTo(yellowFaceRef.value, "x", { duration: 0.2, ease: "power2.out" }),
|
||||
yellowFaceY: gsap.quickTo(yellowFaceRef.value, "y", { duration: 0.2, ease: "power2.out" }),
|
||||
mouthX: gsap.quickTo(yellowMouthRef.value, "x", { duration: 0.2, ease: "power2.out" }),
|
||||
mouthY: gsap.quickTo(yellowMouthRef.value, "y", { duration: 0.2, ease: "power2.out" }),
|
||||
};
|
||||
quickToRef.value = qt;
|
||||
|
||||
const calcPos = (el: HTMLElement) => {
|
||||
const rect = el.getBoundingClientRect();
|
||||
const cx = rect.left + rect.width / 2;
|
||||
const cy = rect.top + rect.height / 3;
|
||||
const dx = mouseRef.x - cx;
|
||||
const dy = mouseRef.y - cy;
|
||||
return {
|
||||
faceX: Math.max(-15, Math.min(15, dx / 20)),
|
||||
faceY: Math.max(-10, Math.min(10, dy / 30)),
|
||||
bodySkew: Math.max(-6, Math.min(6, -dx / 120)),
|
||||
};
|
||||
};
|
||||
|
||||
const calcEyePos = (el: HTMLElement, maxDist: number) => {
|
||||
const r = el.getBoundingClientRect();
|
||||
const cx = r.left + r.width / 2;
|
||||
const cy = r.top + r.height / 2;
|
||||
const dx = mouseRef.x - cx;
|
||||
const dy = mouseRef.y - cy;
|
||||
const dist = Math.min(Math.sqrt(dx ** 2 + dy ** 2), maxDist);
|
||||
const angle = Math.atan2(dy, dx);
|
||||
return { x: Math.cos(angle) * dist, y: Math.sin(angle) * dist };
|
||||
};
|
||||
|
||||
const tick = () => {
|
||||
const container = containerRef.value;
|
||||
if (!container) return;
|
||||
|
||||
const { isTyping: typing, isHidingPassword: hiding, isShowingPassword: showing, isLooking: looking } = stateRef;
|
||||
|
||||
if (purpleRef.value && !showing) {
|
||||
const pp = calcPos(purpleRef.value);
|
||||
if (typing || hiding) {
|
||||
qt.purpleSkew(pp.bodySkew - 12);
|
||||
qt.purpleX(40);
|
||||
qt.purpleHeight(440);
|
||||
} else {
|
||||
qt.purpleSkew(pp.bodySkew);
|
||||
qt.purpleX(0);
|
||||
qt.purpleHeight(400);
|
||||
}
|
||||
}
|
||||
|
||||
if (blackRef.value && !showing) {
|
||||
const bp = calcPos(blackRef.value);
|
||||
if (looking) {
|
||||
qt.blackSkew(bp.bodySkew * 1.5 + 10);
|
||||
qt.blackX(20);
|
||||
} else if (typing || hiding) {
|
||||
qt.blackSkew(bp.bodySkew * 1.5);
|
||||
qt.blackX(0);
|
||||
} else {
|
||||
qt.blackSkew(bp.bodySkew);
|
||||
qt.blackX(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (orangeRef.value && !showing) {
|
||||
const op = calcPos(orangeRef.value);
|
||||
qt.orangeSkew(op.bodySkew);
|
||||
}
|
||||
|
||||
if (yellowRef.value && !showing) {
|
||||
const yp = calcPos(yellowRef.value);
|
||||
qt.yellowSkew(yp.bodySkew);
|
||||
}
|
||||
|
||||
if (purpleRef.value && !showing && !looking) {
|
||||
const pp = calcPos(purpleRef.value);
|
||||
const purpleFaceX = pp.faceX >= 0 ? Math.min(25, pp.faceX * 1.5) : pp.faceX;
|
||||
qt.purpleFaceLeft(45 + purpleFaceX);
|
||||
qt.purpleFaceTop(40 + pp.faceY);
|
||||
}
|
||||
|
||||
if (blackRef.value && !showing && !looking) {
|
||||
const bp = calcPos(blackRef.value);
|
||||
qt.blackFaceLeft(26 + bp.faceX);
|
||||
qt.blackFaceTop(32 + bp.faceY);
|
||||
}
|
||||
|
||||
if (orangeRef.value && !showing) {
|
||||
const op = calcPos(orangeRef.value);
|
||||
qt.orangeFaceX(op.faceX);
|
||||
qt.orangeFaceY(op.faceY);
|
||||
}
|
||||
|
||||
if (yellowRef.value && !showing) {
|
||||
const yp = calcPos(yellowRef.value);
|
||||
qt.yellowFaceX(yp.faceX);
|
||||
qt.yellowFaceY(yp.faceY);
|
||||
qt.mouthX(yp.faceX);
|
||||
qt.mouthY(yp.faceY);
|
||||
}
|
||||
|
||||
if (!showing) {
|
||||
const allPupils = container.querySelectorAll(".pupil");
|
||||
allPupils.forEach((p) => {
|
||||
const el = p as HTMLElement;
|
||||
console.log("--> maxDistance: ", el.dataset.maxDistance);
|
||||
const maxDist = Number(el.dataset.maxDistance) || 5;
|
||||
const ePos = calcEyePos(el, maxDist);
|
||||
gsap.set(el, { x: ePos.x, y: ePos.y });
|
||||
});
|
||||
|
||||
if (!looking) {
|
||||
const allEyeballs = container.querySelectorAll(".eyeball");
|
||||
allEyeballs.forEach((eb) => {
|
||||
const el = eb as HTMLElement;
|
||||
const maxDist = Number(el.dataset.maxDistance) || 10;
|
||||
const pupil = el.querySelector(".eyeball-pupil") as HTMLElement;
|
||||
if (!pupil) return;
|
||||
const ePos = calcEyePos(el, maxDist);
|
||||
gsap.set(pupil, { x: ePos.x, y: ePos.y });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
rafIdRef.value = requestAnimationFrame(tick);
|
||||
};
|
||||
|
||||
const onMove = (e: MouseEvent) => {
|
||||
mouseRef.x = e.clientX;
|
||||
mouseRef.y = e.clientY;
|
||||
};
|
||||
|
||||
window.addEventListener("mousemove", onMove, { passive: true });
|
||||
rafIdRef.value = requestAnimationFrame(tick);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("mousemove", onMove);
|
||||
cancelAnimationFrame(rafIdRef.value);
|
||||
});
|
||||
});
|
||||
|
||||
// Purple character blink
|
||||
onMounted(() => {
|
||||
const purpleEyeballs = purpleRef.value?.querySelectorAll(".eyeball");
|
||||
if (!purpleEyeballs?.length) return;
|
||||
|
||||
const scheduleBlink = () => {
|
||||
purpleBlinkTimerRef.value = setTimeout(
|
||||
() => {
|
||||
purpleEyeballs.forEach((el) => {
|
||||
gsap.to(el, { height: 2, duration: 0.08, ease: "power2.in" });
|
||||
});
|
||||
setTimeout(() => {
|
||||
purpleEyeballs.forEach((el) => {
|
||||
const size = Number((el as HTMLElement).style.width.replace("px", "")) || 18;
|
||||
gsap.to(el, { width: size, height: size, duration: 0.08, ease: "power2.out" });
|
||||
});
|
||||
scheduleBlink();
|
||||
}, 150);
|
||||
},
|
||||
Math.random() * 4000 + 3000,
|
||||
);
|
||||
};
|
||||
|
||||
scheduleBlink();
|
||||
onBeforeUnmount(() => clearTimeout(purpleBlinkTimerRef.value));
|
||||
});
|
||||
|
||||
// Black character blink
|
||||
onMounted(() => {
|
||||
const blackEyeballs = blackRef.value?.querySelectorAll(".eyeball");
|
||||
if (!blackEyeballs?.length) return;
|
||||
|
||||
const scheduleBlink = () => {
|
||||
blackBlinkTimerRef.value = setTimeout(
|
||||
() => {
|
||||
blackEyeballs.forEach((el) => {
|
||||
gsap.to(el, { height: 2, duration: 0.08, ease: "power2.in" });
|
||||
});
|
||||
setTimeout(() => {
|
||||
blackEyeballs.forEach((el) => {
|
||||
const size = Number((el as HTMLElement).style.width.replace("px", "")) || 16;
|
||||
gsap.to(el, { height: size, duration: 0.08, ease: "power2.out" });
|
||||
});
|
||||
scheduleBlink();
|
||||
}, 150);
|
||||
},
|
||||
Math.random() * 4000 + 3000,
|
||||
);
|
||||
};
|
||||
|
||||
scheduleBlink();
|
||||
onBeforeUnmount(() => clearTimeout(blackBlinkTimerRef.value));
|
||||
});
|
||||
|
||||
const applyLookAtEachOther = () => {
|
||||
const qt = quickToRef.value;
|
||||
if (qt) {
|
||||
qt.purpleFaceLeft(55);
|
||||
qt.purpleFaceTop(65);
|
||||
qt.blackFaceLeft(32);
|
||||
qt.blackFaceTop(12);
|
||||
}
|
||||
purpleRef.value?.querySelectorAll(".eyeball-pupil").forEach((p) => {
|
||||
gsap.to(p, { x: 3, y: 4, duration: 0.3, ease: "power2.out", overwrite: "auto" });
|
||||
});
|
||||
blackRef.value?.querySelectorAll(".eyeball-pupil").forEach((p) => {
|
||||
gsap.to(p, { x: 0, y: -4, duration: 0.3, ease: "power2.out", overwrite: "auto" });
|
||||
});
|
||||
};
|
||||
|
||||
const applyHidingPassword = () => {
|
||||
const qt = quickToRef.value;
|
||||
if (qt) {
|
||||
qt.purpleFaceLeft(55);
|
||||
qt.purpleFaceTop(65);
|
||||
}
|
||||
};
|
||||
|
||||
const applyShowPassword = () => {
|
||||
const qt = quickToRef.value;
|
||||
if (qt) {
|
||||
qt.purpleSkew(0);
|
||||
qt.blackSkew(0);
|
||||
qt.orangeSkew(0);
|
||||
qt.yellowSkew(0);
|
||||
qt.purpleX(0);
|
||||
qt.blackX(0);
|
||||
qt.purpleHeight(400);
|
||||
|
||||
qt.purpleFaceLeft(20);
|
||||
qt.purpleFaceTop(35);
|
||||
qt.blackFaceLeft(10);
|
||||
qt.blackFaceTop(28);
|
||||
qt.orangeFaceX(50 - 82);
|
||||
qt.orangeFaceY(85 - 90);
|
||||
qt.yellowFaceX(20 - 52);
|
||||
qt.yellowFaceY(35 - 40);
|
||||
qt.mouthX(10 - 40);
|
||||
qt.mouthY(0);
|
||||
}
|
||||
|
||||
purpleRef.value?.querySelectorAll(".eyeball-pupil").forEach((p) => {
|
||||
gsap.to(p, { x: -4, y: -4, duration: 0.3, ease: "power2.out", overwrite: "auto" });
|
||||
});
|
||||
blackRef.value?.querySelectorAll(".eyeball-pupil").forEach((p) => {
|
||||
gsap.to(p, { x: -4, y: -4, duration: 0.3, ease: "power2.out", overwrite: "auto" });
|
||||
});
|
||||
orangeRef.value?.querySelectorAll(".pupil").forEach((p) => {
|
||||
gsap.to(p, { x: -5, y: -4, duration: 0.3, ease: "power2.out", overwrite: "auto" });
|
||||
});
|
||||
yellowRef.value?.querySelectorAll(".pupil").forEach((p) => {
|
||||
gsap.to(p, { x: -5, y: -4, duration: 0.3, ease: "power2.out", overwrite: "auto" });
|
||||
});
|
||||
};
|
||||
|
||||
// Password peek effect
|
||||
watch(
|
||||
() => [isShowingPassword.value, props.passwordLength],
|
||||
([showing, len]) => {
|
||||
if (!showing || (len as number) <= 0) {
|
||||
clearTimeout(purplePeekTimerRef.value);
|
||||
return;
|
||||
}
|
||||
|
||||
const purpleEyePupils = purpleRef.value?.querySelectorAll(".eyeball-pupil");
|
||||
if (!purpleEyePupils?.length) return;
|
||||
|
||||
const schedulePeek = () => {
|
||||
purplePeekTimerRef.value = setTimeout(
|
||||
() => {
|
||||
purpleEyePupils.forEach((p) => {
|
||||
gsap.to(p, {
|
||||
x: 4,
|
||||
y: 5,
|
||||
duration: 0.3,
|
||||
ease: "power2.out",
|
||||
overwrite: "auto",
|
||||
});
|
||||
});
|
||||
const qt = quickToRef.value;
|
||||
if (qt) {
|
||||
qt.purpleFaceLeft(20);
|
||||
qt.purpleFaceTop(35);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
purpleEyePupils.forEach((p) => {
|
||||
gsap.to(p, {
|
||||
x: -4,
|
||||
y: -4,
|
||||
duration: 0.3,
|
||||
ease: "power2.out",
|
||||
overwrite: "auto",
|
||||
});
|
||||
});
|
||||
schedulePeek();
|
||||
}, 800);
|
||||
},
|
||||
Math.random() * 3000 + 2000,
|
||||
);
|
||||
};
|
||||
|
||||
schedulePeek();
|
||||
onBeforeUnmount(() => clearTimeout(purplePeekTimerRef.value));
|
||||
},
|
||||
);
|
||||
|
||||
// Look at each other when typing
|
||||
watch(
|
||||
() => [props.isTyping, isShowingPassword.value],
|
||||
([typing, showing]) => {
|
||||
if (typing && !showing) {
|
||||
isLookingRef.value = true;
|
||||
stateRef.isLooking = true;
|
||||
applyLookAtEachOther();
|
||||
|
||||
clearTimeout(lookingTimerRef.value);
|
||||
lookingTimerRef.value = setTimeout(() => {
|
||||
isLookingRef.value = false;
|
||||
stateRef.isLooking = false;
|
||||
purpleRef.value?.querySelectorAll(".eyeball-pupil").forEach((p) => {
|
||||
gsap.killTweensOf(p);
|
||||
});
|
||||
}, 800);
|
||||
} else {
|
||||
clearTimeout(lookingTimerRef.value);
|
||||
isLookingRef.value = false;
|
||||
stateRef.isLooking = false;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Password state effects
|
||||
watch(
|
||||
() => {
|
||||
return [isShowingPassword.value, isHidingPassword.value];
|
||||
},
|
||||
([showing, hiding]) => {
|
||||
if (showing) {
|
||||
applyShowPassword();
|
||||
} else if (hiding) {
|
||||
applyHidingPassword();
|
||||
}
|
||||
},
|
||||
);
|
||||
</script>
|
||||
@@ -62,6 +62,6 @@ export default {
|
||||
/**
|
||||
* 底部版权文本内容
|
||||
*/
|
||||
footerContent: 'Copyright © 2018-2026 RuoYi. All Rights Reserved.'
|
||||
footerContent: 'Copyright © 2018-2026 LINGTAO. All Rights Reserved.'
|
||||
}
|
||||
|
||||
|
||||
+132
-101
@@ -1,62 +1,49 @@
|
||||
<template>
|
||||
<div class="login">
|
||||
<div class="character-container">
|
||||
<LoginCharacter :isTyping="isTyping" :showPassword="showPassword" :passwordLength="loginForm.password.length" />
|
||||
</div>
|
||||
<div class="login-container">
|
||||
<el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
|
||||
<h3 class="title">{{ title }}</h3>
|
||||
<el-label class="login-title">账号</el-label>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="loginForm.username"
|
||||
type="text"
|
||||
size="large"
|
||||
auto-complete="off"
|
||||
placeholder="账号"
|
||||
>
|
||||
<template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
|
||||
</el-input>
|
||||
<el-input v-model="loginForm.username" @focus="isTyping = true" @blur="isTyping = false" type="text" size="large" auto-complete="off" placeholder="账号"> </el-input>
|
||||
</el-form-item>
|
||||
<el-label class="login-title">密码</el-label>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="loginForm.password"
|
||||
type="password"
|
||||
size="large"
|
||||
auto-complete="off"
|
||||
placeholder="密码"
|
||||
@keyup.enter="handleLogin"
|
||||
>
|
||||
<template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
|
||||
<el-input v-model="loginForm.password" :type="showPassword ? 'text' : 'password'" size="large" auto-complete="off" placeholder="密码" @keyup.enter="handleLogin" @input="inputPassWord">
|
||||
<template #suffix>
|
||||
<view @click="showPassword = !showPassword" style="cursor: pointer">
|
||||
<template v-if="!showPassword">
|
||||
<el-icon><View /></el-icon>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-icon><Hide /></el-icon>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-label class="login-title">验证码</el-label>
|
||||
<el-form-item prop="code" v-if="captchaEnabled">
|
||||
<el-input
|
||||
v-model="loginForm.code"
|
||||
size="large"
|
||||
auto-complete="off"
|
||||
placeholder="验证码"
|
||||
style="width: 63%"
|
||||
@keyup.enter="handleLogin"
|
||||
>
|
||||
<template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
|
||||
</el-input>
|
||||
<el-input v-model="loginForm.code" size="large" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter="handleLogin"> </el-input>
|
||||
<div class="login-code">
|
||||
<img :src="codeUrl" @click="getCode" class="login-code-img" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
|
||||
<el-form-item style="width:100%;">
|
||||
<el-button
|
||||
:loading="loading"
|
||||
size="large"
|
||||
type="primary"
|
||||
style="width:100%;"
|
||||
@click.prevent="handleLogin"
|
||||
>
|
||||
<el-checkbox v-model="loginForm.rememberMe" style="margin: 0px 0px 25px 0px">记住密码</el-checkbox>
|
||||
<el-form-item style="width: 100%">
|
||||
<el-button :loading="loading" size="large" type="primary" style="width: 100%" @click.prevent="handleLogin">
|
||||
<span v-if="!loading">登 录</span>
|
||||
<span v-else>登 录 中...</span>
|
||||
</el-button>
|
||||
<div style="float: right;" v-if="register">
|
||||
<div style="float: right" v-if="register">
|
||||
<router-link class="link-type" :to="'/register'">立即注册</router-link>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<!-- 底部 -->
|
||||
<div class="el-login-footer">
|
||||
<span>{{ footerContent }}</span>
|
||||
@@ -65,131 +52,160 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getCodeImg } from "@/api/login"
|
||||
import Cookies from "js-cookie"
|
||||
import { encrypt, decrypt } from "@/utils/jsencrypt"
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import defaultSettings from '@/settings'
|
||||
import { getCodeImg } from "@/api/login";
|
||||
import Cookies from "js-cookie";
|
||||
import { encrypt, decrypt } from "@/utils/jsencrypt";
|
||||
import useUserStore from "@/store/modules/user";
|
||||
import defaultSettings from "@/settings";
|
||||
import LoginCharacter from "@/components/LoginCharacters";
|
||||
|
||||
const title = import.meta.env.VITE_APP_TITLE
|
||||
const footerContent = defaultSettings.footerContent
|
||||
const userStore = useUserStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { proxy } = getCurrentInstance()
|
||||
const title = import.meta.env.VITE_APP_TITLE;
|
||||
const footerContent = defaultSettings.footerContent;
|
||||
const userStore = useUserStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const loginForm = ref({
|
||||
username: "admin",
|
||||
password: "admin123",
|
||||
rememberMe: false,
|
||||
code: "",
|
||||
uuid: ""
|
||||
})
|
||||
uuid: "",
|
||||
});
|
||||
|
||||
const loginRules = {
|
||||
username: [{ required: true, trigger: "blur", message: "请输入您的账号" }],
|
||||
password: [{ required: true, trigger: "blur", message: "请输入您的密码" }],
|
||||
code: [{ required: true, trigger: "change", message: "请输入验证码" }]
|
||||
}
|
||||
code: [{ required: true, trigger: "change", message: "请输入验证码" }],
|
||||
};
|
||||
|
||||
const codeUrl = ref("")
|
||||
const loading = ref(false)
|
||||
const codeUrl = ref("");
|
||||
const loading = ref(false);
|
||||
// 验证码开关
|
||||
const captchaEnabled = ref(true)
|
||||
const captchaEnabled = ref(true);
|
||||
// 注册开关
|
||||
const register = ref(false)
|
||||
const redirect = ref(undefined)
|
||||
const register = ref(false);
|
||||
const redirect = ref(undefined);
|
||||
|
||||
watch(route, (newRoute) => {
|
||||
redirect.value = newRoute.query && newRoute.query.redirect
|
||||
}, { immediate: true })
|
||||
const inputPassWordRef = ref(null);
|
||||
const isTyping = ref(false);
|
||||
const showPassword = ref(false);
|
||||
const passwordLength = ref(0);
|
||||
|
||||
watch(
|
||||
route,
|
||||
(newRoute) => {
|
||||
redirect.value = newRoute.query && newRoute.query.redirect;
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
function handleLogin() {
|
||||
proxy.$refs.loginRef.validate(valid => {
|
||||
proxy.$refs.loginRef.validate((valid) => {
|
||||
if (valid) {
|
||||
loading.value = true
|
||||
loading.value = true;
|
||||
// 勾选了需要记住密码设置在 cookie 中设置记住用户名和密码
|
||||
if (loginForm.value.rememberMe) {
|
||||
Cookies.set("username", loginForm.value.username, { expires: 30 })
|
||||
Cookies.set("password", encrypt(loginForm.value.password), { expires: 30 })
|
||||
Cookies.set("rememberMe", loginForm.value.rememberMe, { expires: 30 })
|
||||
Cookies.set("username", loginForm.value.username, { expires: 30 });
|
||||
Cookies.set("password", encrypt(loginForm.value.password), { expires: 30 });
|
||||
Cookies.set("rememberMe", loginForm.value.rememberMe, { expires: 30 });
|
||||
} else {
|
||||
// 否则移除
|
||||
Cookies.remove("username")
|
||||
Cookies.remove("password")
|
||||
Cookies.remove("rememberMe")
|
||||
Cookies.remove("username");
|
||||
Cookies.remove("password");
|
||||
Cookies.remove("rememberMe");
|
||||
}
|
||||
// 调用action的登录方法
|
||||
userStore.login(loginForm.value).then(() => {
|
||||
const query = route.query
|
||||
userStore
|
||||
.login(loginForm.value)
|
||||
.then(() => {
|
||||
const query = route.query;
|
||||
const otherQueryParams = Object.keys(query).reduce((acc, cur) => {
|
||||
if (cur !== "redirect") {
|
||||
acc[cur] = query[cur]
|
||||
acc[cur] = query[cur];
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
router.push({ path: redirect.value || "/", query: otherQueryParams })
|
||||
}).catch(() => {
|
||||
loading.value = false
|
||||
return acc;
|
||||
}, {});
|
||||
router.push({ path: redirect.value || "/", query: otherQueryParams });
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
// 重新获取验证码
|
||||
if (captchaEnabled.value) {
|
||||
getCode()
|
||||
getCode();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function getCode() {
|
||||
getCodeImg().then(res => {
|
||||
captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled
|
||||
getCodeImg().then((res) => {
|
||||
captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled;
|
||||
if (captchaEnabled.value) {
|
||||
codeUrl.value = "data:image/gif;base64," + res.img
|
||||
loginForm.value.uuid = res.uuid
|
||||
codeUrl.value = "data:image/gif;base64," + res.img;
|
||||
loginForm.value.uuid = res.uuid;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function getCookie() {
|
||||
const username = Cookies.get("username")
|
||||
const password = Cookies.get("password")
|
||||
const rememberMe = Cookies.get("rememberMe")
|
||||
const username = Cookies.get("username");
|
||||
const password = Cookies.get("password");
|
||||
const rememberMe = Cookies.get("rememberMe");
|
||||
loginForm.value = {
|
||||
username: username === undefined ? loginForm.value.username : username,
|
||||
password: password === undefined ? loginForm.value.password : decrypt(password),
|
||||
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
|
||||
}
|
||||
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
|
||||
};
|
||||
}
|
||||
|
||||
getCode()
|
||||
getCookie()
|
||||
getCode();
|
||||
getCookie();
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
<style lang="scss" scoped>
|
||||
.character-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.login {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
background-image: url("../assets/images/login-background.jpg");
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
background-color: #707070;
|
||||
background-size: cover;
|
||||
}
|
||||
.title {
|
||||
margin: 0px auto 30px auto;
|
||||
margin: 0px auto 90px auto;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
color: #707070;
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
border-radius: 6px;
|
||||
.login-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
background: #ffffff;
|
||||
width: 400px;
|
||||
}
|
||||
.login-form {
|
||||
padding: 25px 25px 5px 25px;
|
||||
|
||||
z-index: 1;
|
||||
.el-input {
|
||||
height: 40px;
|
||||
height: 50px;
|
||||
input {
|
||||
height: 40px;
|
||||
height: 50px;
|
||||
}
|
||||
}
|
||||
.input-icon {
|
||||
@@ -236,4 +252,19 @@ html.dark .login {
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-input__wrapper) {
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
:deep(.el-input--large) {
|
||||
font-size: 18px;
|
||||
}
|
||||
.login-title {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
:deep(.el-form-item--default) {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="line-container">
|
||||
<view class="form-title">裁切工艺:</view>
|
||||
<ChekcBoxOnly :data="craftData[0].options" v-model="craftData[0].value" />
|
||||
</view>
|
||||
<view class="line-container">
|
||||
<view class="form-title">覆膜工艺:</view>
|
||||
<ChekcBoxOnly :data="craftData[1].options" v-model="craftData[1].value" />
|
||||
</view>
|
||||
<view class="line-container">
|
||||
<!-- 刮刮膜带有子选择 -->
|
||||
<view class="form-title">刮刮膜:</view>
|
||||
<ChekcBoxOnly :data="craftData[2].options" v-model="craftData[2].value" />
|
||||
<el-select v-if="craftData[2].value.length > 0" v-model="craftData[2].size" placeholder="选择尺寸" style="width: 100px; margin-left: 20px">
|
||||
<el-option label="6*18mm" value="6*18" />
|
||||
</el-select>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineEmits, defineProps, watch } from "vue";
|
||||
import ChekcBoxOnly from "@/components/CheckBoxOnly";
|
||||
|
||||
const craftData = ref([
|
||||
{
|
||||
value: ["模切"],
|
||||
options: [
|
||||
{ value: "模切", label: "模切" },
|
||||
{ value: "裁切", label: "裁切" },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: [],
|
||||
options: [
|
||||
{ value: "覆哑膜", label: "覆哑膜" },
|
||||
{ value: "覆亮膜", label: "覆亮膜" },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: [],
|
||||
process: [],
|
||||
size: "",
|
||||
options: [
|
||||
{ value: "配刮刮膜", label: "配刮刮膜" },
|
||||
{ value: "粘刮刮膜", label: "粘刮刮膜" },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
|
||||
watch(
|
||||
craftData,
|
||||
(val) => {
|
||||
let params = [];
|
||||
val.forEach((item) => {
|
||||
if (item.value.length > 0) {
|
||||
item.value.forEach((i) => {
|
||||
let info = {
|
||||
name: i,
|
||||
value: i,
|
||||
};
|
||||
if (i == "配刮刮膜" || i == "粘刮刮膜") {
|
||||
info.size = item.size;
|
||||
}
|
||||
params.push(info);
|
||||
});
|
||||
}
|
||||
});
|
||||
emit("update:modelValue", params);
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.line-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.form-title {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<h1 style="font-weight: bold">不干胶</h1>
|
||||
<el-form>
|
||||
<el-form-item>
|
||||
<el-radio-group v-model="data.types" @change="handleChange">
|
||||
<el-radio label="常用种类" value="常用种类" size="large" border />
|
||||
<el-radio label="专版打印" value="专版打印" size="large" border />
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<view class="form-title">品种</view>
|
||||
<el-form-item>
|
||||
<el-select v-model="data.material" placeholder="请选择" size="large" style="width: 300px">
|
||||
<template v-for="item in materials" :key="item.name">
|
||||
<el-option :label="item.name" :value="item.name"></el-option>
|
||||
</template>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<view class="form-title">尺寸</view>
|
||||
<el-form-item>
|
||||
<el-input style="width: 150px" v-model="data.size[0]" type="number">
|
||||
<template #append> <text>cm</text></template>
|
||||
</el-input>
|
||||
<text style="margin: 0 10px">X</text>
|
||||
<el-input style="width: 150px" v-model="data.size[1]" type="number">
|
||||
<template #append> <text>cm</text> </template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<view class="form-title">个数</view>
|
||||
<el-form-item>
|
||||
<el-input style="width: 300px" v-model="data.count" type="number" placeholder="请输入数量" size="large"></el-input>
|
||||
</el-form-item>
|
||||
<view class="form-title">款数</view>
|
||||
<el-form-item>
|
||||
<el-input style="width: 300px" v-model="data.number" type="number" placeholder="请输入数量" size="large"></el-input>
|
||||
</el-form-item>
|
||||
<view class="form-title">客户旺旺</view>
|
||||
<el-form-item>
|
||||
<el-input style="width: 300px" v-model="data.number" placeholder="请输入客户旺旺" size="large"></el-input>
|
||||
</el-form-item>
|
||||
<component v-if="currentCraftComponent" :is="currentCraftComponent" v-model="data.craft" />
|
||||
<el-button type="primary" @click="handlesave">保存</el-button>
|
||||
</el-form>
|
||||
</view>
|
||||
</template>
|
||||
<script setup>
|
||||
import { reactive, watch, shallowRef } from "vue";
|
||||
import copperplate from "./crafts/copperplate.vue";
|
||||
|
||||
const materials = [
|
||||
{ name: "铜板纸不干胶", images: "" },
|
||||
{ name: "pvc不干胶", images: "" },
|
||||
{ name: "透明不干胶", images: "" },
|
||||
{ name: "牛皮纸", images: "" },
|
||||
{ name: "哑金不干胶", images: "" },
|
||||
{ name: "哑银不干胶", images: "" },
|
||||
{ name: "书写纸不干胶", images: "" },
|
||||
{ name: "银平光", images: "" },
|
||||
{ name: "拉丝金", images: "" },
|
||||
{ name: "拉丝银", images: "" },
|
||||
{ name: "美纹纸", images: "" },
|
||||
{ name: "PP合成纸", images: "" },
|
||||
{ name: "易碎纸不干胶", images: "" },
|
||||
{ name: "刚古水纹超白", images: "" },
|
||||
{ name: "红底散金", images: "" },
|
||||
{ name: "布纹纸超白", images: "" },
|
||||
{ name: "珠光超白", images: "" },
|
||||
{ name: "10C静电膜", images: "" },
|
||||
{ name: "树纹纸", images: "" },
|
||||
{ name: "草香纸/大地纸", images: "" },
|
||||
{ name: "单防热敏纸(底纸白色)", images: "" },
|
||||
{ name: "三防热敏纸(底纸蓝色)", images: "" },
|
||||
];
|
||||
|
||||
const craftMap = {
|
||||
铜板纸不干胶: copperplate,
|
||||
};
|
||||
const currentCraftComponent = shallowRef(null);
|
||||
|
||||
const handlesave = () => {
|
||||
console.log(data.value);
|
||||
};
|
||||
const data = ref({
|
||||
types: "常用种类",
|
||||
material: "",
|
||||
size: [],
|
||||
count: null,
|
||||
number: 1,
|
||||
craft: [],
|
||||
});
|
||||
|
||||
watch(
|
||||
() => data.value.material,
|
||||
(mat) => {
|
||||
currentCraftComponent.value = craftMap[mat] || null;
|
||||
data.value.craft = {}; // 重置
|
||||
},
|
||||
);
|
||||
|
||||
const handleChange = (val) => {
|
||||
const initData = {
|
||||
types: val,
|
||||
material: "",
|
||||
size: [],
|
||||
count: null,
|
||||
number: 1,
|
||||
craft: [],
|
||||
};
|
||||
data.value = initData;
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.el-form-item--default {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.container {
|
||||
margin: 0 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.form-title {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user