From 47890266bffcbbf076a10c668389219706cc79a7 Mon Sep 17 00:00:00 2001 From: zyronon Date: Wed, 13 Aug 2025 02:44:53 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=B7=BB=E5=8A=A0input=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/css/style.scss | 2 + src/pages/pc/Setting.vue | 15 +- src/pages/pc/article/BookList.vue | 2 +- src/pages/pc/article/components/EditBook.vue | 6 +- src/pages/pc/components/BaseTable.vue | 1 + src/pages/pc/components/EditAbleText.vue | 4 +- src/pages/pc/components/Input.vue | 3 +- src/pages/pc/components/base/BaseInput.vue | 278 +++++++++++++++++++ src/pages/pc/components/base/Slider.vue | 236 ++++++++++++++++ src/pages/pc/components/list/ArticleList.vue | 2 +- src/pages/pc/components/list/List.vue | 2 +- src/pages/pc/word/DictDetail.vue | 7 +- src/pages/pc/word/DictList.vue | 2 +- src/pages/pc/word/WordHomePage.vue | 12 +- 14 files changed, 550 insertions(+), 22 deletions(-) create mode 100644 src/pages/pc/components/base/BaseInput.vue create mode 100644 src/pages/pc/components/base/Slider.vue diff --git a/src/assets/css/style.scss b/src/assets/css/style.scss index 28e99401..9b49c4be 100644 --- a/src/assets/css/style.scss +++ b/src/assets/css/style.scss @@ -27,6 +27,7 @@ --article-panel-margin-left: calc(50% + var(--article-width) / 2 + 1rem); --anim-time: 0.3s; + --color-input-color: black; --color-input-bg: white; --color-input-border: #bfbfbf; --color-input-icon: #d3d4d7; @@ -98,6 +99,7 @@ html.dark { --btn-info: transparent; + --color-input-color:white; --color-input-bg: rgba(14, 18, 23, 1); --color-input-icon: #383737; diff --git a/src/pages/pc/Setting.vue b/src/pages/pc/Setting.vue index 6a80c407..79018d0e 100644 --- a/src/pages/pc/Setting.vue +++ b/src/pages/pc/Setting.vue @@ -14,10 +14,11 @@ 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, ElRadio, ElRadioGroup, ElSlider} from 'element-plus' +import {ElInputNumber, ElRadio, ElRadioGroup} 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"; const emit = defineEmits<{ toggleDisabledDialogEscKey: [val: boolean] @@ -197,14 +198,14 @@ function importData(e) {
- + {{ settingStore.wordSoundVolume }}%
- + {{ settingStore.wordSoundSpeed }}
@@ -245,7 +246,7 @@ function importData(e) {
- + {{ settingStore.keyboardSoundVolume }}%
@@ -263,7 +264,7 @@ function importData(e) {
- + {{ settingStore.effectSoundVolume }}%
@@ -337,7 +338,7 @@ function importData(e) {
- @@ -347,7 +348,7 @@ function importData(e) {
- diff --git a/src/pages/pc/article/BookList.vue b/src/pages/pc/article/BookList.vue index 3eff2331..87834b58 100644 --- a/src/pages/pc/article/BookList.vue +++ b/src/pages/pc/article/BookList.vue @@ -53,7 +53,7 @@ const searchList = computed(() => {
- + 取消
diff --git a/src/pages/pc/article/components/EditBook.vue b/src/pages/pc/article/components/EditBook.vue index 1db4b65d..e2ad85b2 100644 --- a/src/pages/pc/article/components/EditBook.vue +++ b/src/pages/pc/article/components/EditBook.vue @@ -11,6 +11,8 @@ import {useBaseStore} from "@/stores/base.ts"; import BaseButton from "@/components/BaseButton.vue"; import {getDefaultDict} from "@/types/func.ts"; import {Option, Select} from "@/pages/pc/components/base/select"; +import Input from "@/pages/pc/components/Input.vue"; +import BaseInput from "@/pages/pc/components/base/BaseInput.vue"; const props = defineProps<{ isAdd: boolean, @@ -99,10 +101,10 @@ onMounted(() => { :model="dictForm" label-width="8rem"> - + - + searchKey = e)} diff --git a/src/pages/pc/components/EditAbleText.vue b/src/pages/pc/components/EditAbleText.vue index a1219260..0a25d26b 100644 --- a/src/pages/pc/components/EditAbleText.vue +++ b/src/pages/pc/components/EditAbleText.vue @@ -4,6 +4,7 @@ import BaseButton from "@/components/BaseButton.vue"; import {ElInput} from "element-plus"; import {watchEffect} from "vue"; +import BaseInput from "@/pages/pc/components/base/BaseInput.vue"; interface IProps { value: string, @@ -38,9 +39,10 @@ function toggle() {
- () defineEmits(['update:modelValue']) @@ -37,6 +38,7 @@ const vFocus = { ref="inputEl" > +import {ref, computed, watch, defineProps, defineEmits, useAttrs, nextTick, PropType} from 'vue'; + +interface Autosize { + minRows?: number; + maxRows?: number; +} + +const props = defineProps({ + modelValue: [String, Number], + placeholder: String, + disabled: Boolean, + type: { + type: String, + default: 'text', + }, + clearable: { + type: Boolean, + default: false, + }, + textarea: { + type: Boolean, + default: false, + }, + required: { + type: Boolean, + default: false, + }, + maxLength: Number, + autosize: { + type: [Boolean, Object] as PropType, + 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(null); + +watch(() => props.modelValue, (val) => { + inputValue.value = val; + validate(val); +}); + +const validate = (val: string | number | null | undefined) => { + let err = ''; + const strVal = val == null ? '' : String(val); + if (props.required && !strVal.trim()) { + err = '不能为空'; + } else if (props.maxLength && strVal.length > props.maxLength) { + err = `长度不能超过 ${props.maxLength} 个字符`; + } + errorMsg.value = err; + emit('validation', err === '', err); + return err === ''; +}; + +const onInput = (e: Event) => { + const target = e.target as HTMLInputElement | HTMLTextAreaElement; + 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) => { + emit('change', e); +}; + +const onFocus = (e: FocusEvent) => { + emit('focus', e); +}; + +const onBlur = (e: FocusEvent) => { + validate(inputValue.value); + emit('blur', e); +}; + +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(); + }); +} + + + + + diff --git a/src/pages/pc/components/base/Slider.vue b/src/pages/pc/components/base/Slider.vue new file mode 100644 index 00000000..ba57ddde --- /dev/null +++ b/src/pages/pc/components/base/Slider.vue @@ -0,0 +1,236 @@ + + + + + diff --git a/src/pages/pc/components/list/ArticleList.vue b/src/pages/pc/components/list/ArticleList.vue index f2b204d9..4ea0604c 100644 --- a/src/pages/pc/components/list/ArticleList.vue +++ b/src/pages/pc/components/list/ArticleList.vue @@ -48,7 +48,7 @@ defineExpose({scrollToBottom, scrollToItem})