fix:color

This commit is contained in:
Zyronon
2025-12-28 03:41:20 +08:00
parent e09db8c22a
commit 0237d2e127
18 changed files with 807 additions and 918 deletions

View File

@@ -33,14 +33,9 @@ provide('tabIndex', computed(() => tabIndex))
<style scoped lang="scss">
.panel {
border-radius: .5rem;
width: var(--panel-width);
background: var(--color-second);
height: 100%;
display: flex;
flex-direction: column;
border: 1px solid var(--color-item-border);
box-shadow: var(--shadow);
@apply shadow-lg flex flex-col h-full rounded-xl;
}
// 移动端适配

View File

@@ -1,22 +1,20 @@
<template>
<div class="inline-flex w-full relative"
:class="[disabled && 'disabled']"
>
<div class="inline-flex w-full relative" :class="[disabled && 'disabled']">
<textarea
ref="textareaRef"
v-model="innerValue"
:placeholder="placeholder"
:maxlength="maxlength"
:rows="rows"
:disabled="disabled"
: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"
ref="textareaRef"
v-model="innerValue"
:placeholder="placeholder"
:maxlength="maxlength"
:rows="rows"
:disabled="disabled"
: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"
v-if="showWordLimit && maxlength"
class="absolute bottom-1 right-2 text-xs text-gray-400 select-none"
>
{{ innerValue.length }} / {{ maxlength }}
</span>
@@ -24,36 +22,38 @@
</template>
<script setup lang="ts">
import {ref, watch, computed, nextTick} from "vue"
import { ref, watch, computed, nextTick } from 'vue'
const props = defineProps<{
modelValue: string,
placeholder?: string,
maxlength?: number,
rows?: number,
modelValue: string
placeholder?: string
maxlength?: number
rows?: number
autosize: boolean | { minRows?: number; maxRows?: number }
showWordLimit?: boolean
disabled?: boolean
}>()
const emit = defineEmits(["update:modelValue"])
const emit = defineEmits(['update:modelValue'])
const innerValue = ref(props.modelValue ?? "")
watch(() => props.modelValue, v => (innerValue.value = v ?? ""))
const innerValue = ref(props.modelValue ?? '')
watch(
() => props.modelValue,
v => (innerValue.value = v ?? '')
)
const textareaRef = ref<HTMLTextAreaElement>()
// 样式(用于控制高度)
const textareaStyle = computed(() => {
return props.autosize ? {height: "auto"} : {}
return props.autosize ? { height: 'auto' } : {}
})
// 输入处理
const handleInput = (e: Event) => {
const val = (e.target as HTMLTextAreaElement).value
innerValue.value = val
emit("update:modelValue", val)
emit('update:modelValue', val)
if (props.autosize) nextTick(resizeTextarea)
}
@@ -61,36 +61,38 @@ const handleInput = (e: Event) => {
const resizeTextarea = () => {
if (!textareaRef.value) return
const el = textareaRef.value
el.style.height = "auto"
el.style.height = 'auto'
let height = el.scrollHeight
let overflow = "hidden"
let overflow = 'hidden'
if (typeof props.autosize === "object") {
const {minRows, maxRows} = props.autosize
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" // 超出时允许滚动
overflow = 'auto' // 超出时允许滚动
}
}
}
el.style.height = height + "px"
el.style.height = height + 'px'
el.style.overflowY = overflow
}
watch(innerValue, () => {
if (props.autosize) nextTick(resizeTextarea)
}, {immediate: true})
watch(
innerValue,
() => {
if (props.autosize) nextTick(resizeTextarea)
},
{ immediate: true }
)
</script>
<style scoped lang="scss">
.disabled {
opacity: 0.5;
textarea {
cursor: not-allowed !important;
}

View File

@@ -13,7 +13,7 @@ const selectHandler = inject('selectHandler', null);
// 计算当前选项是否被选中
const isSelected = computed(() => {
return selectValue === props.value;
return selectValue.value === props.value;
});
// 点击选项时调用ElSelect提供的方法
@@ -45,20 +45,16 @@ watch(() => props.value, () => {}, { immediate: true });
<style scoped lang="scss">
.option {
display: flex;
align-items: center;
padding: 0.2rem 1rem;
cursor: pointer;
transition: background-color 0.3s;
@apply flex items-center px-2 py-1 cursor-pointer transition-all duration-300;
&:hover {
background-color: var(--color-third);
background-color: var(--color-fourth);
}
&.is-selected {
color: var(--color-select-bg);
font-weight: bold;
background-color: var(--color-third);
background-color: var(--color-fifth);
}
&.is-disabled {

View File

@@ -1,197 +1,185 @@
<script setup lang="ts">
import {computed, nextTick, onBeforeUnmount, onMounted, provide, ref, useAttrs, useSlots, VNode, watch} from 'vue';
import {useWindowClick} from "@/hooks/event.ts";
import {
computed,
nextTick,
onBeforeUnmount,
onMounted,
provide,
ref,
useAttrs,
useSlots,
VNode,
watch,
} from 'vue'
import { useWindowClick } from '@/hooks/event.ts'
interface Option {
label: string;
value: any;
disabled?: boolean;
label: string
value: any
disabled?: boolean
}
const props = defineProps<{
modelValue: any;
placeholder?: string;
disabled?: boolean;
options?: Option[];
}>();
modelValue: any
placeholder?: string
disabled?: boolean
options?: Option[]
}>()
const emit = defineEmits(['update:modelValue']);
const attrs = useAttrs();
const emit = defineEmits(['update:modelValue'])
const attrs = useAttrs()
const isOpen = ref(false);
const isReverse = ref(false);
const dropdownStyle = ref({}); // Teleport 用的样式
const selectedOption = ref<Option | null>(null);
const selectRef = ref<HTMLDivElement | null>(null);
const dropdownRef = ref<HTMLDivElement | null>(null);
const slots = useSlots();
const isOpen = ref(false)
const isReverse = ref(false)
const dropdownStyle = ref({}) // Teleport 用的样式
const selectedOption = ref<Option | null>(null)
const selectRef = ref<HTMLDivElement | null>(null)
const dropdownRef = ref<HTMLDivElement | null>(null)
const slots = useSlots()
const displayValue = computed(() => {
return selectedOption.value
? selectedOption.value.label
: props.placeholder || '请选择';
});
return selectedOption.value ? selectedOption.value.label : props.placeholder || '请选择'
})
const updateDropdownPosition = async () => {
if (!selectRef.value || !dropdownRef.value) return;
if (!selectRef.value || !dropdownRef.value) return
// 等待 DOM 完全渲染(尤其是下拉框高度)
await nextTick();
await new Promise(requestAnimationFrame);
await nextTick()
await new Promise(requestAnimationFrame)
const rect = selectRef.value.getBoundingClientRect();
const dropdownHeight = dropdownRef.value.offsetHeight;
const spaceBelow = window.innerHeight - rect.bottom;
const spaceAbove = rect.top;
const rect = selectRef.value.getBoundingClientRect()
const dropdownHeight = dropdownRef.value.offsetHeight
const spaceBelow = window.innerHeight - rect.bottom
const spaceAbove = rect.top
isReverse.value = spaceBelow < dropdownHeight && spaceAbove > spaceBelow;
isReverse.value = spaceBelow < dropdownHeight && spaceAbove > spaceBelow
dropdownStyle.value = {
position: 'fixed',
left: rect.left + 'px',
width: rect.width + 'px',
top: !isReverse.value
? rect.bottom + 5 + 'px'
: 'auto',
bottom: isReverse.value
? window.innerHeight - rect.top + 5 + 'px'
: 'auto',
zIndex: 9999
};
};
top: !isReverse.value ? rect.bottom + 5 + 'px' : 'auto',
bottom: isReverse.value ? window.innerHeight - rect.top + 5 + 'px' : 'auto',
zIndex: 9999,
}
}
const toggleDropdown = async () => {
if (props.disabled) return;
if (props.disabled) return
isOpen.value = !isOpen.value;
isOpen.value = !isOpen.value
if (isOpen.value) {
await nextTick();
await new Promise(requestAnimationFrame);
await updateDropdownPosition();
await nextTick()
await new Promise(requestAnimationFrame)
await updateDropdownPosition()
}
};
}
const selectOption = (value: any, label: string) => {
selectedOption.value = {value, label};
emit('update:modelValue', value);
isOpen.value = false;
};
selectedOption.value = { value, label }
emit('update:modelValue', value)
isOpen.value = false
}
let selectValue = $ref(props.modelValue);
let selectValue = ref(props.modelValue)
provide('selectValue', selectValue);
provide('selectHandler', selectOption);
provide('selectValue', selectValue)
provide('selectHandler', selectOption)
useWindowClick((e: PointerEvent) => {
if (!e) return;
if (!e) return
if (
selectRef.value &&
!selectRef.value.contains(e.target as Node) &&
dropdownRef.value &&
!dropdownRef.value.contains(e.target as Node)
selectRef.value &&
!selectRef.value.contains(e.target as Node) &&
dropdownRef.value &&
!dropdownRef.value.contains(e.target as Node)
) {
isOpen.value = false;
isOpen.value = false
}
});
})
watch(() => props.modelValue, (newValue) => {
selectValue = newValue;
if (slots.default) {
let slot = slots.default();
let list = [];
if (slot.length === 1) {
list = Array.from(slot[0].children as Array<VNode>);
} else {
list = slot;
watch(
() => props.modelValue,
newValue => {
selectValue.value = newValue
if (slots.default) {
let slot = slots.default()
let list = []
if (slot.length === 1) {
list = Array.from(slot[0].children as Array<VNode>)
} else {
list = slot
}
const option = list.find(opt => opt.props.value === newValue)
if (option) {
selectedOption.value = option.props
}
return
}
const option = list.find(opt => opt.props.value === newValue);
if (option) {
selectedOption.value = option.props;
if (props.options) {
const option = props.options.find(opt => opt.value === newValue)
if (option) {
selectedOption.value = option
}
}
return;
}
if (props.options) {
const option = props.options.find(opt => opt.value === newValue);
if (option) {
selectedOption.value = option;
}
}
}, {immediate: true});
},
{ immediate: true }
)
watch(() => props.options, (newOptions) => {
if (newOptions && props.modelValue) {
const option = newOptions.find(opt => opt.value === props.modelValue);
if (option) {
selectedOption.value = option;
watch(
() => props.options,
newOptions => {
if (newOptions && props.modelValue) {
const option = newOptions.find(opt => opt.value === props.modelValue)
if (option) {
selectedOption.value = option
}
}
}
}, {immediate: true});
const handleOptionClick = (option: Option) => {
if (option.disabled) return;
selectOption(option.value, option.label);
};
},
{ immediate: true }
)
const onScrollOrResize = () => {
if (isOpen.value) updateDropdownPosition();
};
if (isOpen.value) updateDropdownPosition()
}
onMounted(() => {
window.addEventListener('scroll', onScrollOrResize, true);
window.addEventListener('resize', onScrollOrResize);
});
window.addEventListener('scroll', onScrollOrResize, true)
window.addEventListener('resize', onScrollOrResize)
})
onBeforeUnmount(() => {
window.removeEventListener('scroll', onScrollOrResize, true);
window.removeEventListener('resize', onScrollOrResize);
});
window.removeEventListener('scroll', onScrollOrResize, true)
window.removeEventListener('resize', onScrollOrResize)
})
</script>
<template>
<div
class="select"
v-bind="attrs"
:class="{ 'is-disabled': disabled, 'is-active': isOpen, 'is-reverse': isReverse }"
ref="selectRef"
>
<div class="select__wrapper" @click="toggleDropdown">
<div class="select" ref="selectRef">
<div
class="select__wrapper"
:class="{ disabled: disabled, active: isOpen }"
@click="toggleDropdown"
>
<div class="select__label" :class="{ 'is-placeholder': !selectedOption }">
{{ displayValue }}
</div>
<div class="select__suffix">
<IconFluentChevronLeft20Filled
class="arrow"
:class="{ 'is-reverse': isOpen }"
width="16"
/>
</div>
<IconFluentChevronLeft20Filled
class="select__arrow"
:class="{ 'is-reverse': isOpen }"
width="16"
/>
</div>
<teleport to="body">
<transition :name="isReverse ? 'zoom-in-bottom' : 'zoom-in-top'" :key="isReverse ? 'bottom' : 'top'">
<div
class="select__dropdown"
v-if="isOpen"
ref="dropdownRef"
:style="dropdownStyle"
>
<ul class="select__options">
<li
v-if="options"
v-for="(option, index) in options"
:key="index"
class="select__option"
:class="{
'is-selected': option.value === modelValue,
'is-disabled': option.disabled
}"
@click="handleOptionClick(option)"
>
{{ option.label }}
</li>
<slot v-else></slot>
</ul>
<transition
:name="isReverse ? 'zoom-in-bottom' : 'zoom-in-top'"
:key="isReverse ? 'bottom' : 'top'"
>
<div class="select__dropdown" v-if="isOpen" ref="dropdownRef" :style="dropdownStyle">
<slot></slot>
</div>
</transition>
</teleport>
@@ -200,24 +188,27 @@ onBeforeUnmount(() => {
<style scoped lang="scss">
.select {
position: relative;
width: 100%;
font-size: 1rem;
@apply relative w-full;
&__wrapper {
display: flex;
align-items: center;
justify-content: space-between;
@apply flex items-center justify-between rounded-md cursor-pointer transition-all duration-300;
height: 2rem;
padding: 0 0.5rem;
border: 1px solid var(--color-input-border);
border-radius: 0.25rem;
background-color: var(--color-input-bg, #fff);
cursor: pointer;
transition: all 0.3s;
&:hover {
border-color: var(--color-select-bg);
&:not(.disabled):hover {
border-color: #3c89e8;
}
&.active {
border-color: #1668dc !important;
box-shadow: 0 0 2px 1px #166ce4;
}
&.disabled {
background: var(--color-fourth);
opacity: 0.5;
cursor: not-allowed;
}
}
@@ -232,17 +223,12 @@ onBeforeUnmount(() => {
}
}
&__suffix {
display: flex;
align-items: center;
&__arrow {
color: #999;
transform: rotate(-90deg);
transition: transform 0.3s;
.arrow {
transform: rotate(-90deg);
transition: transform 0.3s;
}
.is-reverse {
&.is-reverse {
transform: rotate(90deg);
}
}
@@ -251,44 +237,17 @@ onBeforeUnmount(() => {
.select__dropdown {
max-height: 200px;
overflow-y: auto;
background-color: var(--color-input-bg);
border: 1px solid var(--color-input-border);
background-color: var(--color-third);
border-radius: 0.25rem;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.select__options {
margin: 0;
padding: 0;
list-style: none;
}
.select__option {
padding: 0.5rem;
cursor: pointer;
transition: background-color 0.3s;
&:hover {
background-color: var(--color-item-hover);
}
&.is-selected {
color: var(--color-select-bg);
font-weight: bold;
background-color: var(--color-item-active);
}
}
.is-disabled {
opacity: 0.7;
cursor: not-allowed;
@apply shadow-lg;
}
/* 往下展开的动画 */
.zoom-in-top-enter-active,
.zoom-in-top-leave-active {
transition: transform 0.3s cubic-bezier(0.23, 1, 0.32, 1),
opacity 0.3s cubic-bezier(0.23, 1, 0.32, 1);
transition:
transform 0.3s cubic-bezier(0.23, 1, 0.32, 1),
opacity 0.3s cubic-bezier(0.23, 1, 0.32, 1);
transform-origin: center top;
}
@@ -301,8 +260,9 @@ onBeforeUnmount(() => {
/* 往上展开的动画 */
.zoom-in-bottom-enter-active,
.zoom-in-bottom-leave-active {
transition: transform 0.3s cubic-bezier(0.23, 1, 0.32, 1),
opacity 0.3s cubic-bezier(0.23, 1, 0.32, 1);
transition:
transform 0.3s cubic-bezier(0.23, 1, 0.32, 1),
opacity 0.3s cubic-bezier(0.23, 1, 0.32, 1);
transform-origin: center bottom;
}

View File

@@ -1,24 +1,24 @@
<script setup lang="ts">
import { onMounted, onUnmounted, watch } from "vue";
import Tooltip from "@/components/base/Tooltip.vue";
import { useEventListener } from "@/hooks/event.ts";
import { onMounted, onUnmounted, watch } from 'vue'
import Tooltip from '@/components/base/Tooltip.vue'
import { useEventListener } from '@/hooks/event.ts'
import BaseButton from "@/components/BaseButton.vue";
import { useRuntimeStore } from "@/stores/runtime.ts";
import BaseButton from '@/components/BaseButton.vue'
import { useRuntimeStore } from '@/stores/runtime.ts'
export interface ModalProps {
modelValue?: boolean,
showClose?: boolean,
title?: string,
content?: string,
fullScreen?: boolean;
modelValue?: boolean
showClose?: boolean
title?: string
content?: string
fullScreen?: boolean
padding?: boolean
footer?: boolean
header?: boolean
confirmButtonText?: string
cancelButtonText?: string,
keyboard?: boolean,
closeOnClickBg?: boolean,
cancelButtonText?: string
keyboard?: boolean
closeOnClickBg?: boolean
confirm?: any
beforeClose?: any
}
@@ -32,15 +32,10 @@ const props = withDefaults(defineProps<ModalProps>(), {
header: true,
confirmButtonText: '确认',
cancelButtonText: '取消',
keyboard: true
keyboard: true,
})
const emit = defineEmits([
'update:modelValue',
'close',
'ok',
'cancel',
])
const emit = defineEmits(['update:modelValue', 'close', 'ok', 'cancel'])
let confirmButtonLoading = $ref(false)
let zIndex = $ref(999)
@@ -56,21 +51,21 @@ async function close() {
return
}
if (props.beforeClose) {
if (!await props.beforeClose()) {
if (!(await props.beforeClose())) {
return
}
}
//记录停留时间,避免时间太短,弹框闪烁
let stayTime = Date.now() - openTime;
let closeTime = 300;
let stayTime = Date.now() - openTime
let closeTime = 300
if (stayTime < 500) {
closeTime += 500 - stayTime;
closeTime += 500 - stayTime
}
return new Promise((resolve) => {
return new Promise(resolve => {
setTimeout(() => {
maskRef?.classList.toggle('bounce-out');
modalRef?.classList.toggle('bounce-out');
}, 500 - stayTime);
maskRef?.classList.toggle('bounce-out')
modalRef?.classList.toggle('bounce-out')
}, 500 - stayTime)
setTimeout(() => {
emit('update:modelValue', false)
@@ -82,20 +77,23 @@ async function close() {
runtimeStore.modalList.splice(rIndex, 1)
}
}, closeTime)
});
})
}
watch(() => props.modelValue, n => {
// console.log('n', n)
if (n) {
id = Date.now()
runtimeStore.modalList.push({ id, close })
zIndex = 999 + runtimeStore.modalList.length
visible = true
} else {
close()
watch(
() => props.modelValue,
n => {
// console.log('n', n)
if (n) {
id = Date.now()
runtimeStore.modalList.push({ id, close })
zIndex = 999 + runtimeStore.modalList.length
visible = true
} else {
close()
}
}
})
)
onMounted(() => {
if (props.modelValue === undefined) {
@@ -139,32 +137,30 @@ async function cancel() {
emit('cancel')
await close()
}
</script>
<template>
<Teleport to="body">
<div class="modal-root" :style="{'z-index': zIndex}" v-if="visible">
<div class="modal-mask"
ref="maskRef"
v-if="!fullScreen"
@click.stop="closeOnClickBg && close()"></div>
<div class="modal"
ref="modalRef"
:class="[
fullScreen?'full':'window'
]"
>
<div class="modal-root" :style="{ 'z-index': zIndex }" v-if="visible">
<div
class="modal-mask"
ref="maskRef"
v-if="!fullScreen"
@click.stop="closeOnClickBg && close()"
></div>
<div class="modal" ref="modalRef" :class="[fullScreen ? 'full' : 'window']">
<Tooltip title="关闭">
<IconFluentDismiss20Regular @click="close"
v-if="showClose"
class="close cursor-pointer"
width="24"/>
<IconFluentDismiss20Regular
@click="close"
v-if="showClose"
class="close cursor-pointer"
width="24"
/>
</Tooltip>
<div class="modal-header" v-if="header">
<div class="title">{{ props.title }}</div>
</div>
<div class="modal-body" :class="{padding}">
<div class="modal-body" :class="{ padding }">
<slot></slot>
<div v-if="content" class="content max-h-60vh">{{ content }}</div>
</div>
@@ -174,10 +170,8 @@ async function cancel() {
</div>
<div class="right">
<BaseButton type="info" @click="cancel">{{ cancelButtonText }}</BaseButton>
<BaseButton
id="dialog-ok"
:loading="confirmButtonLoading"
@click="ok">{{ confirmButtonText }}
<BaseButton id="dialog-ok" :loading="confirmButtonLoading" @click="ok"
>{{ confirmButtonText }}
</BaseButton>
</div>
</div>
@@ -187,12 +181,7 @@ async function cancel() {
</template>
<style scoped lang="scss">
$modal-mask-bg: rgba(#000, .6);
$radius: .5rem;
$time: 0.3s;
$header-height: 4rem;
@keyframes bounce-in {
0% {
@@ -224,38 +213,21 @@ $header-height: 4rem;
}
.modal-root {
position: fixed;
top: 0;
left: 0;
z-index: 999;
display: flex;
align-items: center;
justify-content: center;
width: 100vw;
height: 100vh;
overflow: hidden;
@apply fixed top-0 left-0 z-999 flex items-center justify-center w-full h-full overflow-hidden;
.modal-mask {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: $modal-mask-bg;
transition: background 0.3s;
@apply fixed top-0 left-0 w-full h-full transition-all duration-300;
background: rgba(#000, 0.6);
animation: fade-in $time;
&.bounce-out {
background: transparent;
@apply bg-transparent;
}
}
.window {
//width: 75vw;
//height: 70vh;
box-shadow: var(--shadow);
border-radius: $radius;
animation: bounce-in $time ease-out;
@apply shadow-lg rounded-lg;
&.bounce-out {
opacity: 0;
@@ -263,8 +235,7 @@ $header-height: 4rem;
}
.full {
width: 100vw;
height: 100vh;
@apply w-full h-full;
animation: bounce-in-full $time ease-out;
&.bounce-out {
@@ -274,61 +245,34 @@ $header-height: 4rem;
}
.modal {
position: relative;
background: var(--color-second);
overflow: hidden;
display: flex;
flex-direction: column;
transition: transform $time, opacity $time;
@apply relative bg-second overflow-hidden flex flex-col transition-all duration-300;
.close {
position: absolute;
right: 1.2rem;
top: 1.2rem;
z-index: 999;
@apply absolute right-1.2rem top-1.2rem z-999;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--modal-padding);
padding-bottom: 0;
border-radius: $radius $radius 0 0;
@apply flex justify-between items-center p-5 pb-0 rounded-t-lg;
.title {
color: var(--color-font-1);
font-weight: bold;
font-size: 1.3rem;
line-height: 1.8rem;
@apply font-bold text-xl;
}
}
.modal-body {
box-sizing: border-box;
color: var(--color-main-text);
font-weight: 400;
font-size: 1.1rem;
line-height: 1.7rem;
width: 100%;
flex: 1;
overflow: hidden;
display: flex;
@apply box-border text-main-text font-normal text-base leading-6 w-full flex-1 overflow-hidden flex;
&.padding {
padding: .2rem var(--modal-padding);
@apply p-1 px-5;
}
.content {
width: 25rem;
padding: .2rem 1.6rem 1.6rem;
@apply w-64 p-2 px-4 pb-4;
}
}
.modal-footer {
display: flex;
justify-content: space-between;
padding: var(--modal-padding);
@apply flex justify-between p-5;
}
}
}

View File

@@ -182,7 +182,7 @@ defineExpose({scrollBottom})
.list {
.item {
box-sizing: border-box;
background: var(--color-item-bg);
background: var(--color-second);
color: var(--color-font-1);
border-radius: .5rem;
margin-bottom: .6rem;
@@ -199,13 +199,14 @@ defineExpose({scrollBottom})
}
&:hover {
background: var(--color-third);
.right {
opacity: 1;
}
}
&.active {
background: var(--color-item-active);
background: var(--color-fourth);
color: var(--color-font-1);
}

View File

@@ -1,16 +1,15 @@
<script setup lang="ts">
import { ShortcutKey } from "@/types/types.ts";
import { SoundFileOptions } from "@/config/env.ts";
import { getAudioFileUrl, usePlayAudio } from "@/hooks/sound.ts";
import Switch from "@/components/base/Switch.vue";
import { Option, Select } from "@/components/base/select";
import Textarea from "@/components/base/Textarea.vue";
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
import Slider from "@/components/base/Slider.vue";
import SettingItem from "@/pages/setting/SettingItem.vue";
import { useSettingStore } from "@/stores/setting.ts";
import { useBaseStore } from "@/stores/base.ts";
import { ShortcutKey } from '@/types/types.ts'
import { SoundFileOptions } from '@/config/env.ts'
import { getAudioFileUrl, usePlayAudio } from '@/hooks/sound.ts'
import Switch from '@/components/base/Switch.vue'
import { Option, Select } from '@/components/base/select'
import Textarea from '@/components/base/Textarea.vue'
import VolumeIcon from '@/components/icon/VolumeIcon.vue'
import Slider from '@/components/base/Slider.vue'
import SettingItem from '@/pages/setting/SettingItem.vue'
import { useSettingStore } from '@/stores/setting.ts'
import { useBaseStore } from '@/stores/base.ts'
const settingStore = useSettingStore()
const store = useBaseStore()
@@ -19,11 +18,9 @@ const simpleWords = $computed({
get: () => store.simpleWords.join(','),
set: v => {
try {
store.simpleWords = v.split(',');
} catch (e) {
}
}
store.simpleWords = v.split(',')
} catch (e) {}
},
})
</script>
@@ -32,81 +29,71 @@ const simpleWords = $computed({
<!-- 通用练习设置-->
<!-- 通用练习设置-->
<div>
<SettingItem title="忽略大小写"
desc="开启后输入时不区分大小写如输入“hello”和“Hello”都会被认为是正确的"
<SettingItem
title="忽略大小写"
desc="开启后输入时不区分大小写如输入“hello”和“Hello”都会被认为是正确的"
>
<Switch v-model="settingStore.ignoreCase"/>
<Switch v-model="settingStore.ignoreCase" />
</SettingItem>
<SettingItem title="允许默写模式下显示提示"
:desc="`开启后,可以通过将鼠标移动到单词上或者按快捷键 ${settingStore.shortcutKeyMap[ShortcutKey.ShowWord]} 显示正确答案`"
<SettingItem
title="允许默写模式下显示提示"
:desc="`开启后,可以通过将鼠标移动到单词上或者按快捷键 ${settingStore.shortcutKeyMap[ShortcutKey.ShowWord]} 显示正确答案`"
>
<Switch v-model="settingStore.allowWordTip"/>
<Switch v-model="settingStore.allowWordTip" />
</SettingItem>
<div class="line"></div>
<SettingItem title="简单词过滤"
desc="开启后,练习的单词中不会包含简单词;文章统计的总词数中不会包含简单词"
<SettingItem
title="简单词过滤"
desc="开启后,练习的单词中不会包含简单词;文章统计的总词数中不会包含简单词"
>
<Switch v-model="settingStore.ignoreSimpleWord"/>
<Switch v-model="settingStore.ignoreSimpleWord" />
</SettingItem>
<SettingItem title="简单词列表"
class="items-start!"
v-if="settingStore.ignoreSimpleWord"
>
<Textarea
placeholder="多个单词用英文逗号隔号"
v-model="simpleWords" :autosize="{minRows: 6, maxRows: 10}"/>
<SettingItem title="简单词列表" class="items-start!" v-if="settingStore.ignoreSimpleWord">
<Textarea
placeholder="多个单词用英文逗号隔号"
v-model="simpleWords"
:autosize="{ minRows: 6, maxRows: 10 }"
/>
</SettingItem>
<!-- 音效-->
<!-- 音效-->
<!-- 音效-->
<div class="line"></div>
<SettingItem main-title="音效"/>
<SettingItem title="单词/句子发音口音"
desc="仅单词生效,文章固定美音"
>
<Select v-model="settingStore.soundType"
placeholder="请选择"
class="w-50!"
>
<Option label="美音" value="us"/>
<Option label="英音" value="uk"/>
<SettingItem main-title="音效" />
<SettingItem title="单词/句子发音口音" desc="仅单词生效,文章固定美音">
<Select v-model="settingStore.soundType" placeholder="请选择" class="w-50!">
<Option label="美音" value="us" />
<Option label="英音" value="uk" />
</Select>
</SettingItem>
<div class="line"></div>
<SettingItem title="按键音">
<Switch v-model="settingStore.keyboardSound"/>
<Switch v-model="settingStore.keyboardSound" />
</SettingItem>
<SettingItem title="按键音效">
<Select v-model="settingStore.keyboardSoundFile"
placeholder="请选择"
class="w-50!"
>
<Select v-model="settingStore.keyboardSoundFile" placeholder="请选择" class="w-50!">
<Option
v-for="item in SoundFileOptions"
:key="item.value"
:label="item.label"
:value="item.value"
v-for="item in SoundFileOptions"
:key="item.value"
:label="item.label"
:value="item.value"
>
<div class="flex justify-between items-center w-full">
<span>{{ item.label }}</span>
<VolumeIcon
:time="100"
@click="usePlayAudio(getAudioFileUrl(item.value)[0])"/>
<VolumeIcon :time="100" @click="usePlayAudio(getAudioFileUrl(item.value)[0])" />
</div>
</Option>
</Select>
</SettingItem>
<SettingItem title="音量">
<Slider v-model="settingStore.keyboardSoundVolume" showText showValue unit="%"/>
<Slider v-model="settingStore.keyboardSoundVolume" showText showValue unit="%" />
</SettingItem>
</div>
</template>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

View File

@@ -3,7 +3,7 @@ import { defineAsyncComponent } from "vue";
import BaseIcon from "@/components/BaseIcon.vue";
import CommonSetting from "@/components/setting/CommonSetting.vue";
import WordSetting from "@/components/setting/WordSetting.vue";
import ArticleSettting from "@/components/setting/ArticleSettting.vue";
import ArticleSetting from "@/components/setting/ArticleSetting.vue";
const Dialog = defineAsyncComponent(() => import('@/components/dialog/Dialog.vue'))
@@ -39,7 +39,7 @@ let show = $ref(false)
<div class="content">
<CommonSetting v-if="tabIndex === 0"/>
<WordSetting v-if="tabIndex === 1"/>
<ArticleSettting v-if="tabIndex === 2"/>
<ArticleSetting v-if="tabIndex === 2"/>
</div>
</div>
</div>