This commit is contained in:
2025-05-15 23:15:57 +08:00
parent 723ceb5f6f
commit 900d8e8560
86 changed files with 2350 additions and 1905 deletions
+1 -1
View File
@@ -1 +1 @@
DNF风格定制组件
DNF风格定制组件
@@ -1,101 +1,101 @@
<script lang="tsx">
import { onKeyStroke, syncRef, useVModel } from "@vueuse/core"
import type { PropType, RendererElement } from "vue"
import { defineComponent, ref, renderSlot } from "vue"
import { onKeyStroke, syncRef, useVModel } from '@vueuse/core'
import type { PropType, RendererElement } from 'vue'
import { defineComponent, ref, renderSlot } from 'vue'
import CalcDialog from "@/components/calc/dialog/index.vue"
import CalcButton from "@/components/calc/button/index.vue"
import type { JSX } from "vue/jsx-runtime"
import CalcDialog from '@/components/calc/dialog/index.vue'
import CalcButton from '@/components/calc/button/index.vue'
import type { JSX } from 'vue/jsx-runtime'
export type ActionDialogResult = "ok" | "reject" | "cancel" | "none"
export type ActionDialogResult = 'ok' | 'reject' | 'cancel' | 'none'
export default defineComponent({
name: "IActionDialog",
name: 'IActionDialog',
props: {
visible: {
type: Boolean,
default: () => false
default: () => false,
},
class: {
type: String
type: String,
},
mask: {
type: Boolean,
default: () => false
default: () => false,
},
// 模态框
modal: {
type: Boolean,
default: () => false
default: () => false,
},
header: {
type: [String, Boolean] as PropType<string | boolean>,
default: () => true
default: () => true,
},
drag: {
type: String as PropType<"header" | "all" | "none">,
default: () => "header"
type: String as PropType<'header' | 'all' | 'none'>,
default: () => 'header',
},
okButton: {
type: [String, Boolean],
default: "确定"
default: '确定',
},
rejectButton: {
type: [String, Boolean],
default: false
default: false,
},
cancelButton: {
type: [String, Boolean],
default: "取消"
default: '取消',
},
closeOnYes: {
type: Boolean,
default: () => true
default: () => true,
},
closeOnCancel: {
type: Boolean,
default: () => true
default: () => true,
},
closeOnReject: {
type: Boolean,
default: () => true
default: () => true,
},
closeOnEsc: {
type: Boolean,
default: () => true
default: () => true,
},
cache: {
// 维持窗口状态
type: Boolean,
default: () => true
default: () => true,
},
defaultStatus: {
type: String as PropType<ActionDialogResult>,
default: () => "cancel"
default: () => 'cancel',
},
appendTo: {
type: [Object, String] as PropType<string | RendererElement | null | undefined>,
default: "body"
default: 'body',
},
scale: {
type: Number,
default: 1.0
}
default: 1.0,
},
},
emits: ["close", "ok", "cancel", "action", "update:visible"],
emits: ['close', 'ok', 'cancel', 'action', 'update:visible'],
setup(props, { emit, slots }) {
const visible = ref(props.visible)
syncRef(useVModel(props, "visible"), visible, { direction: "both" })
syncRef(useVModel(props, 'visible'), visible, { direction: 'both' })
const result = ref<ActionDialogResult>(props.defaultStatus)
function close() {
visible.value = false
emit("close", result.value)
emit('close', result.value)
}
onKeyStroke("ESC", (e: Event) => {
onKeyStroke('ESC', (e: Event) => {
if (visible.value && props.closeOnEsc) {
e.preventDefault()
close()
@@ -103,21 +103,21 @@ export default defineComponent({
})
function onOkClick() {
result.value = "ok"
result.value = 'ok'
if (props.closeOnYes) {
close()
}
}
function onRejectClick() {
result.value = "reject"
result.value = 'reject'
if (props.closeOnReject) {
close()
}
}
function onCancelClick() {
result.value = "cancel"
result.value = 'cancel'
if (props.closeOnCancel) {
close()
}
@@ -133,28 +133,28 @@ export default defineComponent({
if (props.okButton) {
buttons.push(
<CalcButton class="mx-2px" type="primary" onClick={onOkClick}>
{props.okButton === true ? "确定" : props.okButton}
</CalcButton>
{props.okButton === true ? '确定' : props.okButton}
</CalcButton>,
)
}
if (props.rejectButton) {
buttons.push(
<CalcButton class="mx-2px" type="primary" onClick={onRejectClick}>
{props.rejectButton === true ? "拒绝" : props.rejectButton}
</CalcButton>
{props.rejectButton === true ? '拒绝' : props.rejectButton}
</CalcButton>,
)
}
if (props.cancelButton) {
buttons.push(
<CalcButton class="mx-2px" onClick={onCancelClick}>
{props.cancelButton === true ? "取消" : props.cancelButton}
</CalcButton>
{props.cancelButton === true ? '取消' : props.cancelButton}
</CalcButton>,
)
}
return <div class="flex pb-2 items-center justify-center">{buttons}</div>
}
return renderSlot(slots, "action")
return renderSlot(slots, 'action')
}
return () => {
@@ -165,43 +165,43 @@ export default defineComponent({
drag={props.drag}
mask={props.mask}
modal={props.modal}
header={props.header == true ? "提示" : props.header}
header={props.header == true ? '提示' : props.header}
appendTo={props.appendTo}
scale={props.scale}
>
<div class={["flex items-center justify-center"]}> {renderSlot(slots, "default")}</div>
<div class={['flex items-center justify-center']}> {renderSlot(slots, 'default')}</div>
{renderAction()}
</CalcDialog>
)
}
}
},
})
</script>
<style lang="scss" scoped>
.i-button {
text-shadow: none;
.i-button {
text-shadow: none;
color: #a89679;
background: linear-gradient(#12396b, #081f3c);
border: 1px solid #554a3d;
color: #a89679;
background: linear-gradient(#12396b, #081f3c);
border: 1px solid #554a3d;
&:hover {
color: #fee97d;
background: linear-gradient(#2d4e9f, #153051);
// border: 1px solid #e3c18a;
color: #d4d4b6;
background: linear-gradient(#195cab, #0b3260);
border: 1px solid #867760;
}
&:active {
color: #e9c556;
background: linear-gradient(#2d4e9f, #122438);
border: 1px solid #e3c18a;
color: #d4d4b6;
background: linear-gradient(#195cab, #0b3260);
border: 1px solid #918168;
}
&:hover {
color: #fee97d;
background: linear-gradient(#2d4e9f, #153051);
// border: 1px solid #e3c18a;
color: #d4d4b6;
background: linear-gradient(#195cab, #0b3260);
border: 1px solid #867760;
}
&:active {
color: #e9c556;
background: linear-gradient(#2d4e9f, #122438);
border: 1px solid #e3c18a;
color: #d4d4b6;
background: linear-gradient(#195cab, #0b3260);
border: 1px solid #918168;
}
}
</style>
@@ -1,31 +1,40 @@
<script lang="tsx">
import { debouncedWatch, onClickOutside, useVModel } from "@vueuse/core"
import type { CSSProperties, PropType } from "vue";
import { Teleport, Transition, computed, defineComponent, onDeactivated, ref, renderList, watch } from "vue"
import { debouncedWatch, onClickOutside, useVModel } from '@vueuse/core'
import type { CSSProperties, PropType } from 'vue'
import {
Teleport,
Transition,
computed,
defineComponent,
onDeactivated,
ref,
renderList,
watch,
} from 'vue'
type HandleChangeFunction = (value: string | number) => string[] | Promise<string[]>
type HandleChangeFunction = (value: string | number) => string[] | Promise<string[]>
export default defineComponent({
props: {
modelValue: {
type: [String, Number] as PropType<string | number>,
default: () => ""
default: () => '',
},
placeholder: {
type: String,
default: ""
default: '',
},
handleChange: {
type: Function as PropType<HandleChangeFunction>,
default: () => () => []
default: () => () => [],
},
delay: {
type: Number,
default: 300
}
default: 300,
},
},
setup(props, { emit }) {
const modelValue = useVModel(props, "modelValue")
const modelValue = useVModel(props, 'modelValue')
const isOpen = ref(false)
const triggerRef = ref<HTMLElement>()
@@ -39,7 +48,7 @@ export default defineComponent({
return {
left: `${dropdownPosition.value.x}px`,
top: `${dropdownPosition.value.y}px`,
width: `${dropdownPosition.value.w}px`
width: `${dropdownPosition.value.w}px`,
}
})
@@ -49,19 +58,19 @@ export default defineComponent({
dropdownPosition.value = {
w: width,
x: left,
y: top + height + 2
y: top + height + 2,
}
}
}
onClickOutside(triggerRef, () => (isOpen.value = false))
window.addEventListener("resize", onResize)
window.addEventListener("scroll", onResize)
window.addEventListener('resize', onResize)
window.addEventListener('scroll', onResize)
onDeactivated(() => {
window.removeEventListener("resize", onResize)
window.removeEventListener("scroll", onResize)
window.removeEventListener('resize', onResize)
window.removeEventListener('scroll', onResize)
})
function selectItem(item: string) {
@@ -74,17 +83,21 @@ export default defineComponent({
const droplist = ref<string[]>([])
debouncedWatch(modelValue, async value => (droplist.value = await props.handleChange(value)), {
debounce: delay
})
debouncedWatch(
modelValue,
async (value) => (droplist.value = await props.handleChange(value)),
{
debounce: delay,
},
)
function handleInput() {
isOpen.value = droplist.value.length > 0
}
function handleKeyPress(event: KeyboardEvent) {
if (event.code == "Enter") {
emit("enter", modelValue.value)
if (event.code == 'Enter') {
emit('enter', modelValue.value)
}
}
@@ -92,12 +105,24 @@ export default defineComponent({
return (
<div class="i-autocomplete">
<div class="i-autocomplete-trigger" ref={triggerRef}>
<input onKeypress={handleKeyPress} class="i-autocomplete-input" onFocus={handleInput} onInput={handleInput} type="text" v-model={modelValue.value} placeholder={props.placeholder} />
<input
onKeypress={handleKeyPress}
class="i-autocomplete-input"
onFocus={handleInput}
onInput={handleInput}
type="text"
v-model={modelValue.value}
placeholder={props.placeholder}
/>
</div>
<Teleport to="body">
<Transition name="dropdown" mode="out-in">
<div class="i-autocomplete-dropdown" style={dropdownStyle.value} v-show={isOpen.value}>
{renderList(droplist.value, item => (
<div
class="i-autocomplete-dropdown"
style={dropdownStyle.value}
v-show={isOpen.value}
>
{renderList(droplist.value, (item) => (
<div class="i-autocomplete-dropdown-item" onClick={selectItem(item)}>
{item}
</div>
@@ -108,115 +133,115 @@ export default defineComponent({
</div>
)
}
}
},
})
</script>
<style scoped lang="scss">
$text-color: #937639;
.i-autocomplete {
min-width: 80px;
width: 160px;
user-select: none;
outline: none;
height: 24px;
min-height: 16px;
line-height: 100%;
// font-size: 12px;
color: $text-color;
background-color: rgba(0, 0, 0, 1);
border: 1px solid #5b472a;
border-radius: 2px;
display: block;
$text-color: #937639;
.i-autocomplete {
min-width: 80px;
width: 160px;
user-select: none;
outline: none;
height: 24px;
min-height: 16px;
line-height: 100%;
// font-size: 12px;
color: $text-color;
background-color: rgba(0, 0, 0, 1);
border: 1px solid #5b472a;
border-radius: 2px;
display: block;
.i-autocomplete-trigger {
display: flex;
height: 100%;
opacity: 0.8;
justify-content: space-between;
align-items: center;
.i-autocomplete-trigger {
display: flex;
height: 100%;
opacity: 0.8;
justify-content: space-between;
align-items: center;
.i-autocomplete-input {
padding-left: 8px;
width: 100%;
border: none;
outline: none;
color: inherit;
// font-size: 12px;
background-color: black;
font-family: SimSun;
}
&.disabled {
color: gray;
.i-select-down-icon {
background-image: url("./img/select_down_disabled.png");
}
}
}
.i-autocomplete-trigger:hover {
opacity: 1;
}
}
.i-autocomplete-dropdown {
position: fixed;
max-height: 160px;
overflow-y: auto;
background: black;
// font-size: 12px;
z-index: 888;
$hoverColor: #002947;
$activeColor: color.scale($hoverColor, $lightness: 5%);
color: $text-color;
.i-autocomplete-dropdown-item {
// font-size: 12px;
height: 20px;
line-height: 20px;
margin: 0;
padding: 0 8px;
.i-autocomplete-input {
padding-left: 8px;
width: 100%;
border: none;
outline: none;
appearance: none;
display: block;
overflow: hidden;
color: inherit;
// font-size: 12px;
background-color: black;
font-family: SimSun;
}
&.active {
background-color: $activeColor;
}
&.disabled {
color: gray;
&:hover:not(.active) {
// font-size: 12px;
background-color: $hoverColor;
.i-select-down-icon {
background-image: url('./img/select_down_disabled.png');
}
}
}
.dropdown-active {
animation: dropdown-enter 0.2s ease-in;
.i-autocomplete-trigger:hover {
opacity: 1;
}
}
.dropdown-active {
animation: dropdown-leave 0.2s ease-in;
}
.i-autocomplete-dropdown {
position: fixed;
max-height: 160px;
overflow-y: auto;
background: black;
// font-size: 12px;
z-index: 888;
.dropdown-enter-active {
animation: dropdown-enter 0.2s ease-in;
}
$hoverColor: #002947;
$activeColor: color.scale($hoverColor, $lightness: 5%);
color: $text-color;
.dropdown-leave-active {
animation: dropdown-leave 0.2s ease-in;
}
.i-autocomplete-dropdown-item {
// font-size: 12px;
height: 20px;
line-height: 20px;
margin: 0;
padding: 0 8px;
border: none;
outline: none;
appearance: none;
display: block;
overflow: hidden;
.dropdown-enter-active {
animation: dropdown-enter 0.2s ease-in;
}
&.active {
background-color: $activeColor;
}
.dropdown-leave-active {
animation: dropdown-leave 0.2s ease-in;
&:hover:not(.active) {
// font-size: 12px;
background-color: $hoverColor;
}
}
}
.dropdown-active {
animation: dropdown-enter 0.2s ease-in;
}
.dropdown-active {
animation: dropdown-leave 0.2s ease-in;
}
.dropdown-enter-active {
animation: dropdown-enter 0.2s ease-in;
}
.dropdown-leave-active {
animation: dropdown-leave 0.2s ease-in;
}
.dropdown-enter-active {
animation: dropdown-enter 0.2s ease-in;
}
.dropdown-leave-active {
animation: dropdown-leave 0.2s ease-in;
}
</style>
+74 -70
View File
@@ -1,116 +1,120 @@
<script lang="tsx">
import { defineComponent, renderSlot } from "vue"
import { RouterLink } from "vue-router"
import { defineComponent, renderSlot } from 'vue'
import { RouterLink } from 'vue-router'
export default defineComponent({
name: "CalcButton",
name: 'CalcButton',
props: {
small: {
type: Boolean,
default: false
default: false,
},
to: {
type: String,
default: null
default: null,
},
class: {
type: [String],
default: ""
default: '',
},
icon: {
type: String
type: String,
},
type: {
type: String,
default: "primary"
default: 'primary',
},
disabled: {
type: Boolean,
default: false
}
default: false,
},
},
emits: ["click"],
setup(props, { slots,emit }) {
emits: ['click'],
setup(props, { slots, emit }) {
return () => {
const proerpties = {
to: props.to,
class: {
[props.class]: !!props.class,
"i-button cursor-pointer outline-none text-shadow": true,
"small": props.small,
[`icon-${props.icon}`]: !!props.icon
'i-button cursor-pointer outline-none text-shadow': true,
small: props.small,
[`icon-${props.icon}`]: !!props.icon,
},
onClick: (e: MouseEvent) => {
if (props.to) {
e.preventDefault()
}
emit("click", e)
emit('click', e)
},
disabled: props.disabled
disabled: props.disabled,
}
const content = renderSlot(slots, "default")
return props.to ? <RouterLink {...proerpties}>{content}</RouterLink> : <button {...proerpties}>{content}</button>
const content = renderSlot(slots, 'default')
return props.to ? (
<RouterLink {...proerpties}>{content}</RouterLink>
) : (
<button {...proerpties}>{content}</button>
)
}
}
},
})
</script>
<style lang="scss">
.i-button {
min-width: 56px;
height: 24px;
// line-height: 20px;
min-width: 56px;
height: 24px;
// line-height: 20px;
// font-size: 12px;
// font-size: 12px;
color: #e9c556;
background: linear-gradient(#223768, #122438);
border: 1px solid #755f44;
border-radius: 2px;
display: block;
text-align: center;
text-decoration: none;
transition: all 200ms ease-in-out;
&[class*='icon-'] {
min-width: 1px;
width: 28px;
height: 28px;
padding: 0;
border: none !important;
transition: all 0.2s ease-in-out;
}
&:hover {
color: #fee97d;
background: linear-gradient(#2d4e9f, #153051);
// border: 1px solid #e3c18a;
}
&:active {
color: #e9c556;
background: linear-gradient(#223768, #122438);
border: 1px solid #755f44;
background: linear-gradient(#2d4e9f, #122438);
border: 1px solid #e3c18a;
}
&:disabled {
color: #696969;
background: linear-gradient(#353535, #1c1c1c);
border: 1px solid #414141;
border-radius: 2px;
display: block;
text-align: center;
text-decoration: none;
transition: all 200ms ease-in-out;
&[class*="icon-"] {
min-width: 1px;
width: 28px;
height: 28px;
padding: 0;
border: none !important;
transition: all 0.2s ease-in-out;
}
&:hover {
color: #fee97d;
background: linear-gradient(#2d4e9f, #153051);
// border: 1px solid #e3c18a;
}
&:active {
color: #e9c556;
background: linear-gradient(#2d4e9f, #122438);
border: 1px solid #e3c18a;
}
&:disabled {
color: #696969;
background: linear-gradient(#353535, #1c1c1c);
border: 1px solid #414141;
border-radius: 2px;
}
&.small {
min-width: 28px;
height: 20px;
// line-height: 20px;
font-size: 12px;
padding-top: 0;
padding-bottom: 0;
}
}
.i-href {
display: block;
text-decoration: none;
&.small {
min-width: 28px;
height: 20px;
// line-height: 20px;
font-size: 12px;
padding-top: 0;
padding-bottom: 0;
}
}
.i-href {
display: block;
text-decoration: none;
}
</style>
+122 -119
View File
@@ -1,9 +1,9 @@
<script lang="tsx">
import { selectionEmits, selectionProps } from "hoci"
import type { CSSProperties, PropType } from "vue";
import { Teleport, Transition, computed, defineComponent, onDeactivated, ref, watch } from "vue"
import { selectionEmits, selectionProps } from 'hoci'
import type { CSSProperties, PropType } from 'vue'
import { Teleport, Transition, computed, defineComponent, onDeactivated, ref, watch } from 'vue'
import { onClickOutside, useVModel } from "@vueuse/core"
import { onClickOutside, useVModel } from '@vueuse/core'
interface ICascaderItem {
label: string
id?: string | number
@@ -13,33 +13,33 @@ interface ICascaderItem {
}
export default defineComponent({
name: "ICascader",
name: 'ICascader',
props: {
...selectionProps,
disabled: {
type: Boolean,
default: false
default: false,
},
width: {
type: Number,
default: 120
default: 120,
},
emptyLabel: {
type: String
type: String,
},
highlight: {
type: String,
// warn remind
default: ""
default: '',
},
items: {
type: Array as PropType<ICascaderItem[]>,
default: () => []
}
default: () => [],
},
},
emits: [...selectionEmits, "select"],
emits: [...selectionEmits, 'select'],
setup(props, { emit }) {
const modelValue = useVModel(props, "modelValue")
const modelValue = useVModel(props, 'modelValue')
const isOpen = ref(false)
const triggerRef = ref<HTMLElement>()
@@ -60,13 +60,13 @@ export default defineComponent({
return {
left: `${dropdownPosition.value.x}px`,
top: `${dropdownPosition.value.y}px`,
width: `${dropdownPosition.value.w}px`
width: `${dropdownPosition.value.w}px`,
}
} else {
return {
left: `${dropdownPosition.value.x}px`,
bottom: `${dropdownPosition.value.z}px`,
width: `${dropdownPosition.value.w}px`
width: `${dropdownPosition.value.w}px`,
}
}
})
@@ -79,26 +79,25 @@ export default defineComponent({
w: width,
x: left,
y: top + height + 2,
z: 0
z: 0,
}
} else {
dropdownPosition.value = {
w: width,
x: left,
z: window.innerHeight - top + 2,
y: 0
y: 0,
}
}
}
}
window.addEventListener("resize", onResize)
window.addEventListener("scroll", onResize)
window.addEventListener('resize', onResize)
window.addEventListener('scroll', onResize)
onDeactivated(() => {
window.removeEventListener("resize", onResize)
window.removeEventListener("scroll", onResize)
window.removeEventListener('resize', onResize)
window.removeEventListener('scroll', onResize)
})
const selectItem = ref<ICascaderItem>()
@@ -108,7 +107,7 @@ export default defineComponent({
isOpen.value = false
}
selectItem.value = item
emit("select", item.value)
emit('select', item.value)
}
function loadNode(item: ICascaderItem) {
@@ -122,11 +121,15 @@ export default defineComponent({
return () => {
return (
<div class={["min-w-20 w-40 i-cascader"].concat(props.highlight)} onClick={collapse} ref={selectRef}>
<div
class={['min-w-20 w-40 i-cascader'].concat(props.highlight)}
onClick={collapse}
ref={selectRef}
>
<div
class={{
"i-cascader-trigger": true,
"disabled": props.disabled
'i-cascader-trigger': true,
disabled: props.disabled,
}}
ref={triggerRef}
>
@@ -151,122 +154,122 @@ export default defineComponent({
</div>
)
}
}
},
})
</script>
<style lang="scss">
@use 'sass:color';
$text-color: #937639;
.i-cascader {
min-width: 80px;
width: 160px;
user-select: none;
outline: none;
height: 16px;
min-height: 16px;
line-height: 100%;
// font-size: 12px;
color: $text-color;
background-color: rgba(0, 0, 0, 1);
border: 1px solid #5b472a;
border-radius: 2px;
.i-cascader {
min-width: 80px;
width: 160px;
user-select: none;
outline: none;
height: 16px;
min-height: 16px;
line-height: 100%;
// font-size: 12px;
color: $text-color;
background-color: rgba(0, 0, 0, 1);
border: 1px solid #5b472a;
border-radius: 2px;
padding: 0;
padding-right: 3px;
margin: 0;
display: block;
.i-cascader-trigger {
padding: 0;
padding-right: 3px;
margin: 0;
display: block;
display: flex;
height: 100%;
opacity: 0.9;
.i-cascader-trigger {
padding: 0;
display: flex;
height: 100%;
opacity: 0.9;
justify-content: space-between;
align-items: center;
justify-content: space-between;
align-items: center;
.i-cascader-label {
padding-left: 5px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
max-width: calc(100% - 20px);
}
.i-cascader-label {
padding-left: 5px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
max-width: calc(100% - 20px);
}
.i-cascader-down-icon {
background-image: url('./img/select_down.png');
background-size: cover;
background-repeat: no-repeat;
background-position: center;
border-radius: 2px;
width: 15px;
height: 14px;
}
.i-cascader-down-icon:active {
background-image: url('./img/select_down_clicked.png');
background-size: cover;
background-repeat: no-repeat;
background-position: center;
border-radius: 2px;
width: 15px;
height: 14px;
}
&.disabled {
color: gray;
.i-cascader-down-icon {
background-image: url("./img/select_down.png");
background-size: cover;
background-repeat: no-repeat;
background-position: center;
border-radius: 2px;
width: 15px;
height: 14px;
}
.i-cascader-down-icon:active {
background-image: url("./img/select_down_clicked.png");
background-size: cover;
background-repeat: no-repeat;
background-position: center;
border-radius: 2px;
width: 15px;
height: 14px;
}
&.disabled {
color: gray;
.i-cascader-down-icon {
background-image: url("./img/select_down_disabled.png");
}
background-image: url('./img/select_down_disabled.png');
}
}
.i-cascader-trigger:hover {
opacity: 1;
}
}
.remind {
color: #32e432 !important;
border: 1px solid #aa8651;
.i-cascader-trigger:hover {
opacity: 1;
}
}
.i-cascader-dropdown {
position: fixed;
max-height: 160px;
overflow-y: auto;
overflow-x: hidden;
background: black;
// font-size: 12px;
z-index: 888;
.remind {
color: #32e432 !important;
border: 1px solid #aa8651;
}
$hoverColor: #002947;
$activeColor: color.scale($hoverColor, $lightness: 5%);
color: $text-color;
}
.i-cascader-dropdown {
position: fixed;
max-height: 160px;
overflow-y: auto;
overflow-x: hidden;
background: black;
// font-size: 12px;
z-index: 888;
.dropdown-active {
animation: dropdown-enter 0.2s ease-in;
}
$hoverColor: #002947;
$activeColor: color.scale($hoverColor, $lightness: 5%);
color: $text-color;
}
.dropdown-active {
animation: dropdown-leave 0.2s ease-in;
}
.dropdown-active {
animation: dropdown-enter 0.2s ease-in;
}
.dropdown-enter-active {
animation: dropdown-enter 0.2s ease-in;
}
.dropdown-active {
animation: dropdown-leave 0.2s ease-in;
}
.dropdown-leave-active {
animation: dropdown-leave 0.2s ease-in;
}
.dropdown-enter-active {
animation: dropdown-enter 0.2s ease-in;
}
.dropdown-enter-active {
animation: dropdown-enter 0.2s ease-in;
}
.dropdown-leave-active {
animation: dropdown-leave 0.2s ease-in;
}
.dropdown-leave-active {
animation: dropdown-leave 0.2s ease-in;
}
.dropdown-enter-active {
animation: dropdown-enter 0.2s ease-in;
}
.dropdown-leave-active {
animation: dropdown-leave 0.2s ease-in;
}
</style>
@@ -1,25 +1,25 @@
<script lang="tsx">
import { defineComponent, provide } from "vue"
import { selectionEmits, selectionProps, useSelectionList } from "hoci"
import { CHECKBOX_GROUP } from "../shared"
import { defineComponent, provide } from 'vue'
import { selectionEmits, selectionProps, useSelectionList } from 'hoci'
import { CHECKBOX_GROUP } from '../shared'
export default defineComponent({
name: "ICheckboxGroup",
name: 'ICheckboxGroup',
components: {},
props: {
...selectionProps,
activeClass: {
type: String,
default: "checked"
default: 'checked',
},
unactiveClass: {
type: String,
default: "unchecked"
default: 'unchecked',
},
multiple: {
type: [Boolean, Number],
default: true
}
default: true,
},
},
emits: [...selectionEmits],
@@ -29,45 +29,45 @@ export default defineComponent({
const { render } = useSelectionList(props, context)
return () => render()
}
},
})
</script>
<style lang="scss">
.i-checkbox {
display: flex;
align-items: center;
&.checked {
.i-checkbox-icon {
background-image: url("@/assets/img/control/checkbox_checked.png");
}
}
.i-checkbox {
display: flex;
align-items: center;
&.checked {
.i-checkbox-icon {
width: 12px;
min-width: 12px;
height: 12px;
margin-right: 4px;
background-image: url("@/assets/img/control/checkbox_uncheck.png");
}
.i-checkbox-icon-disable {
width: 12px;
min-width: 12px;
height: 12px;
margin-right: 4px;
background-image: url("@/assets/img/control/checkbox_disable.png");
}
&:hover:not(.checked) {
.i-checkbox-icon {
background-image: url("@/assets/img/control/checkbox_hover.png");
}
}
.i-checkbox-label {
line-height: 24px;
color: #937639;
background-image: url('@/assets/img/control/checkbox_checked.png');
}
}
.i-checkbox-icon {
width: 12px;
min-width: 12px;
height: 12px;
margin-right: 4px;
background-image: url('@/assets/img/control/checkbox_uncheck.png');
}
.i-checkbox-icon-disable {
width: 12px;
min-width: 12px;
height: 12px;
margin-right: 4px;
background-image: url('@/assets/img/control/checkbox_disable.png');
}
&:hover:not(.checked) {
.i-checkbox-icon {
background-image: url('@/assets/img/control/checkbox_hover.png');
}
}
.i-checkbox-label {
line-height: 24px;
color: #937639;
}
}
</style>
+19 -11
View File
@@ -1,33 +1,41 @@
<script lang="tsx">
import { useToggle, useVModel } from "@vueuse/core"
import { defineComponent, renderSlot } from "vue"
import { useToggle, useVModel } from '@vueuse/core'
import { defineComponent, renderSlot } from 'vue'
export default defineComponent({
name: "ICollapse",
name: 'ICollapse',
props: {
title: {
type: String
type: String,
},
modelValue: {
type: Boolean,
default: () => true
}
default: () => true,
},
},
setup(props, { slots, emit }) {
const modelValue = useVModel(props, "modelValue", emit, { passive: true })
const modelValue = useVModel(props, 'modelValue', emit, { passive: true })
const toggle = useToggle(modelValue)
return () => {
return (
<div class="rounded h-auto bg-hex-00000078 border-1 border-hex-ffffff28 my-1 overflow-hidden">
<div onClick={() => toggle()} v-text={props.title} class="bg-gradient-to-b flex from-hex-244281 to-hex-122438 h-6 text-xs text-center w-full items-center justify-center"></div>
<div class={["transition-all", "ease-out-in", "h-auto"].concat(modelValue.value ? ["max-h-200"] : ["max-h-0"])}>
<div class="p-2">{renderSlot(slots, "default")}</div>
<div
onClick={() => toggle()}
v-text={props.title}
class="bg-gradient-to-b flex from-hex-244281 to-hex-122438 h-6 text-xs text-center w-full items-center justify-center"
></div>
<div
class={['transition-all', 'ease-out-in', 'h-auto'].concat(
modelValue.value ? ['max-h-200'] : ['max-h-0'],
)}
>
<div class="p-2">{renderSlot(slots, 'default')}</div>
</div>
</div>
)
}
}
},
})
</script>
+79 -48
View File
@@ -1,63 +1,72 @@
<script lang="tsx">
import { onClickOutside, onKeyDown, syncRef, useDraggable, useVModel } from "@vueuse/core"
import type { PropType, RendererElement } from "vue"
import { Teleport, Transition, computed, defineComponent, provide, ref, renderSlot, watch } from "vue"
import { provideShow } from "@/components/hooks/show"
import { onClickOutside, onKeyDown, syncRef, useDraggable, useVModel } from '@vueuse/core'
import type { PropType, RendererElement } from 'vue'
import {
Teleport,
Transition,
computed,
defineComponent,
provide,
ref,
renderSlot,
watch,
} from 'vue'
import { provideShow } from '@/components/hooks/show'
export default defineComponent({
name: "IDialog",
name: 'IDialog',
props: {
visible: {
type: Boolean,
default: () => false
default: () => false,
},
class: {
type: String
type: String,
},
mask: {
type: Boolean,
default: () => false
default: () => false,
},
// 模态框
modal: {
type: Boolean,
default: () => false
default: () => false,
},
header: {
type: [String, Boolean],
default: () => "提示"
default: () => '提示',
},
closeOnEsc: {
type: Boolean,
default: () => true
default: () => true,
},
closeButton: {
type: Boolean,
default: () => true
default: () => true,
},
cache: {
// 维持窗口状态
type: Boolean,
default: () => true
default: () => true,
},
lazy: {
type: Boolean,
default: () => false
default: () => false,
},
drag: {
type: String as PropType<"header" | "all" | "none">,
default: () => "header"
type: String as PropType<'header' | 'all' | 'none'>,
default: () => 'header',
},
appendTo: {
type: [Object, String] as PropType<string | RendererElement | null | undefined>,
default: "body"
default: 'body',
},
scale: {
type: Number,
default: 1
}
default: 1,
},
},
emits: ["close", "update:visible"],
emits: ['close', 'update:visible'],
setup(props, { emit, slots }) {
const dialogRef = ref<HTMLElement | null>(null)
const headerRef = ref<HTMLElement | null>(null)
@@ -66,18 +75,18 @@ export default defineComponent({
provideShow(visible)
provide("dialogRef", dialogRef)
provide('dialogRef', dialogRef)
const loaded = ref(!props.lazy)
const stopLoadWatch = watch(visible, val => {
const stopLoadWatch = watch(visible, (val) => {
if (val) {
loaded.value = true
stopLoadWatch()
}
})
syncRef(useVModel(props, "visible"), visible, { direction: "both" })
syncRef(useVModel(props, 'visible'), visible, { direction: 'both' })
onClickOutside(dialogRef, () => {
if (props.modal) {
@@ -87,17 +96,17 @@ export default defineComponent({
function close() {
visible.value = false
emit("close")
emit('close')
}
onKeyDown(
"Escape",
'Escape',
() => {
if (visible.value && props.closeOnEsc) {
close()
}
},
{ target: document }
{ target: document },
)
const init = ref(true)
@@ -105,24 +114,24 @@ export default defineComponent({
const { x, y, style } = useDraggable(
computed(() => {
switch (props.drag) {
case "header":
case 'header':
return headerRef.value
case "all":
case 'all':
return dialogRef.value
case "none":
case 'none':
default:
return null
}
}),
{
initialValue: { x: 0, y: 0 }
}
initialValue: { x: 0, y: 0 },
},
)
const isFixed = computed(() => x.value + y.value > 0)
const appendToRef = computed(() => {
return props.appendTo || "body"
return props.appendTo || 'body'
})
return () => {
@@ -130,19 +139,41 @@ export default defineComponent({
<Teleport to={appendToRef.value}>
<Transition name="dialog" mode="out-in">
{loaded.value && (props.cache || visible.value) && (
<div v-show={visible.value} class={["dialog-mask w-full h-full fixed top-0 left-0 z-999 flex justify-center items-center "].concat(props.mask ? "bg-hex-000 bg-opacity-66" : "")} style={`transform: scale(${props.scale});${x.value == 0 && y.value == 0 ?"transform-origin:center":"transform-origin:left top"}`}>
<div ref={dialogRef} class={["h-auto border-1 border-hex-2b2b2b border-solid shadow-sm round-1 dialog min-w-48", isFixed.value ? "fixed" : "", props.class]} style={`top:${y.value/props.scale}px;left:${x.value/props.scale}px`}>
<div
v-show={visible.value}
class={[
'dialog-mask w-full h-full fixed top-0 left-0 z-999 flex justify-center items-center ',
].concat(props.mask ? 'bg-hex-000 bg-opacity-66' : '')}
style={`transform: scale(${props.scale});${x.value == 0 && y.value == 0 ? 'transform-origin:center' : 'transform-origin:left top'}`}
>
<div
ref={dialogRef}
class={[
'h-auto border-1 border-hex-2b2b2b border-solid shadow-sm round-1 dialog min-w-48',
isFixed.value ? 'fixed' : '',
props.class,
]}
style={`top:${y.value / props.scale}px;left:${x.value / props.scale}px`}
>
{props.header && (
<div ref={headerRef} class="flex h-4 px-1 leading-4 z-9999 justify-center relative app-header py-2px">
<div
ref={headerRef}
class="flex h-4 px-1 leading-4 z-9999 justify-center relative app-header py-2px"
>
<div class="text-xs text-shadow ">{props.header}</div>
{props.closeButton && (
<div class="flex top-0 right-0 bottom-0 items-center absolute">
<div onClick={close} class="cursor-pointer flex h-4 text-center w-4 items-center close-icon"></div>
<div
onClick={close}
class="cursor-pointer flex h-4 text-center w-4 items-center close-icon"
></div>
</div>
)}
</div>
)}
<div class="bg-hex-000 bg-opacity-80 text-white text-xs">{renderSlot(slots, "default")}</div>
<div class="bg-hex-000 bg-opacity-80 text-white text-xs">
{renderSlot(slots, 'default')}
</div>
</div>
</div>
)}
@@ -150,21 +181,21 @@ export default defineComponent({
</Teleport>
)
}
}
},
})
</script>
<style lang="scss" scoped>
.dialog-enter-active {
animation: fade-in 400ms;
.dialog {
animation: zoom-in 400ms;
}
.dialog-enter-active {
animation: fade-in 400ms;
.dialog {
animation: zoom-in 400ms;
}
.dialog-leave-active {
animation: fade-in reverse 400ms;
.dialog {
animation: zoom-out 400ms;
}
}
.dialog-leave-active {
animation: fade-in reverse 400ms;
.dialog {
animation: zoom-out 400ms;
}
}
</style>
+137 -128
View File
@@ -1,33 +1,42 @@
<script lang="tsx">
import { selectionEmits, selectionProps, useSelectionList } from "hoci"
import type { CSSProperties, PropType, RendererElement } from "vue";
import { Teleport, Transition, computed, defineComponent, onDeactivated, ref, renderSlot, watch } from "vue"
import { selectionEmits, selectionProps, useSelectionList } from 'hoci'
import type { CSSProperties, PropType, RendererElement } from 'vue'
import {
Teleport,
Transition,
computed,
defineComponent,
onDeactivated,
ref,
renderSlot,
watch,
} from 'vue'
import { onClickOutside } from "@vueuse/core"
import { onClickOutside } from '@vueuse/core'
export default defineComponent({
name: "IIconselect",
name: 'IIconselect',
props: {
...selectionProps,
disabled: {
type: Boolean,
default: false
default: false,
},
width: {
type: Number,
default: 28
default: 28,
},
emptyLabel: {
type: String
type: String,
},
columnNum: {
type: Number,
default: 1
default: 1,
},
appendTo: {
type: [Object, String] as PropType<string | RendererElement | null | undefined>,
default: "body"
}
default: 'body',
},
},
emits: selectionEmits,
setup(props, context) {
@@ -36,9 +45,9 @@ export default defineComponent({
const { renderItem } = useSelectionList(() => {
return {
...props,
itemClass: "i-option",
activeClass: "active",
unactiveClass: "unactive"
itemClass: 'i-option',
activeClass: 'active',
unactiveClass: 'unactive',
}
}, context)
@@ -52,7 +61,7 @@ export default defineComponent({
watch(isOpen, onResize)
const appendToRef = computed(() => {
return props.appendTo || "body"
return props.appendTo || 'body'
})
function collapse() {
@@ -66,7 +75,7 @@ export default defineComponent({
return {
left: `${dropdownPosition.value.x}px`,
top: `${dropdownPosition.value.y}px`,
width: `${columnNum.value * 32 + 11}px`
width: `${columnNum.value * 32 + 11}px`,
}
})
@@ -76,19 +85,19 @@ export default defineComponent({
dropdownPosition.value = {
w: width,
x: left - 2,
y: top + height + 2
y: top + height + 2,
}
}
}
onClickOutside(triggerRef, () => (isOpen.value = false))
window.addEventListener("resize", onResize)
window.addEventListener("scroll", onResize)
window.addEventListener('resize', onResize)
window.addEventListener('scroll', onResize)
onDeactivated(() => {
window.removeEventListener("resize", onResize)
window.removeEventListener("scroll", onResize)
window.removeEventListener('resize', onResize)
window.removeEventListener('scroll', onResize)
})
const { slots } = context
@@ -101,8 +110,8 @@ export default defineComponent({
<div class="i-icon-select" onClick={collapse}>
<div
class={{
"i-select-trigger": true,
"disabled": props.disabled
'i-select-trigger': true,
disabled: props.disabled,
}}
ref={triggerRef}
>
@@ -111,133 +120,133 @@ export default defineComponent({
<Teleport to={appendToRef.value}>
<Transition name="dropdown" mode="out-in">
<div class="i-icon-select-dropdown" style={dropdownStyle.value} v-show={isOpen.value}>
{renderSlot(slots, "default")}
{renderSlot(slots, 'default')}
</div>
</Transition>
</Teleport>
</div>
)
}
}
},
})
</script>
<style lang="scss">
@use 'sass:color';
$text-color: #e9c556;
.i-icon-select {
width: 28px;
min-width: 28px;
user-select: none;
outline: none;
height: 28px;
line-height: 100%;
// font-size: 12px;
color: $text-color;
background-color: rgba(0, 0, 0, 1);
border: 1px solid #5b472a;
border-radius: 2px;
@use 'sass:color';
$text-color: #e9c556;
.i-icon-select {
width: 28px;
min-width: 28px;
user-select: none;
outline: none;
height: 28px;
line-height: 100%;
// font-size: 12px;
color: $text-color;
background-color: rgba(0, 0, 0, 1);
border: 1px solid #5b472a;
border-radius: 2px;
padding: 0;
margin: 0;
display: block;
.i-select-trigger {
padding: 0;
margin: 0;
display: block;
.i-select-trigger {
padding: 0;
display: flex;
height: 100%;
opacity: 1;
justify-content: center;
align-items: center;
&.disabled {
color: gray;
}
}
.i-select-trigger:hover {
opacity: 1;
}
}
.i-icon-select-dropdown {
display: flex;
flex-direction: row;
flex-wrap: wrap;
position: fixed;
overflow-y: auto;
overflow-x: hidden;
max-height: 480px;
// background: rgba(255, 255, 255, 0.1);
// font-size: 12px;
z-index: 888;
min-height: 28px;
height: 100%;
opacity: 1;
$hoverColor: #002947;
$activeColor: color.scale($hoverColor, $lightness: 5%);
color: $text-color;
justify-content: center;
align-items: center;
.i-option {
// font-size: 12px;
height: 28px;
line-height: 28px;
margin: 1px;
border: none;
outline: none;
appearance: none;
display: block;
overflow: hidden;
border: gray 1px solid;
// &.active {
// background-color: $activeColor;
// }
&.active::before {
content: "";
width: 28px;
height: 28px;
position: absolute;
background-image: url("/src/assets/img/control/item_checked.png");
background-size: 80% 80%;
background-repeat: no-repeat;
background-position: center;
z-index: 6;
}
&:hover:not(.active) ::before {
content: "";
width: 28px;
height: 28px;
position: absolute;
background-image: url("@/assets/img/control/item_hover.png");
background-size: 100% 100%;
z-index: 6;
}
&.disabled {
color: gray;
}
}
.dropdown-active {
animation: dropdown-enter 0.2s ease-in;
.i-select-trigger:hover {
opacity: 1;
}
}
.dropdown-active {
animation: dropdown-leave 0.2s ease-in;
}
.i-icon-select-dropdown {
display: flex;
flex-direction: row;
flex-wrap: wrap;
position: fixed;
overflow-y: auto;
overflow-x: hidden;
max-height: 480px;
// background: rgba(255, 255, 255, 0.1);
// font-size: 12px;
z-index: 888;
min-height: 28px;
.dropdown-enter-active {
animation: dropdown-enter 0.2s ease-in;
}
$hoverColor: #002947;
$activeColor: color.scale($hoverColor, $lightness: 5%);
color: $text-color;
.dropdown-leave-active {
animation: dropdown-leave 0.2s ease-in;
}
.i-option {
// font-size: 12px;
height: 28px;
line-height: 28px;
margin: 1px;
border: none;
outline: none;
appearance: none;
display: block;
overflow: hidden;
border: gray 1px solid;
.dropdown-enter-active {
animation: dropdown-enter 0.2s ease-in;
}
// &.active {
// background-color: $activeColor;
// }
.dropdown-leave-active {
animation: dropdown-leave 0.2s ease-in;
&.active::before {
content: '';
width: 28px;
height: 28px;
position: absolute;
background-image: url('/src/assets/img/control/item_checked.png');
background-size: 80% 80%;
background-repeat: no-repeat;
background-position: center;
z-index: 6;
}
&:hover:not(.active) ::before {
content: '';
width: 28px;
height: 28px;
position: absolute;
background-image: url('@/assets/img/control/item_hover.png');
background-size: 100% 100%;
z-index: 6;
}
}
}
.dropdown-active {
animation: dropdown-enter 0.2s ease-in;
}
.dropdown-active {
animation: dropdown-leave 0.2s ease-in;
}
.dropdown-enter-active {
animation: dropdown-enter 0.2s ease-in;
}
.dropdown-leave-active {
animation: dropdown-leave 0.2s ease-in;
}
.dropdown-enter-active {
animation: dropdown-enter 0.2s ease-in;
}
.dropdown-leave-active {
animation: dropdown-leave 0.2s ease-in;
}
</style>
+15 -16
View File
@@ -1,18 +1,17 @@
import type { App } from "vue"
import Button from "./button/index.vue"
import Tabs from "./tabs/index.vue"
import Tab from "./tab/index.vue"
import Select from "./select/index.vue"
import Option from "./option/index.vue"
import Menu from "./menu/menu.vue"
import Checkbox from "./checkbox/index.vue"
import Collapse from "./collapse/index.vue"
import Dialog from "./dialog/index.vue"
import Tooltip from "./tooltip/index.vue"
import Iconselect from "./iconselect/index.vue"
import Selection from "./selection/index.vue"
import Slider from "./slider/index.vue"
import type { App } from 'vue'
import Button from './button/index.vue'
import Tabs from './tabs/index.vue'
import Tab from './tab/index.vue'
import Select from './select/index.vue'
import Option from './option/index.vue'
import Menu from './menu/menu.vue'
import Checkbox from './checkbox/index.vue'
import Collapse from './collapse/index.vue'
import Dialog from './dialog/index.vue'
import Tooltip from './tooltip/index.vue'
import Iconselect from './iconselect/index.vue'
import Selection from './selection/index.vue'
import Slider from './slider/index.vue'
export const components = {
Button,
@@ -27,7 +26,7 @@ export const components = {
Tooltip,
Iconselect,
Selection,
Slider
Slider,
}
export type CalcComponents = typeof components
+1 -1
View File
@@ -1,4 +1,4 @@
<script lang="tsx">
import { HiItem } from "hoci"
import { HiItem } from 'hoci'
export default HiItem
</script>
+51 -24
View File
@@ -1,23 +1,26 @@
<script lang="tsx">
import { computed, defineComponent, renderSlot } from "vue"
import { classPropType } from "@/components/hooks/types"
import { computed, defineComponent, renderSlot } from 'vue'
import { classPropType } from '@/components/hooks/types'
export default defineComponent({
props: {
loading: {
type: Boolean,
default: () => false
default: () => false,
},
class: {
type: classPropType,
default: () => ""
}
default: () => '',
},
},
setup(props, { slots }) {
const loading = computed(() => props.loading)
return () => {
return (
<div class="relative">
<div v-show={loading.value} class="flex h-full w-full z-99 absolute items-center justify-center">
<div
v-show={loading.value}
class="flex h-full w-full z-99 absolute items-center justify-center"
>
<svg
class="i-loading-spin"
version="1.1"
@@ -29,41 +32,65 @@ export default defineComponent({
xml:space="preserve"
>
<g>
<linearGradient id="right" gradientUnits="userSpaceOnUse" x1="150" y1="20" x2="150" y2="180">
<linearGradient
id="right"
gradientUnits="userSpaceOnUse"
x1="150"
y1="20"
x2="150"
y2="180"
>
<stop offset="0" style="stop-color:transparent" />
<stop offset="1" style="stop-color:currentColor" />
</linearGradient>
<path fill="url(#right)" d="M100,0v20c44.1,0,80,35.9,80,80c0,44.1-35.9,80-80,80v20c55.2,0,100-44.8,100-100S155.2,0,100,0z" />
<linearGradient id="left" gradientUnits="userSpaceOnUse" x1="50" y1="0" x2="50" y2="180">
<path
fill="url(#right)"
d="M100,0v20c44.1,0,80,35.9,80,80c0,44.1-35.9,80-80,80v20c55.2,0,100-44.8,100-100S155.2,0,100,0z"
/>
<linearGradient
id="left"
gradientUnits="userSpaceOnUse"
x1="50"
y1="0"
x2="50"
y2="180"
>
<stop offset="0" style="stop-color:currentColor" />
<stop offset="1" style="stop-color:transparent" />
</linearGradient>
<path fill="url(#left)" d="M20,100c0-44.1,35.9-80,80-80V0C44.8,0,0,44.8,0,100s44.8,100,100,100v-20C55.9,180,20,144.1,20,100z" />
<path
fill="url(#left)"
d="M20,100c0-44.1,35.9-80,80-80V0C44.8,0,0,44.8,0,100s44.8,100,100,100v-20C55.9,180,20,144.1,20,100z"
/>
<circle fill="currentColor" cx="100" cy="10" r="10" />
</g>
</svg>
</div>
<div class={[props.class].concat("duration-200").concat(loading.value ? "opacity-0" : "")}>{renderSlot(slots, "default", {}, () => [])}</div>
<div
class={[props.class].concat('duration-200').concat(loading.value ? 'opacity-0' : '')}
>
{renderSlot(slots, 'default', {}, () => [])}
</div>
</div>
)
}
}
},
})
</script>
<style lang="scss">
.i-loading-spin {
width: 32px;
height: 32px;
animation: spin infinite 1s linear;
}
.i-loading-spin {
width: 32px;
height: 32px;
animation: spin infinite 1s linear;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
+37 -35
View File
@@ -1,10 +1,10 @@
<script lang="tsx">
import { useToggle } from "@vueuse/core"
import { defineComponent, ref, renderSlot } from "vue"
import { useToggle } from '@vueuse/core'
import { defineComponent, ref, renderSlot } from 'vue'
export default defineComponent({
name: "CalcMenu",
props: ["label"],
name: 'CalcMenu',
props: ['label'],
setup(props, { slots }) {
const expand = ref(false)
@@ -24,48 +24,50 @@ export default defineComponent({
</div>
<div class="i-menu-label" v-trans={props.label}></div>
</div>
<div class={{ "i-menu-items": true, "expand": expand.value }}>{renderSlot(slots, "default")}</div>
<div class={{ 'i-menu-items': true, expand: expand.value }}>
{renderSlot(slots, 'default')}
</div>
</div>
)
}
}
},
})
</script>
<style lang="scss">
.i-menu {
min-width: 120px;
width: 200px;
transition: all 0.3s ease;
.i-menu {
min-width: 120px;
width: 200px;
transition: all 0.3s ease;
.i-menu-content {
width: 100%;
height: 16px;
line-height: 16px;
display: flex;
background-color: rgba(37, 37, 37, 0.4);
padding: 4px;
.i-menu-content {
width: 100%;
height: 16px;
line-height: 16px;
display: flex;
background-color: rgba(37, 37, 37, 0.4);
padding: 4px;
.i-menu-expand-icon {
width: 12px;
height: 12px;
color: rgb(255, 247, 148);
margin-right: 8px;
}
.i-menu-label {
width: auto;
// font-size: 12px;
}
.i-menu-expand-icon {
width: 12px;
height: 12px;
color: rgb(255, 247, 148);
margin-right: 8px;
}
.i-menu-items {
height: 0;
overflow: hidden;
&.expand {
height: 32px;
}
.i-menu-label {
width: auto;
// font-size: 12px;
}
}
.i-menu-items {
height: 0;
overflow: hidden;
&.expand {
height: 32px;
}
}
}
</style>
@@ -1,4 +1,4 @@
<script lang="tsx">
import { HiItem } from "hoci"
import { HiItem } from 'hoci'
export default HiItem
</script>
@@ -1,33 +1,35 @@
<script lang="tsx">
import { onKeyStroke } from "@vueuse/core"
import type { PropType } from "vue";
import { computed, defineComponent, onUnmounted } from "vue"
import { onKeyStroke } from '@vueuse/core'
import type { PropType } from 'vue'
import { computed, defineComponent, onUnmounted } from 'vue'
export default defineComponent({
props: {
page: {
type: Number,
default: () => 0
default: () => 0,
},
totalPage: {
type: Number,
default: () => 0
default: () => 0,
},
minPage: {
type: Number,
default: () => 1
default: () => 1,
},
parent: {
type: Object as PropType<HTMLElement>,
default: () => document
default: () => document,
},
disabled: {
type: Boolean,
default: false
}
default: false,
},
},
setup(props, { emit }) {
const totalPage = computed(() => (Number.isNaN(props.totalPage) ? props.minPage : props.totalPage))
const totalPage = computed(() =>
Number.isNaN(props.totalPage) ? props.minPage : props.totalPage,
)
const page = computed({
get() {
@@ -37,17 +39,16 @@ export default defineComponent({
return Math.max(Math.min(props.page, totalPage.value), props.minPage)
},
set(val) {
emit("update:page", val)
}
emit('update:page', val)
},
})
function prev(e: Event) {
e.preventDefault()
if (page.value > 1 && !props.disabled) {
page.value--
emit("prev")
emit("change", page.value)
emit('prev')
emit('change', page.value)
}
}
@@ -55,13 +56,13 @@ export default defineComponent({
e.preventDefault()
if (page.value < totalPage.value && !props.disabled) {
page.value = page.value + 1
emit("next")
emit("change", page.value)
emit('next')
emit('change', page.value)
}
}
const removePrev = onKeyStroke(["F1", "PageUp"], prev, { target: props.parent })
const removeNext = onKeyStroke(["F2", "PageDown"], next, { target: props.parent })
const removePrev = onKeyStroke(['F1', 'PageUp'], prev, { target: props.parent })
const removeNext = onKeyStroke(['F2', 'PageDown'], next, { target: props.parent })
onUnmounted(() => {
removePrev()
@@ -71,15 +72,23 @@ export default defineComponent({
return () => {
return (
<div class="flex space-x-1 h-10 w-full items-center justify-center">
<calc-button icon="prev" disabled={page.value <= 1 || props.disabled} onClick={prev}></calc-button>
<calc-button
icon="prev"
disabled={page.value <= 1 || props.disabled}
onClick={prev}
></calc-button>
<span class="text-center w-8">
{page.value}/{totalPage.value}
</span>
<calc-button icon="next" disabled={page.value >= totalPage.value || props.disabled} onClick={next}></calc-button>
<calc-button
icon="next"
disabled={page.value >= totalPage.value || props.disabled}
onClick={next}
></calc-button>
</div>
)
}
}
},
})
</script>
@@ -1,17 +1,17 @@
<script lang="tsx">
import { computed, defineComponent } from "vue"
import { computed, defineComponent } from 'vue'
export default defineComponent({
props: {
value: {
type: [Number, String],
default: 0
default: 0,
},
total: {
type: [Number, String],
default: 100
}
default: 100,
},
},
setup(props) {
const progress = computed(() => {
@@ -24,16 +24,28 @@ export default defineComponent({
return () => (
<div class="border-solid bg-hex-171717 border-hex-272521 border-2">
<div class="border-transparent border-solid border-2">
<div style={progress.value == 0 ? "width:0" : `width:${progress.value.toFixed(2)}%`} class="h-2 duration-200 calc-progress"></div>
<div
style={progress.value == 0 ? 'width:0' : `width:${progress.value.toFixed(2)}%`}
class="h-2 duration-200 calc-progress"
></div>
</div>
</div>
)
}
},
})
</script>
<style lang="scss" scoped>
.calc-progress {
background: linear-gradient(to bottom, #48d0eb 0%, #6cf4fe 15%, #4fbdfe 30%, #3080fe 40%, #1d5bfe 55%, #3283fd 70%, #000456 100%);
}
.calc-progress {
background: linear-gradient(
to bottom,
#48d0eb 0%,
#6cf4fe 15%,
#4fbdfe 30%,
#3080fe 40%,
#1d5bfe 55%,
#3283fd 70%,
#000456 100%
);
}
</style>
+176 -153
View File
@@ -1,57 +1,67 @@
<script lang="tsx">
import { selectionEmits, selectionProps, useSelectionList } from "hoci"
import type { CSSProperties, PropType, RendererElement } from "vue";
import { Teleport, Transition, computed, defineComponent, onDeactivated, ref, renderList, renderSlot, watch } from "vue"
import { selectionEmits, selectionProps, useSelectionList } from 'hoci'
import type { CSSProperties, PropType, RendererElement } from 'vue'
import {
Teleport,
Transition,
computed,
defineComponent,
onDeactivated,
ref,
renderList,
renderSlot,
watch,
} from 'vue'
import { onClickOutside, useVModel } from "@vueuse/core"
import { classPropType, labelPropType } from "@/components/hooks/types"
import { onClickOutside, useVModel } from '@vueuse/core'
import { classPropType, labelPropType } from '@/components/hooks/types'
export default defineComponent({
name: "ISelect",
name: 'ISelect',
props: {
...selectionProps,
disabled: {
type: Boolean,
default: false
default: false,
},
width: {
type: Number,
default: 120
default: 120,
},
emptyLabel: {
type: labelPropType
type: labelPropType,
},
highlight: {
type: String,
// warn remind
default: ""
default: '',
},
labelClass: {
type: String,
default: ""
default: '',
},
inputClass: {
type: classPropType,
default: ""
default: '',
},
appendTo: {
type: [Object, String] as PropType<string | RendererElement | null | undefined>,
default() {
return "body"
}
}
return 'body'
},
},
},
emits: [...selectionEmits],
setup(props, context) {
const modelValue = useVModel(props, "modelValue", context.emit, { passive: true })
const modelValue = useVModel(props, 'modelValue', context.emit, { passive: true })
const { renderItem } = useSelectionList(() => {
return {
...props,
modelValue: modelValue.value,
itemClass: "i-select-dropdown-item",
activeClass: "active",
unactiveClass: "unactive"
itemClass: 'i-select-dropdown-item',
activeClass: 'active',
unactiveClass: 'unactive',
}
}, context)
@@ -74,13 +84,13 @@ export default defineComponent({
return {
left: `${dropdownPosition.value.x}px`,
top: `${dropdownPosition.value.y}px`,
width: `${dropdownPosition.value.w}px`
width: `${dropdownPosition.value.w}px`,
}
} else {
return {
left: `${dropdownPosition.value.x}px`,
bottom: `${dropdownPosition.value.z}px`,
width: `${dropdownPosition.value.w}px`
width: `${dropdownPosition.value.w}px`,
}
}
})
@@ -93,32 +103,31 @@ export default defineComponent({
w: width,
x: left,
y: top + height + 2,
z: 0
z: 0,
}
} else {
dropdownPosition.value = {
w: width,
x: left,
z: window.innerHeight - top + 2,
y: 0
y: 0,
}
}
}
}
const appendToRef = computed(() => {
return props.appendTo || "body"
return props.appendTo || 'body'
})
onClickOutside(triggerRef, () => (isOpen.value = false))
window.addEventListener("resize", onResize)
window.addEventListener("scroll", onResize)
window.addEventListener('resize', onResize)
window.addEventListener('scroll', onResize)
onDeactivated(() => {
window.removeEventListener("resize", onResize)
window.removeEventListener("scroll", onResize)
window.removeEventListener('resize', onResize)
window.removeEventListener('scroll', onResize)
})
const { slots } = context
@@ -127,179 +136,193 @@ export default defineComponent({
const children = renderItem()
if (children) {
if (!Array.isArray(children)) {
return <span class={["i-select-input"].concat(props.inputClass)}>{children}</span>
return <span class={['i-select-input'].concat(props.inputClass)}>{children}</span>
} else if (children.length > 0) {
return (
<span class={["i-select-input space-x-1"].concat(props.inputClass)}>
{renderList(children, item => (
<span class={['i-select-input space-x-1'].concat(props.inputClass)}>
{renderList(children, (item) => (
<span>{item}</span>
))}
</span>
)
}
}
const emptyLabel = typeof props.emptyLabel == "function" ? props.emptyLabel() : props.emptyLabel
const emptyLabel =
typeof props.emptyLabel == 'function' ? props.emptyLabel() : props.emptyLabel
return <span class="w-full pl-2 i-select-input i-select-empty-label">{emptyLabel}</span>
}
return () => {
return (
<div class={["min-w-20 w-40 i-select "].concat(props.highlight)} onClick={collapse} ref={selectRef}>
<div
class={['min-w-20 w-40 i-select '].concat(props.highlight)}
onClick={collapse}
ref={selectRef}
>
<div
class={{
"i-select-trigger": true,
"disabled": props.disabled
'i-select-trigger': true,
disabled: props.disabled,
}}
ref={triggerRef}
>
{renderLabel()}
<calc-button disabled={props.disabled} icon="dropdown" class="cursor-pointer"></calc-button>
<calc-button
disabled={props.disabled}
icon="dropdown"
class="cursor-pointer"
></calc-button>
</div>
<Teleport to={appendToRef.value}>
<Transition name="dropdown" mode="out-in">
<div class="i-select-dropdown" style={dropdownStyle.value} v-show={isOpen.value} ref={dropdownRef}>
{renderSlot(slots, "default")}
<div
class="i-select-dropdown"
style={dropdownStyle.value}
v-show={isOpen.value}
ref={dropdownRef}
>
{renderSlot(slots, 'default')}
</div>
</Transition>
</Teleport>
</div>
)
}
}
},
})
</script>
<style lang="scss">
@use "sass:color";
$text-color: #937639;
.i-select {
min-width: 80px;
width: 160px;
user-select: none;
outline: none;
height: 16px;
min-height: 16px;
line-height: 100%;
// font-size: 12px;
color: $text-color;
background-color: rgba(0, 0, 0, 1);
border: 1px solid #5b472a;
border-radius: 2px;
@use 'sass:color';
$text-color: #937639;
.i-select {
min-width: 80px;
width: 160px;
user-select: none;
outline: none;
height: 16px;
min-height: 16px;
line-height: 100%;
// font-size: 12px;
color: $text-color;
background-color: rgba(0, 0, 0, 1);
border: 1px solid #5b472a;
border-radius: 2px;
padding: 0;
padding-right: 3px;
margin: 0;
display: block;
.i-select-trigger {
padding: 0;
padding-right: 3px;
margin: 0;
display: block;
display: flex;
height: 100%;
.i-select-trigger {
padding: 0;
display: flex;
height: 100%;
justify-content: space-between;
align-items: center;
justify-content: space-between;
align-items: center;
.i-select-input {
padding-left: 8px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
max-width: calc(100% - 20px);
}
.i-select-empty-label {
color: #5e5e5e;
}
&:hover {
.i-select-empty-label {
color: #757575;
}
}
&.disabled {
color: gray;
}
}
.i-select-trigger:hover {
opacity: 1;
}
}
.warn {
color: red !important;
border: 1px solid #aa8651;
}
.remind {
color: #32e432 !important;
border: 1px solid #aa8651;
}
.i-select-dropdown {
position: fixed;
max-height: 160px;
overflow-y: auto;
background: black;
// font-size: 12px;
z-index: 888;
$hoverColor: #002947;
$activeColor: color.scale($hoverColor, $lightness: 5%);
color: $text-color;
.i-select-dropdown-item {
// font-size: 12px;
height: 20px;
line-height: 20px;
margin: 0;
padding: 0 5px;
border: none;
outline: none;
appearance: none;
display: block;
.i-select-input {
padding-left: 8px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
max-width: calc(100% - 20px);
}
&.active {
// background-color: $activeColor;
color: #e9c556;
}
&.active::after {
content: "✓";
position: absolute;
right: 5px;
}
.i-select-empty-label {
color: #5e5e5e;
}
&:hover:not(.active) {
// font-size: 12px;
background-color: $hoverColor;
&:hover {
.i-select-empty-label {
color: #757575;
}
}
&.disabled {
color: gray;
}
}
.dropdown-active {
animation: dropdown-enter 0.2s ease-in;
.i-select-trigger:hover {
opacity: 1;
}
}
.dropdown-active {
animation: dropdown-leave 0.2s ease-in;
}
.warn {
color: red !important;
border: 1px solid #aa8651;
}
.dropdown-enter-active {
animation: dropdown-enter 0.2s ease-in;
}
.remind {
color: #32e432 !important;
border: 1px solid #aa8651;
}
.dropdown-leave-active {
animation: dropdown-leave 0.2s ease-in;
}
.i-select-dropdown {
position: fixed;
max-height: 160px;
overflow-y: auto;
background: black;
// font-size: 12px;
z-index: 888;
.dropdown-enter-active {
animation: dropdown-enter 0.2s ease-in;
}
$hoverColor: #002947;
$activeColor: color.scale($hoverColor, $lightness: 5%);
color: $text-color;
.dropdown-leave-active {
animation: dropdown-leave 0.2s ease-in;
.i-select-dropdown-item {
// font-size: 12px;
height: 20px;
line-height: 20px;
margin: 0;
padding: 0 5px;
border: none;
outline: none;
appearance: none;
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
&.active {
// background-color: $activeColor;
color: #e9c556;
}
&.active::after {
content: '✓';
position: absolute;
right: 5px;
}
&:hover:not(.active) {
// font-size: 12px;
background-color: $hoverColor;
}
}
}
.dropdown-active {
animation: dropdown-enter 0.2s ease-in;
}
.dropdown-active {
animation: dropdown-leave 0.2s ease-in;
}
.dropdown-enter-active {
animation: dropdown-enter 0.2s ease-in;
}
.dropdown-leave-active {
animation: dropdown-leave 0.2s ease-in;
}
.dropdown-enter-active {
animation: dropdown-enter 0.2s ease-in;
}
.dropdown-leave-active {
animation: dropdown-leave 0.2s ease-in;
}
</style>
@@ -1,5 +1,5 @@
<script lang="tsx">
import { HiSelection } from "hoci"
import { HiSelection } from 'hoci'
export default HiSelection
</script>
+2 -2
View File
@@ -1,3 +1,3 @@
import type { InjectionKey } from "vue";
import type { InjectionKey } from 'vue'
export const CHECKBOX_GROUP: InjectionKey<boolean> = Symbol("checkbox-group")
export const CHECKBOX_GROUP: InjectionKey<boolean> = Symbol('checkbox-group')
+38 -34
View File
@@ -3,37 +3,36 @@ import { defineComponent, ref, computed, shallowRef, watch } from 'vue'
import { useVModel, useDraggable, clamp } from '@vueuse/core'
export default defineComponent({
name: "ISlider",
name: 'ISlider',
props: {
modelValue: {
type: Number,
default: 0
default: 0,
},
min: {
type: Number,
default: 0
default: 0,
},
max: {
type: Number,
default: 100
default: 100,
},
step: {
type: Number,
default: 1
default: 1,
},
disabled: {
type: Boolean,
default: false
default: false,
},
showPercent: {
type: Boolean,
default: true
default: true,
},
formatValue: {
type: Function,
default: undefined
}
default: undefined,
},
},
emits: ['update:modelValue', 'change'],
setup(props, { emit }) {
@@ -47,17 +46,20 @@ export default defineComponent({
useDraggable(buttonRef, {
onMove(position) {
if (!runwayRef.value || props.disabled){
if (!runwayRef.value || props.disabled) {
return
}
const runwayRect = runwayRef.value?.getBoundingClientRect()
if (!runwayRect){
return
if (!runwayRect) {
return
}
// 使用runway的位置和宽度来计算百分比
const percentage = Math.max(0, Math.min(100, ((position.x - runwayRect.left) / runwayRect.width) * 100))
const percentage = Math.max(
0,
Math.min(100, ((position.x - runwayRect.left) / runwayRect.width) * 100),
)
const range = props.max - props.min
let value = (percentage * range) / 100 + props.min
@@ -70,20 +72,17 @@ export default defineComponent({
modelValue.value = value
emit('change', value)
}
},
})
return () => (
<div
class={[
'w-full h-8 relative cursor-pointer flex items-center gap-3 py-2',
{ 'cursor-not-allowed': props.disabled }
{ 'cursor-not-allowed': props.disabled },
]}
>
<div
class="flex-1 h-1 bg-[#4D524D] relative"
ref={runwayRef}
>
<div class="flex-1 h-1 bg-[#4D524D] relative" ref={runwayRef}>
<div
class="h-1 bg-[#8E6D28] absolute left-0 top-0"
style={{ width: `${percentage.value}%` }}
@@ -93,26 +92,32 @@ export default defineComponent({
style={{ left: `calc(${percentage.value}% - 0.75rem)` }}
ref={buttonRef}
>
<div class={[
'w-4 h-4 absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2',
'bg-gradient-to-b from-[#223768] to-[#122438]',
'border border-solid border-[#937639] rounded-full transition-all duration-200',
'shadow-[0_2px_4px_rgba(0,0,0,0.2)]',
{ 'bg-gradient-to-b from-[#353535] to-[#1c1c1c] border-[#414141]': props.disabled }
]}></div>
<div
class={[
'w-4 h-4 absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2',
'bg-gradient-to-b from-[#223768] to-[#122438]',
'border border-solid border-[#937639] rounded-full transition-all duration-200',
'shadow-[0_2px_4px_rgba(0,0,0,0.2)]',
{ 'bg-gradient-to-b from-[#353535] to-[#1c1c1c] border-[#414141]': props.disabled },
]}
></div>
</div>
</div>
{props.showPercent && (
<div class={[
'w-auto min-w-5 text-center text-sm font-medium',
props.disabled ? 'text-[#696969]' : 'text-[#e9c556]'
]}>
{props.formatValue ? props.formatValue(modelValue.value) : `${percentage.value.toFixed(0)}%`}
<div
class={[
'w-auto min-w-5 text-center text-sm font-medium',
props.disabled ? 'text-[#696969]' : 'text-[#e9c556]',
]}
>
{props.formatValue
? props.formatValue(modelValue.value)
: `${percentage.value.toFixed(0)}%`}
</div>
)}
</div>
)
}
},
})
</script>
@@ -204,4 +209,3 @@ export default defineComponent({
font-weight: 500;
}
</style>
+6 -6
View File
@@ -1,16 +1,16 @@
<script lang="tsx">
import { defineComponent, renderSlot } from "vue"
import { HiItem, selectionProps } from "hoci"
import { defineComponent, renderSlot } from 'vue'
import { HiItem, selectionProps } from 'hoci'
export default defineComponent({
name: "CalcTab",
name: 'CalcTab',
props: {
...selectionProps
...selectionProps,
},
setup(props, { slots }) {
return () => {
return <HiItem {...props}>{renderSlot(slots, "default")}</HiItem>
return <HiItem {...props}>{renderSlot(slots, 'default')}</HiItem>
}
}
},
})
</script>
@@ -1,5 +1,5 @@
export const AciveSymbol = Symbol("[i-tabs]active")
export const AciveSymbol = Symbol('[i-tabs]active')
export const AciveClassSymbol = Symbol("[i-tabs]active-class")
export const AciveClassSymbol = Symbol('[i-tabs]active-class')
export const ChangeActiveSymbol = Symbol("[i-tabs]change-active")
export const ChangeActiveSymbol = Symbol('[i-tabs]change-active')
+77 -72
View File
@@ -1,30 +1,30 @@
<script lang="tsx">
import { HiSelection, selectionProps } from "hoci"
import type { PropType } from "vue";
import { computed, defineComponent, renderSlot } from "vue"
import type { LocationQuery } from "vue-router";
import { parseQuery, useRouter } from "vue-router"
import type { BaseType } from "@/components/hooks/types"
import { HiSelection, selectionProps } from 'hoci'
import type { PropType } from 'vue'
import { computed, defineComponent, renderSlot } from 'vue'
import type { LocationQuery } from 'vue-router'
import { parseQuery, useRouter } from 'vue-router'
import type { BaseType } from '@/components/hooks/types'
export default defineComponent({
name: "CalcTabs",
name: 'CalcTabs',
props: {
...selectionProps,
vertical: {
type: Boolean
type: Boolean,
},
route: {
type: Boolean
type: Boolean,
},
query: {
type: [Object, String] as PropType<string | LocationQuery>
}
type: [Object, String] as PropType<string | LocationQuery>,
},
},
setup(props, { emit, slots }) {
const router = useRouter()
const routeQuery = computed(() => {
if (typeof props.query == "string") {
if (typeof props.query == 'string') {
return parseQuery(props.query)
}
return props.query ?? {}
@@ -43,81 +43,86 @@ export default defineComponent({
val = decodeURIComponent(val.toString())
router.push({ path: val, query: routeQuery.value })
}
emit("update:modelValue", val)
emit("change", val)
}
emit('update:modelValue', val)
emit('change', val)
},
})
return () => {
return (
<HiSelection v-model={modelValue.value} class={{ "i-tabs": true, "vertical": !!props.vertical }} activeClass="active" itemClass={["i-tab cursor-pointer"].concat(props.itemClass)}>
{renderSlot(slots, "default")}
<HiSelection
v-model={modelValue.value}
class={{ 'i-tabs': true, vertical: !!props.vertical }}
activeClass="active"
itemClass={['i-tab cursor-pointer'].concat(props.itemClass)}
>
{renderSlot(slots, 'default')}
</HiSelection>
)
}
}
},
})
</script>
<style lang="scss">
.i-tabs {
display: flex;
list-style: none;
align-items: flex-end;
border-bottom: 1px solid #4f4838;
width: 100%;
display: flex;
list-style: none;
align-items: flex-end;
border-bottom: 1px solid #4f4838;
width: 100%;
font-size: 14px;
font-size: 14px;
color: #c6b083;
margin: 0;
padding: 0;
a {
text-decoration: none;
color: #c6b083;
margin: 0;
padding: 0;
a {
text-decoration: none;
color: #c6b083;
&:visited {
color: currentColor;
}
}
&.vertical {
flex-direction: column;
.i-tab {
border-radius: 0;
}
}
.i-tab {
// font-size: 12px;
margin-left: 1px;
width: 120px;
height: 20px;
line-height: 20px;
text-align: center;
text-decoration: none;
&:visited {
color: currentColor;
}
border: #4f4838 1px solid;
border-bottom: none;
border-radius: 5px 5px 0 0;
background: linear-gradient(to top, rgba(6, 6, 6, 0.9), rgba(52, 52, 52, 0.9));
transition: all 0.4s ease-in-out;
&:hover {
background: linear-gradient(#4a4b4e, #1d1e22);
}
&.active {
background: linear-gradient(#574d38, #25221d);
border-image: url("@/assets/img/control/active_top.png") 1 fill stretch;
// color: #ffb400;
}
&:visited {
color: currentColor;
}
}
&.vertical {
flex-direction: column;
.i-tab {
border-radius: 0;
}
}
.i-tab {
// font-size: 12px;
margin-left: 1px;
width: 120px;
height: 20px;
line-height: 20px;
text-align: center;
text-decoration: none;
&:visited {
color: currentColor;
}
border: #4f4838 1px solid;
border-bottom: none;
border-radius: 5px 5px 0 0;
background: linear-gradient(to top, rgba(6, 6, 6, 0.9), rgba(52, 52, 52, 0.9));
transition: all 0.4s ease-in-out;
&:hover {
background: linear-gradient(#4a4b4e, #1d1e22);
}
&.active {
background: linear-gradient(#574d38, #25221d);
border-image: url('@/assets/img/control/active_top.png') 1 fill stretch;
// color: #ffb400;
}
}
}
</style>
+10 -7
View File
@@ -1,24 +1,27 @@
<script lang="tsx">
import { defineComponent } from "vue"
import { NTooltip } from "@/components/base"
import { defineComponent } from 'vue'
import { NTooltip } from '@/components/base'
import { tooltipProps } from "@/components/base/tooltip"
import { tooltipProps } from '@/components/base/tooltip'
export default defineComponent({
name: "ITooltip",
name: 'ITooltip',
components: { NTooltip },
props: {
...tooltipProps
...tooltipProps,
},
setup(props, { slots }) {
return () => {
return (
<n-tooltip {...props} pop-class="i-popper z-999 fixed top-0 left-0 bg-black bg-opacity-90 border-1.5 border-hex-272928 rounded-2px border-solid">
<n-tooltip
{...props}
pop-class="i-popper z-999 fixed top-0 left-0 bg-black bg-opacity-90 border-1.5 border-hex-272928 rounded-2px border-solid"
>
{slots}
</n-tooltip>
)
}
}
},
})
</script>
+42 -30
View File
@@ -1,55 +1,55 @@
<script lang="tsx">
import { HiSelection, selectionProps } from "hoci"
import { useVModel } from "@vueuse/core"
import type { PropType } from "vue";
import { computed, defineComponent, renderList, watch } from "vue"
import CalcTreeNode from "./node.vue"
import type { TreeNode } from "./types"
import { labelPropType, valuePropType } from "@/components/hooks/types"
import { HiSelection, selectionProps } from 'hoci'
import { useVModel } from '@vueuse/core'
import type { PropType } from 'vue'
import { computed, defineComponent, renderList, watch } from 'vue'
import CalcTreeNode from './node.vue'
import type { TreeNode } from './types'
import { labelPropType, valuePropType } from '@/components/hooks/types'
const Tree = defineComponent({
name: "CalcTree",
name: 'CalcTree',
props: {
...selectionProps,
modelValue: {
type: valuePropType,
default: () => null
default: () => null,
},
activeItem: {
type: Object as PropType<TreeNode>,
default: () => null
default: () => null,
},
idKey: {
type: String,
default: "id"
default: 'id',
},
label: {
type: labelPropType
type: labelPropType,
},
data: {
type: Array as PropType<TreeNode[]>,
default: () => []
default: () => [],
},
depth: {
type: [Number, String] as PropType<number>,
default: () => 0
default: () => 0,
},
depthStyle: {
type: Function as PropType<(depth: number) => string>
}
type: Function as PropType<(depth: number) => string>,
},
},
setup(props, { emit }) {
const modelValue = useVModel(props, "modelValue")
const modelValue = useVModel(props, 'modelValue')
function onSelect(item?: TreeNode) {
if (item) {
emit("select", item)
emit('select', item)
item.onSelect?.()
}
}
function loadItem(total: TreeNode[], items: TreeNode[]) {
items?.forEach(item => {
items?.forEach((item) => {
if (item.children) {
loadItem(total, item.children)
}
@@ -62,31 +62,43 @@ const Tree = defineComponent({
return loadItem([], props.data)
})
watch(modelValue, val => {
watch(modelValue, (val) => {
val = val ?? props.defaultValue
const item = children.value.find(item => item.value === val)
const item = children.value.find((item) => item.value === val)
onSelect(item)
})
return () => {
return (
<HiSelection {...props} activeClass="i-tree-node-active" v-model={modelValue.value} class="i-tree">
{renderList(props.data, item => {
return <CalcTreeNode {...item} onSelect={onSelect} depth-style={props.depthStyle} depth={props.depth} />
<HiSelection
{...props}
activeClass="i-tree-node-active"
v-model={modelValue.value}
class="i-tree"
>
{renderList(props.data, (item) => {
return (
<CalcTreeNode
{...item}
onSelect={onSelect}
depth-style={props.depthStyle}
depth={props.depth}
/>
)
})}
</HiSelection>
)
}
}
},
})
export default Tree
</script>
<style>
.i-tree {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.i-tree {
display: flex;
flex-direction: column;
align-items: flex-start;
}
</style>
+94 -85
View File
@@ -1,34 +1,34 @@
<script lang="tsx">
import { HiItem } from "hoci"
import type { PropType } from "vue";
import { Transition, computed, defineComponent, ref, renderList } from "vue"
import type { TreeNode } from "./types"
import { HiItem } from 'hoci'
import type { PropType } from 'vue'
import { Transition, computed, defineComponent, ref, renderList } from 'vue'
import type { TreeNode } from './types'
const NTreeNode = defineComponent({
props: {
label: {
type: String,
default: ""
default: '',
},
value: {
type: [String, Number, Boolean, Object, Array] as PropType<any>,
default: () => null
default: () => null,
},
children: {
type: Array as PropType<TreeNode[]>,
default: () => []
default: () => [],
},
depth: {
type: [String, Number] as PropType<number>,
default: 0
default: 0,
},
depthStyle: {
type: Function as PropType<(depth: number) => string>
type: Function as PropType<(depth: number) => string>,
},
depthOffset: {
type: String,
default: "12px"
}
default: '12px',
},
},
setup(props, { emit }) {
const isOpen = ref(false)
@@ -39,7 +39,7 @@ const NTreeNode = defineComponent({
}
function onSelect(item: TreeNode) {
return emit("select", item)
return emit('select', item)
}
const depthStyle = computed(() => {
@@ -51,109 +51,118 @@ const NTreeNode = defineComponent({
return () => (
<div class="w-full i-tree-item relative">
<HiItem label={props.label} value={props.value} style={depthStyle.value} class="flex i-tree-item-label items-center" onClick={open}>
<HiItem
label={props.label}
value={props.value}
style={depthStyle.value}
class="flex i-tree-item-label items-center"
onClick={open}
>
<div class="top-0 right-0 bottom-0 left-0 absolute i-tree-item-active-mask"></div>
<div class="top-0 right-0 bottom-0 left-0 absolute i-tree-item-mask"></div>
<calc-button v-show={props.children?.length} icon={isOpen.value ? "collapse-up" : "collapse-down"}></calc-button>
<calc-button
v-show={props.children?.length}
icon={isOpen.value ? 'collapse-up' : 'collapse-down'}
></calc-button>
<div class="pl-1">{props.label}</div>
</HiItem>
<Transition name="dropdown" mode="out-in">
<div v-show={isOpen.value}>
{renderList(props.children, item => {
{renderList(props.children, (item) => {
return <NTreeNode onSelect={onSelect} {...item} depth={Number(props.depth) + 1} />
})}
</div>
</Transition>
</div>
)
}
},
})
export default NTreeNode
</script>
<style lang="scss">
.i-tree-item {
display: flex;
flex-direction: column;
cursor: pointer;
.i-tree-item {
display: flex;
flex-direction: column;
cursor: pointer;
border: 1px solid transparent;
background-color: black;
}
border: 1px solid transparent;
background-color: black;
}
.i-tree-item-label {
flex: 1;
height: 16px;
min-height: 16px;
color: #ddc593;
position: relative;
&:hover {
.i-tree-item-mask {
background: url("./img/item_hover.png") no-repeat;
background-size: 100% 100%;
}
}
&.i-tree-node-active {
.i-tree-item-active-mask {
background: url("./img/item_active.png") no-repeat;
background-size: 100% 100%;
}
.i-tree-item-label {
flex: 1;
height: 16px;
min-height: 16px;
color: #ddc593;
position: relative;
&:hover {
.i-tree-item-mask {
background: url('./img/item_hover.png') no-repeat;
background-size: 100% 100%;
}
}
.icon-collapse-up {
background-image: url("./img/collapse_up.png");
background-size: 100% 100%;
background-repeat: no-repeat;
background-position: center;
min-width: 13px !important;
width: 13px !important;
height: 13px !important;
&.i-tree-node-active {
.i-tree-item-active-mask {
background: url('./img/item_active.png') no-repeat;
background-size: 100% 100%;
}
}
}
.icon-collapse-down {
background-image: url("./img/collapse_down.png");
background-size: 100% 100%;
background-repeat: no-repeat;
background-position: center;
min-width: 13px !important;
width: 13px !important;
height: 13px !important;
}
.icon-collapse-up {
background-image: url('./img/collapse_up.png');
background-size: 100% 100%;
background-repeat: no-repeat;
background-position: center;
min-width: 13px !important;
width: 13px !important;
height: 13px !important;
}
.i-tree-dropdown {
width: 100%;
max-height: 0;
overflow: hidden;
background-color: #fff;
border-radius: 0.5rem;
box-shadow: 0 0 0.5rem 0 rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.icon-collapse-down {
background-image: url('./img/collapse_down.png');
background-size: 100% 100%;
background-repeat: no-repeat;
background-position: center;
min-width: 13px !important;
width: 13px !important;
height: 13px !important;
}
.dropdown-active {
animation: dropdown-enter 0.2s ease-in;
}
.i-tree-dropdown {
width: 100%;
max-height: 0;
overflow: hidden;
background-color: #fff;
border-radius: 0.5rem;
box-shadow: 0 0 0.5rem 0 rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.dropdown-active {
animation: dropdown-leave 0.2s ease-in;
}
.dropdown-active {
animation: dropdown-enter 0.2s ease-in;
}
.dropdown-enter-active {
animation: dropdown-enter 0.2s ease-in;
}
.dropdown-active {
animation: dropdown-leave 0.2s ease-in;
}
.dropdown-leave-active {
animation: dropdown-leave 0.2s ease-in;
}
.dropdown-enter-active {
animation: dropdown-enter 0.2s ease-in;
}
.dropdown-enter-active {
animation: dropdown-enter 0.2s ease-in;
}
.dropdown-leave-active {
animation: dropdown-leave 0.2s ease-in;
}
.dropdown-leave-active {
animation: dropdown-leave 0.2s ease-in;
}
.dropdown-enter-active {
animation: dropdown-enter 0.2s ease-in;
}
.dropdown-leave-active {
animation: dropdown-leave 0.2s ease-in;
}
</style>
+10 -10
View File
@@ -1,10 +1,10 @@
export interface TreeNode {
label: string
id?: string | number
value?: any
children?: TreeNode[]
onSelect?: () => void
[key: string]: any
}
export interface TreeData {}
export interface TreeNode {
label: string
id?: string | number
value?: any
children?: TreeNode[]
onSelect?: () => void
[key: string]: any
}
export interface TreeData {}