fix:remove ElInput
This commit is contained in:
@@ -25,7 +25,7 @@
|
||||
--shadow: rgba(0, 0, 0, 0.08) 0px 4px 12px;
|
||||
--panel-margin-left: calc(50% + var(--toolbar-width) / 2 + 1rem);
|
||||
--article-panel-margin-left: calc(50% + var(--article-width) / 2 + 1rem);
|
||||
--anim-time: 0.3s;
|
||||
--anim-time: 0.5s;
|
||||
|
||||
--color-input-color: black;
|
||||
--color-input-bg: white;
|
||||
@@ -409,8 +409,8 @@ a {
|
||||
background: var(--color-second);
|
||||
}
|
||||
|
||||
.center {
|
||||
@apply flex justify-center items-center;
|
||||
.inline-center {
|
||||
@apply inline-flex justify-center items-center;
|
||||
}
|
||||
|
||||
.title {
|
||||
|
||||
@@ -14,13 +14,13 @@ import {saveAs} from "file-saver";
|
||||
import {GITHUB} from "@/config/ENV.ts";
|
||||
import dayjs from "dayjs";
|
||||
import BasePage from "@/pages/pc/components/BasePage.vue";
|
||||
import {ElInputNumber} from 'element-plus'
|
||||
import Toast from '@/pages/pc/components/base/toast/Toast.ts'
|
||||
import {Option, Select} from "@/pages/pc/components/base/select";
|
||||
import Switch from "@/pages/pc/components/base/Switch.vue";
|
||||
import Slider from "@/pages/pc/components/base/Slider.vue";
|
||||
import RadioGroup from "@/pages/pc/components/base/radio/RadioGroup.vue";
|
||||
import Radio from "@/pages/pc/components/base/radio/Radio.vue";
|
||||
import InputNumber from "@/pages/pc/components/base/InputNumber.vue";
|
||||
|
||||
const emit = defineEmits<{
|
||||
toggleDisabledDialogEscKey: [val: boolean]
|
||||
@@ -284,7 +284,7 @@ function importData(e) {
|
||||
</RadioGroup>
|
||||
<div class="mini-row" v-if="settingStore.repeatCount === 100">
|
||||
<label class="item-title">循环次数</label>
|
||||
<ElInputNumber v-model="settingStore.repeatCustomCount"
|
||||
<InputNumber v-model="settingStore.repeatCustomCount"
|
||||
:min="6"
|
||||
:max="15"
|
||||
type="number"
|
||||
@@ -365,10 +365,10 @@ function importData(e) {
|
||||
<div class="row">
|
||||
<label class="sub-title">切换下一个单词时间</label>
|
||||
<div class="wrapper">
|
||||
<ElInputNumber v-model="settingStore.waitTimeForChangeWord"
|
||||
:min="6"
|
||||
:max="100"
|
||||
type="number"
|
||||
<InputNumber v-model="settingStore.waitTimeForChangeWord"
|
||||
:min="10"
|
||||
:max="100"
|
||||
type="number"
|
||||
/>
|
||||
<span>毫秒</span>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,6 @@ import {genArticleSectionData, splitCNArticle2, splitEnArticle2, usePlaySentence
|
||||
import {_nextTick, _parseLRC, cloneDeep, last} from "@/utils";
|
||||
import {watch} from "vue";
|
||||
import Empty from "@/components/Empty.vue";
|
||||
import {ElInputNumber} from "element-plus";
|
||||
import Toast from '@/pages/pc/components/base/toast/Toast.ts'
|
||||
import * as Comparison from "string-comparison"
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
@@ -18,6 +17,7 @@ import {getDefaultArticle} from "@/types/func.ts";
|
||||
import copy from "copy-to-clipboard";
|
||||
import {Option, Select} from "@/pages/pc/components/base/select";
|
||||
import Tooltip from "@/pages/pc/components/base/Tooltip.vue";
|
||||
import InputNumber from "@/pages/pc/components/base/InputNumber.vue";
|
||||
|
||||
interface IProps {
|
||||
article?: Article,
|
||||
@@ -497,11 +497,7 @@ function setStartTime(val: Sentence, i: number, j: number) {
|
||||
<div>开始时间:</div>
|
||||
<div class="flex justify-between flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<ElInputNumber v-model="editSentence.audioPosition[0]" :precision="2" :step="0.1">
|
||||
<template #suffix>
|
||||
<span>s</span>
|
||||
</template>
|
||||
</ElInputNumber>
|
||||
<InputNumber v-model="editSentence.audioPosition[0]" :precision="2" :step="0.1"/>
|
||||
<BaseIcon
|
||||
@click="jumpAudio(editSentence.audioPosition[0])"
|
||||
title="跳转"
|
||||
@@ -520,11 +516,7 @@ function setStartTime(val: Sentence, i: number, j: number) {
|
||||
<div>结束时间:</div>
|
||||
<div class="flex justify-between flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<ElInputNumber v-model="editSentence.audioPosition[1]" :precision="2" :step="0.1">
|
||||
<template #suffix>
|
||||
<span>s</span>
|
||||
</template>
|
||||
</ElInputNumber>
|
||||
<InputNumber v-model="editSentence.audioPosition[1]" :precision="2" :step="0.1"/>
|
||||
<span>或</span>
|
||||
<BaseButton size="small" @click="editSentence.audioPosition[1] = -1">结束</BaseButton>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import {ElInput} from "element-plus";
|
||||
|
||||
import {watchEffect} from "vue";
|
||||
import BaseInput from "@/pages/pc/components/base/BaseInput.vue";
|
||||
import Textarea from "@/pages/pc/components/base/Textarea.vue";
|
||||
|
||||
interface IProps {
|
||||
value: string,
|
||||
@@ -32,6 +32,7 @@ function save() {
|
||||
|
||||
function toggle() {
|
||||
edit = !edit
|
||||
editVal = props.value
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -39,7 +40,7 @@ function toggle() {
|
||||
<div
|
||||
v-if="edit"
|
||||
class="edit-text">
|
||||
<BaseInput
|
||||
<Textarea
|
||||
v-model="editVal"
|
||||
ref="inputRef"
|
||||
textarea
|
||||
@@ -48,7 +49,7 @@ function toggle() {
|
||||
type="textarea"
|
||||
:input-style="`color: var(--color-font-1);font-size: 1rem;`"
|
||||
/>
|
||||
<div class="options">
|
||||
<div class="flex justify-end mt-2">
|
||||
<BaseButton @click="toggle">取消</BaseButton>
|
||||
<BaseButton @click="save">应用</BaseButton>
|
||||
</div>
|
||||
@@ -65,13 +66,6 @@ function toggle() {
|
||||
.edit-text {
|
||||
margin-top: .6rem;
|
||||
color: var(--color-font-1);
|
||||
|
||||
.options {
|
||||
margin-top: .6rem;
|
||||
gap: .6rem;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import {ref, computed, watch, defineProps, defineEmits, useAttrs, nextTick, PropType} from 'vue';
|
||||
|
||||
interface Autosize {
|
||||
minRows?: number;
|
||||
maxRows?: number;
|
||||
}
|
||||
import {ref, watch, defineProps, defineEmits, useAttrs} from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: [String, Number],
|
||||
@@ -18,28 +13,18 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
textarea: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
maxLength: Number,
|
||||
autosize: {
|
||||
type: [Boolean, Object] as PropType<boolean | Autosize>,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'input', 'change', 'focus', 'blur', 'validation']);
|
||||
|
||||
const attrs = useAttrs();
|
||||
|
||||
const inputValue = ref(props.modelValue);
|
||||
const errorMsg = ref('');
|
||||
const textareaRef = ref<HTMLTextAreaElement | null>(null);
|
||||
|
||||
watch(() => props.modelValue, (val) => {
|
||||
inputValue.value = val;
|
||||
@@ -60,17 +45,11 @@ const validate = (val: string | number | null | undefined) => {
|
||||
};
|
||||
|
||||
const onInput = (e: Event) => {
|
||||
const target = e.target as HTMLInputElement | HTMLTextAreaElement;
|
||||
const target = e.target as HTMLInputElement;
|
||||
inputValue.value = target.value;
|
||||
validate(target.value);
|
||||
emit('update:modelValue', target.value);
|
||||
emit('input', e);
|
||||
|
||||
if (props.textarea && props.autosize) {
|
||||
nextTick(() => {
|
||||
calcTextareaHeight();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onChange = (e: Event) => {
|
||||
@@ -90,105 +69,32 @@ const clearInput = () => {
|
||||
inputValue.value = '';
|
||||
validate('');
|
||||
emit('update:modelValue', '');
|
||||
if (props.textarea && props.autosize) {
|
||||
nextTick(() => {
|
||||
calcTextareaHeight();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 计算并设置 textarea 高度,支持 autosize 功能
|
||||
const calcTextareaHeight = () => {
|
||||
if (!textareaRef.value) return;
|
||||
const ta = textareaRef.value;
|
||||
|
||||
ta.style.height = 'auto'; // 先重置高度
|
||||
|
||||
const style = window.getComputedStyle(ta);
|
||||
const lineHeight = parseFloat(style.lineHeight);
|
||||
const paddingTop = parseFloat(style.paddingTop);
|
||||
const paddingBottom = parseFloat(style.paddingBottom);
|
||||
|
||||
let height = ta.scrollHeight;
|
||||
|
||||
let minRows = 1;
|
||||
let maxRows = Infinity;
|
||||
|
||||
if (typeof props.autosize === 'object') {
|
||||
if (props.autosize.minRows) minRows = props.autosize.minRows;
|
||||
if (props.autosize.maxRows) maxRows = props.autosize.maxRows;
|
||||
} else if (props.autosize === true) {
|
||||
minRows = 1;
|
||||
maxRows = Infinity;
|
||||
}
|
||||
|
||||
const minHeight = lineHeight * minRows + paddingTop + paddingBottom;
|
||||
const maxHeight = lineHeight * maxRows + paddingTop + paddingBottom;
|
||||
|
||||
height = Math.min(Math.max(height, minHeight), maxHeight);
|
||||
|
||||
ta.style.height = height + 'px';
|
||||
};
|
||||
|
||||
// 组件初始化时,调整高度(针对多行)
|
||||
if (props.textarea && props.autosize) {
|
||||
nextTick(() => {
|
||||
calcTextareaHeight();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="custom-input" :class="{ 'is-disabled': disabled, 'has-error': errorMsg }">
|
||||
<template v-if="textarea">
|
||||
<textarea
|
||||
v-bind="attrs"
|
||||
ref="textareaRef"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:value="inputValue"
|
||||
@input="onInput"
|
||||
@change="onChange"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
class="custom-input__textarea"
|
||||
:maxlength="maxLength"
|
||||
rows="1"
|
||||
:style="autosize ? {overflowY: 'hidden'} : {}"
|
||||
></textarea>
|
||||
<button
|
||||
v-if="clearable && inputValue && !disabled"
|
||||
type="button"
|
||||
class="custom-input__clear"
|
||||
@click="clearInput"
|
||||
aria-label="Clear input"
|
||||
>×
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<input
|
||||
v-bind="attrs"
|
||||
:type="type"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:value="inputValue"
|
||||
@input="onInput"
|
||||
@change="onChange"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
class="custom-input__inner"
|
||||
:maxlength="maxLength"
|
||||
/>
|
||||
<button
|
||||
v-if="clearable && inputValue && !disabled"
|
||||
type="button"
|
||||
class="custom-input__clear"
|
||||
@click="clearInput"
|
||||
aria-label="Clear input"
|
||||
>×
|
||||
</button>
|
||||
</template>
|
||||
<input
|
||||
v-bind="attrs"
|
||||
:type="type"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:value="inputValue"
|
||||
@input="onInput"
|
||||
@change="onChange"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
class="custom-input__inner"
|
||||
:maxlength="maxLength"
|
||||
/>
|
||||
<button
|
||||
v-if="clearable && inputValue && !disabled"
|
||||
type="button"
|
||||
class="custom-input__clear"
|
||||
@click="clearInput"
|
||||
aria-label="Clear input"
|
||||
>×
|
||||
</button>
|
||||
|
||||
<div v-if="errorMsg" class="custom-input__error">{{ errorMsg }}</div>
|
||||
</div>
|
||||
@@ -205,8 +111,7 @@ if (props.textarea && props.autosize) {
|
||||
}
|
||||
|
||||
&.has-error {
|
||||
.custom-input__inner,
|
||||
.custom-input__textarea {
|
||||
.custom-input__inner {
|
||||
border-color: #f56c6c;
|
||||
}
|
||||
|
||||
@@ -217,15 +122,13 @@ if (props.textarea && props.autosize) {
|
||||
}
|
||||
}
|
||||
|
||||
&__inner,
|
||||
&__textarea {
|
||||
&__inner {
|
||||
width: 100%;
|
||||
padding: 0.4rem 1.5rem 0.4rem 0.5rem;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
box-sizing: border-box;
|
||||
resize: vertical;
|
||||
transition: all .3s;
|
||||
color: var(--color-input-color);
|
||||
background: var(--color-input-bg);
|
||||
@@ -242,11 +145,6 @@ if (props.textarea && props.autosize) {
|
||||
}
|
||||
}
|
||||
|
||||
&__textarea {
|
||||
min-height: 5rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
&__clear {
|
||||
position: absolute;
|
||||
right: 0.4rem;
|
||||
@@ -270,9 +168,4 @@ if (props.textarea && props.autosize) {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-input__textarea {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
195
src/pages/pc/components/base/InputNumber.vue
Normal file
195
src/pages/pc/components/base/InputNumber.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<div class="input-number inline-center select-none anim" :class="{ 'is-disabled': disabled }">
|
||||
<!-- 减号 -->
|
||||
<button
|
||||
class="btn minus-btn inline-center cursor-pointer anim border-none outline-none w-8 h-8"
|
||||
type="button"
|
||||
:disabled="disabled || isMin"
|
||||
@mousedown.prevent="onHold(-1)"
|
||||
@mouseup="onRelease"
|
||||
@mouseleave="onRelease"
|
||||
aria-label="decrease"
|
||||
>-
|
||||
</button>
|
||||
|
||||
<!-- 输入框 -->
|
||||
<input
|
||||
ref="inputRef"
|
||||
class="flex-1 h-8 px-2 text-center border-none outline-none bg-transparent input-inner w-14"
|
||||
:value="displayValue"
|
||||
:disabled="disabled"
|
||||
inputmode="decimal"
|
||||
@input="e => displayValue = e.target.value"
|
||||
@keydown.up.prevent="change(1)"
|
||||
@keydown.down.prevent="change(-1)"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
|
||||
<!-- 加号 -->
|
||||
<button
|
||||
class="btn plus-btn inline-center cursor-pointer anim border-none outline-none w-8 h-8"
|
||||
type="button"
|
||||
:disabled="disabled || isMax"
|
||||
@mousedown.prevent="onHold(1)"
|
||||
@mouseup="onRelease"
|
||||
@mouseleave="onRelease"
|
||||
aria-label="increase"
|
||||
>+
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, computed, onBeforeUnmount} from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {type: [Number, String], default: null},
|
||||
min: {type: Number, default: -Infinity},
|
||||
max: {type: Number, default: Infinity},
|
||||
step: {type: Number, default: 1},
|
||||
precision: {type: Number},
|
||||
disabled: {type: Boolean, default: false},
|
||||
stepStrictly: {type: Boolean, default: false},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'input', 'change'])
|
||||
|
||||
const inputRef = ref<HTMLInputElement | null>(null)
|
||||
const inner = ref<number | null>(normalizeToNumber(props.modelValue))
|
||||
let holdTimer: number | null = null
|
||||
let holdInterval: number | null = null
|
||||
|
||||
const displayValue = computed({
|
||||
get: () => inner.value === null ? '' : format(inner.value),
|
||||
set: v => {
|
||||
const n = parseInput(v)
|
||||
if (n === 'editing') return
|
||||
setValue(n)
|
||||
}
|
||||
})
|
||||
|
||||
const isMin = computed(() => inner.value !== null && inner.value <= props.min)
|
||||
const isMax = computed(() => inner.value !== null && inner.value >= props.max)
|
||||
|
||||
function normalizeToNumber(v: any): number | null {
|
||||
const n = Number(v)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
|
||||
function clamp(n: number | null) {
|
||||
if (n === null) return null
|
||||
if (n < props.min) return props.min
|
||||
if (n > props.max) return props.max
|
||||
return n
|
||||
}
|
||||
|
||||
function format(n: number) {
|
||||
return props.precision != null ? n.toFixed(props.precision) : String(n)
|
||||
}
|
||||
|
||||
function parseInput(s: string): number | 'editing' | null {
|
||||
const trimmed = s.trim()
|
||||
if (['', '-', '+', '.', '-.', '+.'].includes(trimmed)) return 'editing'
|
||||
const n = Number(trimmed)
|
||||
return Number.isFinite(n) ? n : 'editing'
|
||||
}
|
||||
|
||||
function applyStepStrict(n: number | null) {
|
||||
if (n === null) return null
|
||||
if (!props.stepStrictly) return n
|
||||
const base = Number.isFinite(props.min) ? props.min : 0
|
||||
const k = Math.round((n - base) / props.step)
|
||||
return base + k * props.step
|
||||
}
|
||||
|
||||
function toPrecision(n: number) {
|
||||
return props.precision != null ? Number(n.toFixed(props.precision)) : n
|
||||
}
|
||||
|
||||
function setValue(n: number | null) {
|
||||
const v = clamp(toPrecision(applyStepStrict(n)))
|
||||
inner.value = v
|
||||
emit('update:modelValue', v)
|
||||
emit('input', v)
|
||||
emit('change', v)
|
||||
}
|
||||
|
||||
function change(dir: 1 | -1) {
|
||||
if (props.disabled) return
|
||||
const base = inner.value ?? (Number.isFinite(props.min) ? props.min : 0)
|
||||
setValue(base + dir * props.step)
|
||||
}
|
||||
|
||||
function onHold(dir: 1 | -1) {
|
||||
change(dir)
|
||||
holdTimer = window.setTimeout(() => {
|
||||
holdInterval = window.setInterval(() => change(dir), 100)
|
||||
}, 400)
|
||||
}
|
||||
|
||||
function onRelease() {
|
||||
if (holdTimer) {
|
||||
clearTimeout(holdTimer);
|
||||
holdTimer = null
|
||||
}
|
||||
if (holdInterval) {
|
||||
clearInterval(holdInterval);
|
||||
holdInterval = null
|
||||
}
|
||||
}
|
||||
|
||||
function onBlur() {
|
||||
const n = parseInput(displayValue.value)
|
||||
setValue(n === 'editing' ? inner.value : n)
|
||||
}
|
||||
|
||||
onBeforeUnmount(onRelease)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.input-number {
|
||||
border: 1px solid var(--color-input-border);
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
background: var(--color-input-bg);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-select-bg);
|
||||
}
|
||||
|
||||
&.is-disabled {
|
||||
opacity: .7;
|
||||
|
||||
.btn, .input-inner {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.input-inner {
|
||||
color: var(--color-input-color);
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: var(--color-second);
|
||||
color: var(--color-input-color);
|
||||
|
||||
&.minus-btn {
|
||||
border-right: 1px solid var(--color-input-border);
|
||||
}
|
||||
|
||||
&.plus-btn {
|
||||
border-left: 1px solid var(--color-input-border);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--color-third);
|
||||
color: var(--color-select-bg);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: .5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
100
src/pages/pc/components/base/Textarea.vue
Normal file
100
src/pages/pc/components/base/Textarea.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div class="inline-flex w-full relative">
|
||||
<textarea
|
||||
ref="textareaRef"
|
||||
v-model="innerValue"
|
||||
:placeholder="placeholder"
|
||||
:maxlength="maxlength"
|
||||
:rows="rows"
|
||||
:style="textareaStyle"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md outline-none resize-none transition-colors duration-200 box-border"
|
||||
@input="handleInput"
|
||||
/>
|
||||
<!-- 字数统计 -->
|
||||
<span
|
||||
v-if="showWordLimit && maxlength"
|
||||
class="absolute bottom-1 right-2 text-xs text-gray-400 select-none"
|
||||
>
|
||||
{{ innerValue.length }} / {{ maxlength }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, watch, computed, nextTick} from "vue"
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: String,
|
||||
placeholder: String,
|
||||
maxlength: Number,
|
||||
rows: {type: Number, default: 1},
|
||||
autosize: {
|
||||
type: [Boolean, Object] as () => boolean | { minRows?: number; maxRows?: number },
|
||||
default: false
|
||||
},
|
||||
showWordLimit: Boolean
|
||||
})
|
||||
|
||||
const emit = defineEmits(["update:modelValue"])
|
||||
|
||||
const innerValue = ref(props.modelValue ?? "")
|
||||
watch(() => props.modelValue, v => (innerValue.value = v ?? ""))
|
||||
|
||||
const textareaRef = ref<HTMLTextAreaElement>()
|
||||
|
||||
// 样式(用于控制高度)
|
||||
const textareaStyle = computed(() => {
|
||||
return props.autosize ? { height: "auto" } : {}
|
||||
})
|
||||
|
||||
// 输入处理
|
||||
const handleInput = (e: Event) => {
|
||||
const val = (e.target as HTMLTextAreaElement).value
|
||||
innerValue.value = val
|
||||
emit("update:modelValue", val)
|
||||
if (props.autosize) nextTick(resizeTextarea)
|
||||
}
|
||||
|
||||
// 自动调整高度
|
||||
const resizeTextarea = () => {
|
||||
if (!textareaRef.value) return
|
||||
const el = textareaRef.value
|
||||
el.style.height = "auto"
|
||||
let height = el.scrollHeight
|
||||
let overflow = "hidden"
|
||||
|
||||
if (typeof props.autosize === "object") {
|
||||
const { minRows, maxRows } = props.autosize
|
||||
const lineHeight = 24 // 行高约等于 24px
|
||||
if (minRows) height = Math.max(height, minRows * lineHeight)
|
||||
if (maxRows) {
|
||||
const maxHeight = maxRows * lineHeight
|
||||
if (height > maxHeight) {
|
||||
height = maxHeight
|
||||
overflow = "auto" // 超出时允许滚动
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
el.style.height = height + "px"
|
||||
el.style.overflowY = overflow
|
||||
}
|
||||
|
||||
watch(innerValue, () => {
|
||||
if (props.autosize) nextTick(resizeTextarea)
|
||||
}, {immediate: true})
|
||||
|
||||
</script>
|
||||
<style>
|
||||
textarea {
|
||||
font-family: var(--font-family);
|
||||
color: var(--color-input-color);
|
||||
background: var(--color-input-bg);
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: #409eff;
|
||||
box-shadow: 0 0 3px #409eff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -10,7 +10,7 @@ import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import BaseTable from "@/pages/pc/components/BaseTable.vue";
|
||||
import WordItem from "@/pages/pc/components/WordItem.vue";
|
||||
import type {FormInstance, FormRules} from "element-plus";
|
||||
import {ElForm, ElFormItem, ElInput} from "element-plus";
|
||||
import {ElForm, ElFormItem} from "element-plus";
|
||||
import Toast from '@/pages/pc/components/base/toast/Toast.ts'
|
||||
import PopConfirm from "@/pages/pc/components/PopConfirm.vue";
|
||||
import BackIcon from "@/pages/pc/components/BackIcon.vue";
|
||||
@@ -20,6 +20,7 @@ import {useBaseStore} from "@/stores/base.ts";
|
||||
import EditBook from "@/pages/pc/article/components/EditBook.vue";
|
||||
import {getDefaultDict} from "@/types/func.ts";
|
||||
import BaseInput from "@/pages/pc/components/base/BaseInput.vue";
|
||||
import Textarea from "@/pages/pc/components/base/Textarea.vue";
|
||||
|
||||
const runtimeStore = useRuntimeStore()
|
||||
const base = useBaseStore()
|
||||
@@ -208,7 +209,8 @@ defineRender(() => {
|
||||
<BackIcon class="z-2" onClick={() => router.back()}/>
|
||||
<div class="absolute page-title text-align-center w-full">{runtimeStore.editDict.name}</div>
|
||||
<div class="flex">
|
||||
<BaseButton loading={studyLoading || loading} type="info" onClick={() => isEdit = true}>编辑</BaseButton>
|
||||
<BaseButton loading={studyLoading || loading} type="info"
|
||||
onClick={() => isEdit = true}>编辑</BaseButton>
|
||||
<BaseButton loading={studyLoading || loading} onClick={addMyStudyList}>学习</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
@@ -286,52 +288,46 @@ defineRender(() => {
|
||||
onUpdate:modelValue={e => wordForm.phonetic1 = e}/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="翻译">
|
||||
<ElInput
|
||||
<Textarea
|
||||
modelValue={wordForm.trans}
|
||||
onUpdate:modelValue={e => wordForm.trans = e}
|
||||
placeholder="一行一个翻译,前面词性,后面内容(如n.取消);多个翻译请换行"
|
||||
autosize={{minRows: 6, maxRows: 10}}
|
||||
type="textarea"/>
|
||||
autosize={{minRows: 6, maxRows: 10}}/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="例句">
|
||||
<ElInput
|
||||
<Textarea
|
||||
modelValue={wordForm.sentences}
|
||||
onUpdate:modelValue={e => wordForm.sentences = e}
|
||||
placeholder="一行原文,一行译文;多个请换两行"
|
||||
autosize={{minRows: 6, maxRows: 10}}
|
||||
type="textarea"/>
|
||||
autosize={{minRows: 6, maxRows: 10}}/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="短语">
|
||||
<ElInput
|
||||
<Textarea
|
||||
modelValue={wordForm.phrases}
|
||||
onUpdate:modelValue={e => wordForm.phrases = e}
|
||||
placeholder="一行原文,一行译文;多个请换两行"
|
||||
autosize={{minRows: 6, maxRows: 10}}
|
||||
type="textarea"/>
|
||||
autosize={{minRows: 6, maxRows: 10}}/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="同义词">
|
||||
<ElInput
|
||||
<Textarea
|
||||
modelValue={wordForm.synos}
|
||||
onUpdate:modelValue={e => wordForm.synos = e}
|
||||
placeholder="请参考已有单词格式"
|
||||
autosize={{minRows: 6, maxRows: 20}}
|
||||
type="textarea"/>
|
||||
autosize={{minRows: 6, maxRows: 20}}/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="同根词">
|
||||
<ElInput
|
||||
<Textarea
|
||||
modelValue={wordForm.relWords}
|
||||
onUpdate:modelValue={e => wordForm.relWords = e}
|
||||
placeholder="请参考已有单词格式"
|
||||
autosize={{minRows: 6, maxRows: 20}}
|
||||
type="textarea"/>
|
||||
autosize={{minRows: 6, maxRows: 20}}/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="词源">
|
||||
<ElInput
|
||||
<Textarea
|
||||
modelValue={wordForm.etymology}
|
||||
onUpdate:modelValue={e => wordForm.etymology = e}
|
||||
placeholder="请参考已有单词格式"
|
||||
autosize={{minRows: 6, maxRows: 10}}
|
||||
type="textarea"/>
|
||||
autosize={{minRows: 6, maxRows: 10}}/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<div class="center">
|
||||
|
||||
Reference in New Issue
Block a user