This commit is contained in:
lzp
2026-03-09 14:19:44 +08:00
parent 3b2b49b0f5
commit 8600e20753
7 changed files with 0 additions and 783 deletions

View File

@@ -1,79 +0,0 @@
<template>
<div class="my-input">
<span class="decorate"></span>
<span v-show="icon" class="icon">
<svg-icon :name="icon" :size="iconSize" size-unit="px" />
</span>
<span v-show="before" class="before">{{ before }}</span>
<input v-bind="attrs" :value="modelValue" @input="onInput" @copy.stop @keydown.stop />
<span v-show="after" class="after">{{ after }}</span>
</div>
</template>
<script setup lang="ts">
import { ref, useAttrs, watch } from 'vue'
const props = defineProps({
modelValue: { type: [String, Number] },
icon: { default: '', type: String },
iconSize: { default: '10', type: [Number, String] },
before: { default: '', type: String },
after: { default: '', type: String }
})
const attrs = useAttrs()
const emit = defineEmits(['update:modelValue', 'input'])
const onInput = (e) => {
var value = e.target.value
if (attrs.type === 'number') value = Number(value)
emit('update:modelValue', value)
emit('input', value)
}
</script>
<style scoped lang="less">
.my-input {
display: flex;
align-items: center;
width: 100%;
border: 1px solid rgba(230, 230, 231, 1);
border-radius: 1.7px;
height: 17px;
padding: 0 4px 0 2px;
> .decorate {
width: 2px;
background-color: rgba(230, 230, 231, 1);
border-radius: 3px;
height: 85%;
margin-right: 4px;
}
> .iconfont {
font-size: 12px;
color: #000;
margin-right: 2px;
}
> .before {
font-size: 12px;
color: #000;
margin-right: 2px;
}
> .after {
font-size: 12px;
color: #000;
margin-left: 1px;
}
> input {
font-size: 12px;
width: 0;
flex: 1;
text-align: right;
outline: none;
border: none;
background-color: transparent;
padding: 0;
-moz-appearance: textfield; /* Firefox */
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
}
}
</style>

View File

@@ -1,52 +0,0 @@
<template>
<div class="my-select">
<el-select :model-value="modelValue" @change="onChange" v-bind="attrs">
<el-option v-for="v in list" :key="v.value" :label="v.label" :value="v.value" />
</el-select>
</div>
</template>
<script setup lang="ts">
import { ref, useAttrs, watch } from 'vue'
const props = defineProps({
modelValue: { required: true },
list: { default: () => [], type: [Array, Object] }
})
const attrs = useAttrs()
const emit = defineEmits(['update:modelValue', 'change'])
const onChange = (value) => {
emit('update:modelValue', value)
emit('change', value)
}
</script>
<style scoped lang="less">
.my-select {
&:deep(.el-select) {
--el-select-input-font-size: 12px;
.el-select__wrapper {
font-size: 12px;
min-height: 0;
height: 28px;
padding: 0 8px;
}
.el-select__selected-item,
.el-select__input-wrapper,
.el-select__placeholder {
line-height: normal;
}
.el-select__input {
height: 24px;
}
}
}
.el-popper {
.el-select-dropdown {
li {
padding-left: 8px;
height: 30px;
line-height: 30px;
font-size: 12px;
}
}
}
</style>

View File

@@ -1,86 +0,0 @@
<template>
<div class="my-textarea">
<textarea
:placeholder="placeholder"
:value="modelValue"
@input="onInput"
@change="onChange"
@copy.stop
@keydown.stop
></textarea>
<div class="bths">
<button><svg-icon name="mobang" size="10" size-unit="px" /></button>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref, markRaw, onMounted } from 'vue'
const emit = defineEmits(['update:modelValue', 'input', 'change'])
const props = defineProps({
modelValue: { type: String },
placeholder: {
type: String,
default: 'Enter the scene you want to describe...'
}
})
const onInput = (e) => {
const value = e.target.value
emit('update:modelValue', value)
emit('input', value)
}
const onChange = (e) => {
emit('change', e.target.value)
}
</script>
<style lang="less" scoped>
.my-textarea {
width: 100%;
height: 115px;
border: 1px solid #e4e4e7;
border-radius: 10px;
padding: 10px 5px;
display: flex;
flex-direction: column;
> textarea {
padding: 0 5px;
width: 100%;
flex: 1;
border: none;
outline: none;
resize: none;
font-family: Medium;
font-size: 10px;
color: #333;
&::placeholder {
color: #c9c9c9;
}
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-thumb {
border-radius: 4px;
background: rgba(0, 0, 0, 0.2);
}
&::-webkit-scrollbar-track {
border-radius: 4px;
background: rgba(0, 0, 0, 0.1);
}
}
> .bths {
padding: 5px 5px 0;
> button {
width: 20px;
height: 20px;
padding: 0;
border-radius: 4px;
background-color: #f0f0f0;
border: 1px solid #e4e4e7;
&:active {
opacity: 0.5;
}
}
}
}
</style>

View File

@@ -1,217 +0,0 @@
<template>
<div class="offset-tool">
<div class="input" v-show="showInput">
<my-input v-model="left" type="number" before="X" after="%" :min="-100" :max="100" />
<my-input v-model="top" type="number" before="Y" after="%" :min="-100" :max="100" />
</div>
<div
class="dish"
@mousedown="mousedown"
@touchstart="mousedown"
ref="dishRef"
v-show="showDish"
>
<img src="/src/assets/images/icon/xyz.png" />
<span class="ball" :style="ballStyle"></span>
<span class="tip x">X: {{ left }}%</span>
<span class="tip y">Y: {{ top }}%</span>
<span class="line x"></span>
<span class="line y"></span>
<span class="line z" :style="lineZStyle"></span>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch, computed } from 'vue'
import MyInput from './my-input.vue'
const props = defineProps({
modelValue: { type: Object as () => { x: number; y: number } },
showInput: {
type: Boolean,
default: true
},
showDish: {
type: Boolean,
default: true
}
})
const emit = defineEmits(['update:modelValue', 'change', 'input'])
// 工具的实际坐标 -100 ~ 100
const top = ref(Math.round(props.modelValue.y))
const left = ref(Math.round(props.modelValue.x))
// 原点的坐标 0 ~ 100
const ballStyle = computed(() => ({
top: 50 + top.value / 2 + '%',
left: 50 + left.value / 2 + '%'
}))
watch(
() => props.modelValue.x,
(v) => (left.value = v)
)
watch(
() => props.modelValue.y,
(v) => (top.value = v)
)
const dishRef = ref<HTMLDivElement>()
const mousedown = (e: MouseEvent | TouchEvent) => {
if (!dishRef.value) return
const mousemove = (e: MouseEvent | TouchEvent) => {
if (!dishRef.value) return
const rect = dishRef.value.getBoundingClientRect()
const X = e.clientX || (e as TouchEvent).touches[0].clientX
const Y = e.clientY || (e as TouchEvent).touches[0].clientY
var x = ((X - rect.left) / rect.width) * 100
var y = ((Y - rect.top) / rect.height) * 100
if (x < 0) x = 0
if (x > 100) x = 100
if (y < 0) y = 0
if (y > 100) y = 100
left.value = Math.round((x - 50) * 2)
top.value = Math.round((y - 50) * 2)
onInput()
}
mousemove(e)
const mouseup = () => {
onChange()
document.removeEventListener('mousemove', mousemove)
document.removeEventListener('touchmove', mousemove)
document.removeEventListener('mouseup', mouseup)
document.removeEventListener('touchend', mouseup)
}
document.addEventListener('mousemove', mousemove)
document.addEventListener('touchmove', mousemove)
document.addEventListener('mouseup', mouseup)
document.addEventListener('touchend', mouseup)
}
const onInput = () => {
const value = {
x: left.value,
y: top.value
}
emit('update:modelValue', value)
emit('input', value)
}
var changeTime: any = null
const onChange = () => {
clearTimeout(changeTime)
changeTime = setTimeout(() => {
const value = {
x: left.value,
y: top.value
}
emit('update:modelValue', value)
emit('change', value)
}, 500)
}
const lineZStyle = computed(() => ({
'--rotateZ': calculateAngle(0, 0, left.value, top.value) + 'deg',
width: calculateDistance(0, 0, left.value, top.value) / 2 + '%'
}))
// 计算角度
function calculateAngle(x1: number, y1: number, x2: number, y2: number) {
const deltaX = x2 - x1
const deltaY = y1 - y2
let angle = Math.atan2(deltaX, deltaY) * (180 / Math.PI) - 90
return angle
}
// 计算距离
function calculateDistance(x1: number, y1: number, x2: number, y2: number) {
const deltaX = x2 - x1
const deltaY = y2 - y1
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY)
return distance
}
</script>
<style scoped lang="less">
.offset-tool {
position: relative;
> .input {
display: flex;
align-items: center;
justify-content: center;
> * {
flex: 1;
margin-right: 10px;
&:last-child {
margin-right: 0;
}
}
}
> .dish {
width: 115px;
height: 115px;
border: 1px solid #eaeaea;
border-radius: 3.4px;
cursor: pointer;
position: relative;
background-color: #f6f6f6;
margin-top: 20px;
> * {
position: absolute;
pointer-events: none;
user-select: none;
}
> img {
width: 12px;
height: 12px;
bottom: 3.5px;
right: 3.5px;
}
> .ball {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 8.5px;
height: 8.5px;
border: 1px solid #fff;
background-color: #333;
border-radius: 50%;
box-shadow: 0px 0.68px 0.17px 0px rgba(0, 0, 0, 0.26);
}
> .tip {
font-size: 8.5px;
color: #000;
line-height: 24px;
&.x {
top: 50%;
right: 0%;
transform: translate(100%, -50%);
padding-left: 6px;
}
&.y {
top: 0%;
left: 50%;
transform: translate(-50%, -100%);
}
}
> .line {
border-color: #d9d9d9;
border-style: dashed;
border-width: 0;
width: 0;
height: 0;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
&.x {
width: 100%;
border-top-width: 1px;
}
&.y {
height: 100%;
border-left-width: 1px;
}
&.z {
width: 50%;
border-top-width: 1px;
border-color: #454754;
transform: translate(0%, -50%) rotateZ(var(--rotateZ));
transform-origin: left center;
}
}
}
}
</style>

View File

@@ -1,69 +0,0 @@
<template>
<div class="pixel-ratio-selection">
<div
v-for="v in list"
:key="v"
:class="{ active: v === modelValue }"
@click="onChange(v)"
:style="{ '--w': v.split(':')[0], '--h': v.split(':')[1] }"
>
{{ v }}
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, defineExpose } from 'vue'
const emit = defineEmits(['update:modelValue', 'change'])
const props = defineProps({
modelValue: { type: String },
list: {
type: Array,
default: () => ['1:1', '4:3', '3:4', '16:9']
}
})
const data = reactive({})
const onChange = (v) => {
emit('update:modelValue', v)
emit('change', v)
}
defineExpose({ data })
</script>
<style lang="less" scoped>
.pixel-ratio-selection {
width: 100%;
height: 34px;
border-radius: 6px;
background-color: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
padding: 0 17px;
user-select: none;
> div {
text-align: center;
cursor: pointer;
font-size: 12px;
color: #7c7c7c;
height: 21px;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
&.active {
border-radius: 4px;
background-color: #fff;
}
&::before {
content: '';
border-radius: 1px;
border: 1px solid #7c7c7c;
width: calc(var(--w) / max(var(--w), var(--h)) * 10px);
height: calc(var(--h) / max(var(--w), var(--h)) * 10px);
margin-right: 4px;
box-sizing: border-box;
}
}
}
</style>

View File

@@ -1,172 +0,0 @@
<template>
<div class="slider" :disabled="disabled">
<div
class="input-range"
:style="{
'--progress': (value - props.min) / (props.max - props.min)
}"
>
<span class="tip">{{ props.tipFormatter(value) }}</span>
<input
type="range"
v-model="value"
v-bind="$attrs"
@input="onInput"
@change="onChange"
:disabled="disabled"
:min="props.min"
:max="props.max"
/>
</div>
<div class="input" v-show="isInput">
<my-input
type="number"
v-model="value"
v-bind="$attrs"
@input="onInput"
@change="onChange"
:disabled="disabled"
:min="props.min"
:max="props.max"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, defineProps, defineEmits, watch } from 'vue'
import MyInput from './my-input.vue'
const props = defineProps({
modelValue: { type: Number },
disabled: {
type: Boolean,
default: false
},
min: {
type: Number,
default: 0
},
max: {
type: Number,
default: 100
},
tipFormatter: {
type: Function,
default: (v) => v
},
isInput: {
type: Boolean,
default: true
}
})
const emit = defineEmits(['update:modelValue', 'change', 'input'])
watch(
() => props.modelValue,
(v) => {
value.value = v
}
)
const value = ref(props.modelValue)
const onInput = () => {
if (props.disabled) return
const v = Number(value.value)
emit('update:modelValue', v)
emit('input', v)
}
const onChange = () => {
if (props.disabled) return
const v = Number(value.value)
emit('update:modelValue', v)
emit('change', v)
}
</script>
<style scoped lang="less">
.slider {
position: relative;
display: flex;
align-items: center;
--input-thumb-size: 8px;
--backcolor1: var(--slider-thumb-color1, #4285f4);
--backcolor2: var(--slider-thumb-color2, rgba(0, 0, 0, 0.1));
&:hover {
> .input-range > .tip {
display: block;
}
}
> .input-range {
position: relative;
flex: 2;
display: flex;
> input {
width: 100%;
-webkit-appearance: none;
appearance: none;
height: 3px;
border-radius: 3px;
outline: none;
background: linear-gradient(
to right,
var(--backcolor1) 0%,
var(--backcolor1) calc(var(--progress) * 100%),
var(--backcolor2) calc(var(--progress) * 100%),
var(--backcolor2) 100%
);
&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: var(--input-thumb-size);
height: var(--input-thumb-size);
border-radius: 50%;
background: var(--backcolor1); /* 蓝色滑块 */
cursor: pointer;
transition: all 0.2s;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
&::-webkit-slider-thumb:hover {
transform: scale(1.1);
}
}
> .tip {
position: absolute;
font-size: 10px;
pointer-events: none;
user-select: none;
color: #666;
top: calc(var(--input-thumb-size) / -2 - 3.5px);
left: calc(
(100% - var(--input-thumb-size)) * var(--progress) + var(--input-thumb-size) / 2
);
transform: translate(-50%, -100%);
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 3px 5px;
border-radius: 4px;
font-size: 10px;
white-space: nowrap;
pointer-events: none;
display: none;
&::after {
content: '';
position: absolute;
top: 97%;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 5px solid rgba(0, 0, 0, 0.8);
}
}
}
> .input {
flex: 1;
margin-left: 10px;
> input {
border-radius: 3px;
width: 100%;
}
}
}
</style>

View File

@@ -1,108 +0,0 @@
<template>
<div class="upload-file">
<div class="preview" v-if="url">
<img :src="url" @error="onChange(null)" />
<div class="close" @click="onChange(null)">
<svg-icon name="close-border" size="16" size-unit="px" />
</div>
</div>
<div class="control" v-else>
<div class="icon"><svg-icon name="upload" size="17" size-unit="px" /></div>
<div class="txt">{{ tip }}</div>
<div class="btn" @click="onSelectFile">Select File</div>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, computed } from 'vue'
const emit = defineEmits(['update:modelValue', 'change'])
const props = defineProps({
modelValue: { type: [File, Object, String, null] },
tip: { type: String, default: 'Upload your files' }
})
const data = reactive({
file: null
})
const url = computed(() => {
const type = props.modelValue?.constructor
var str = ''
if (type === File) {
str = URL.createObjectURL(props.modelValue as File)
} else if (type === String) {
str = props.modelValue as string
}
return str
})
const onChange = (v) => {
emit('update:modelValue', v)
emit('change', v)
}
const onSelectFile = () => {
const input = document.createElement('input')
input.type = 'file'
input.accept = 'image/png, image/jpeg, image/jpg'
input.addEventListener('change', (e) => {
const file = e.target.files[0]
if (file) onChange(file)
})
input.click()
}
defineExpose({ data })
</script>
<style lang="less" scoped>
.upload-file {
width: 100%;
height: 99px;
border-radius: 10px;
background-color: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
> .control {
text-align: center;
> .txt {
margin-top: 6px;
margin-bottom: 8px;
font-size: 8px;
color: #7c7c7c;
}
> .btn {
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0px 0.75px 0px 0px rgba(0, 0, 0, 0.02);
min-width: 39px;
height: 13px;
border-radius: 2.3px;
background-color: #fff;
font-size: 6px;
color: #000;
border: 1px solid #d9d9d9;
cursor: pointer;
&:active {
opacity: 0.6;
}
}
}
> .preview {
width: 80px;
height: 80px;
position: relative;
> img {
height: 100%;
width: 100%;
object-fit: contain;
}
> .close {
position: absolute;
top: 0.1px;
right: 0.1px;
border-radius: 50%;
background-color: #fff;
cursor: pointer;
}
}
}
</style>