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
View File
@@ -543,6 +543,7 @@ class Character(CharacterProperty):
return res return res
def calc_init(self, setInfo: dict[str, dict]): def calc_init(self, setInfo: dict[str, dict]):
self.equ_options = setInfo.get('options', {})
# 打造信息导入 # 打造信息导入
self.SetDetail(setInfo) self.SetDetail(setInfo)
# 技能等级设置 # 技能等级设置
+1 -1
View File
@@ -10,7 +10,7 @@ from . import suit
from .register import execture from .register import execture
options = [ options = [
{'id': 1, 'name': '龙战八荒,', 'options': ['BUFF OFF', 'BUFF ON', '期望']}, {'id': 1, 'name': '龙战八荒', 'options': ['BUFF OFF', 'BUFF ON', '期望']},
{'id': 2, 'name': '混沌净化', 'options': ['净化', '堕落']}, {'id': 2, 'name': '混沌净化', 'options': ['净化', '堕落']},
{'id': 3, 'name': '群猎美学', 'options': ['单刷', '组队']}, {'id': 3, 'name': '群猎美学', 'options': ['单刷', '组队']},
{'id': 4, 'name': '魔攻核', 'options': ['普通', '追击']}, {'id': 4, 'name': '魔攻核', 'options': ['普通', '追击']},
-2
View File
@@ -15,8 +15,6 @@
"format": "prettier --write src/" "format": "prettier --write src/"
}, },
"dependencies": { "dependencies": {
"@iconify/vue": "^4.3.0",
"@radix-ui/colors": "^3.0.0",
"@vueuse/core": "^12.8.2", "@vueuse/core": "^12.8.2",
"axios": "^1.8.2", "axios": "^1.8.2",
"hoci": "^0.6.0", "hoci": "^0.6.0",
+1 -1
View File
@@ -6,7 +6,7 @@ export default defineRequest((request) => {
async calc(config: IConfig) { async calc(config: IConfig) {
const res = await request.post<IResult>('/calc', config) const res = await request.post<IResult>('/calc', config)
return res.data return res.data
} },
} }
}) })
// Compare this snippet from frontend/src/api/common/index.ts: // Compare this snippet from frontend/src/api/common/index.ts:
+15 -16
View File
@@ -1,12 +1,11 @@
export interface IResult { export interface IResult {
uuid: string uuid: string
skills: IResultSkill[] skills: IResultSkill[]
info:IResultUserInfo[] info: IResultUserInfo[]
suits:IResultSuit[] suits: IResultSuit[]
buffer:boolean buffer: boolean
} }
export interface IResultSkill { export interface IResultSkill {
name: string name: string
icon: string icon: string
@@ -16,24 +15,24 @@ export interface IResultSkill {
cd: number cd: number
damage: number damage: number
mode: string mode: string
buff:number[] buff: number[]
learnLv: number learnLv: number
} }
export interface IResultUserInfo { export interface IResultUserInfo {
name:string name: string
value:string|number value: string | number
} }
export interface IResultSuit{ export interface IResultSuit {
id:number id: number
count:number count: number
imageUrl:string imageUrl: string
level:number level: number
name:string name: string
point:number point: number
rarity:string rarity: string
value:string value: string
} }
export interface IResultSkillCount { export interface IResultSkillCount {
+1 -1
View File
@@ -27,7 +27,7 @@ if (import.meta.env.MODE == 'web' || import.meta.env.MODE == 'show') {
baseURL = 'https://api.dnftools.com/' baseURL = 'https://api.dnftools.com/'
} }
if(import.meta.env.MODE == 'test') { if (import.meta.env.MODE == 'test') {
baseURL = 'https://testapi.dnftools.com/' baseURL = 'https://testapi.dnftools.com/'
} }
+30 -29
View File
@@ -35,16 +35,17 @@ export interface ICharacterInfo {
skills: ISkill[] skills: ISkill[]
suits: ISuit[] suits: ISuit[]
avatar: Record<string, IAvatarInfo[]> avatar: Record<string, IAvatarInfo[]>
jades:IJade[] jades: IJade[]
sundry:Record<string, ISundry> sundry: Record<string, ISundry>
options: { id: number; name: string; options: string[] }[]
} }
export interface ISundry { export interface ISundry {
id:number, id: number
options:{ options: {
name:string, name: string
value:number value: number
}[], }[]
} }
export interface IEquipment { export interface IEquipment {
@@ -56,19 +57,19 @@ export interface IEquipment {
categorize: string categorize: string
suit: string[] suit: string[]
rarity: string rarity: string
max_adaptation:number max_adaptation: number
STR:number[] STR: number[]
INT:number[] INT: number[]
Vitality:number[] Vitality: number[]
Spirit:number[] Spirit: number[]
AtkP:number[] AtkP: number[]
AtkM:number[] AtkM: number[]
AtkI:number[] AtkI: number[]
SkillAttack:number[] SkillAttack: number[]
Attack:number[] Attack: number[]
Buffer:number[] Buffer: number[]
detail: string detail: string
bufferDetail:string bufferDetail: string
} }
export interface ISkill { export interface ISkill {
@@ -77,7 +78,7 @@ export interface ISkill {
id: number id: number
learnLv: number learnLv: number
position: number position: number
line:number line: number
type: string type: string
maxLearnLv: number maxLearnLv: number
maxLv: number maxLv: number
@@ -90,12 +91,12 @@ export interface ISuit {
imageUrl: string imageUrl: string
suitId: number suitId: number
suitName: string suitName: string
point:number point: number
rarity: string rarity: string
level: number level: number
SkillAttack: number SkillAttack: number
Buffer:number Buffer: number
value:string value: string
} }
export interface IEnchantingInfo { export interface IEnchantingInfo {
@@ -118,10 +119,10 @@ export interface IAvatarInfo {
} }
export interface IJade { export interface IJade {
name:string name: string
id:number id: number
max:number max: number
min:number min: number
pre:number pre: number
unit:string unit: string
} }
+1 -1
View File
@@ -1 +1 @@
export { default as NTooltip } from "./tooltip" export { default as NTooltip } from './tooltip'
+55 -32
View File
@@ -1,42 +1,52 @@
import { syncRefs, useVModel } from "@vueuse/core" import { syncRefs, useVModel } from '@vueuse/core'
import type { CSSProperties, PropType } from "vue"; import type { CSSProperties, PropType } from 'vue'
import { Teleport, Transition, computed, defineComponent, nextTick, reactive, ref, renderSlot, watch } from "vue" import {
import { provideShow } from "../hooks/show" Teleport,
Transition,
computed,
defineComponent,
nextTick,
reactive,
ref,
renderSlot,
watch,
} from 'vue'
import { provideShow } from '../hooks/show'
export const tooltipProps = { export const tooltipProps = {
popClass: { popClass: {
type: String type: String,
}, },
position: { position: {
type: String as PropType<"bottom" | "top" | "left" | "right" | "auto">, type: String as PropType<'bottom' | 'top' | 'left' | 'right' | 'auto'>,
default: () => "auto" default: () => 'auto',
}, },
offset: { offset: {
type: Number, type: Number,
default: () => 8 default: () => 8,
}, },
lazy: { lazy: {
type: Boolean, type: Boolean,
default: () => false default: () => false,
}, },
visible: { visible: {
type: Boolean, type: Boolean,
default: () => false default: () => false,
}, },
disabled: { disabled: {
type: Boolean, type: Boolean,
default: () => false default: () => false,
} },
} }
export default defineComponent({ export default defineComponent({
name: "ITooltip", name: 'ITooltip',
props: tooltipProps, props: tooltipProps,
emits: ["change", "update:visible"], emits: ['change', 'update:visible'],
setup(props, { slots, emit }) { setup(props, { slots, emit }) {
const isOpen = ref(props.visible) const isOpen = ref(props.visible)
syncRefs(useVModel(props, "visible"), isOpen) syncRefs(useVModel(props, 'visible'), isOpen)
provideShow(isOpen) provideShow(isOpen)
@@ -47,13 +57,19 @@ export default defineComponent({
const dropdownPosition = reactive({ x: 0, y: 0 }) const dropdownPosition = reactive({ x: 0, y: 0 })
const isShow = computed(() => isOpen.value && triggerRef.value && popupRef.value && dropdownPosition.x + dropdownPosition.y > 0) const isShow = computed(
() =>
isOpen.value &&
triggerRef.value &&
popupRef.value &&
dropdownPosition.x + dropdownPosition.y > 0,
)
const dropdownStyle = computed<CSSProperties>(() => { const dropdownStyle = computed<CSSProperties>(() => {
return { return {
left: `${dropdownPosition.x}px`, left: `${dropdownPosition.x}px`,
top: `${dropdownPosition.y}px`, top: `${dropdownPosition.y}px`,
visibility: isShow.value ? "visible" : "hidden" visibility: isShow.value ? 'visible' : 'hidden',
} }
}) })
@@ -66,9 +82,9 @@ export default defineComponent({
timer = setTimeout( timer = setTimeout(
() => { () => {
isOpen.value = true isOpen.value = true
emit("change", true) emit('change', true)
}, },
props.lazy ? 800 : 100 props.lazy ? 800 : 100,
) )
} }
@@ -78,7 +94,7 @@ export default defineComponent({
} }
clearTimeout(timer) clearTimeout(timer)
isOpen.value = false isOpen.value = false
emit("change", false) emit('change', false)
} }
function resize() { function resize() {
@@ -96,34 +112,37 @@ export default defineComponent({
const offset = props.offset const offset = props.offset
switch (props.position) { switch (props.position) {
case "auto": case 'auto':
case "bottom": case 'bottom':
x = left + width / 2 x = left + width / 2
y = top + height + offset y = top + height + offset
break break
case "top": case 'top':
x = left x = left
y = top - pHeight - offset y = top - pHeight - offset
break break
case "left": case 'left':
x = left - pWidth - offset x = left - pWidth - offset
y = top y = top
break break
case "right": case 'right':
x = left + width + offset x = left + width + offset
y = top y = top
break break
} }
if (props.position == "auto") { if (props.position == 'auto') {
x = window.innerWidth - x > 500 ? x + 20 : x - 20 - 310 x = window.innerWidth - x > 500 ? x + 20 : x - 20 - 310
y = window.innerHeight - y > popup.offsetHeight + 50 ? y : window.innerHeight - (popup.offsetHeight + 50) y =
window.innerHeight - y > popup.offsetHeight + 50
? y
: window.innerHeight - (popup.offsetHeight + 50)
} }
dropdownPosition.x = x dropdownPosition.x = x
dropdownPosition.y = y dropdownPosition.y = y
} }
} }
watch(isOpen, val => { watch(isOpen, (val) => {
if (val) { if (val) {
mounted.value = true mounted.value = true
} }
@@ -133,12 +152,16 @@ export default defineComponent({
return () => { return () => {
return ( return (
<div {...{ onMouseout, onMouseover }} ref={triggerRef}> <div {...{ onMouseout, onMouseover }} ref={triggerRef}>
{renderSlot(slots, "default")} {renderSlot(slots, 'default')}
<Teleport to="body"> <Teleport to="body">
<Transition name="dropdown" mode="out-in"> <Transition name="dropdown" mode="out-in">
{mounted.value && !props.disabled && ( {mounted.value && !props.disabled && (
<div class={["i-popper z-999", props.popClass]} style={dropdownStyle.value} ref={popupRef}> <div
{renderSlot(slots, "popper")} class={['i-popper z-999', props.popClass]}
style={dropdownStyle.value}
ref={popupRef}
>
{renderSlot(slots, 'popper')}
</div> </div>
)} )}
</Transition> </Transition>
@@ -146,5 +169,5 @@ export default defineComponent({
</div> </div>
) )
} }
} },
}) })
@@ -1,101 +1,101 @@
<script lang="tsx"> <script lang="tsx">
import { onKeyStroke, syncRef, useVModel } from "@vueuse/core" import { onKeyStroke, syncRef, useVModel } from '@vueuse/core'
import type { PropType, RendererElement } from "vue" import type { PropType, RendererElement } from 'vue'
import { defineComponent, ref, renderSlot } from "vue" import { defineComponent, ref, renderSlot } from 'vue'
import CalcDialog from "@/components/calc/dialog/index.vue" import CalcDialog from '@/components/calc/dialog/index.vue'
import CalcButton from "@/components/calc/button/index.vue" import CalcButton from '@/components/calc/button/index.vue'
import type { JSX } from "vue/jsx-runtime" import type { JSX } from 'vue/jsx-runtime'
export type ActionDialogResult = "ok" | "reject" | "cancel" | "none" export type ActionDialogResult = 'ok' | 'reject' | 'cancel' | 'none'
export default defineComponent({ export default defineComponent({
name: "IActionDialog", name: 'IActionDialog',
props: { props: {
visible: { visible: {
type: Boolean, type: Boolean,
default: () => false default: () => false,
}, },
class: { class: {
type: String type: String,
}, },
mask: { mask: {
type: Boolean, type: Boolean,
default: () => false default: () => false,
}, },
// 模态框 // 模态框
modal: { modal: {
type: Boolean, type: Boolean,
default: () => false default: () => false,
}, },
header: { header: {
type: [String, Boolean] as PropType<string | boolean>, type: [String, Boolean] as PropType<string | boolean>,
default: () => true default: () => true,
}, },
drag: { drag: {
type: String as PropType<"header" | "all" | "none">, type: String as PropType<'header' | 'all' | 'none'>,
default: () => "header" default: () => 'header',
}, },
okButton: { okButton: {
type: [String, Boolean], type: [String, Boolean],
default: "确定" default: '确定',
}, },
rejectButton: { rejectButton: {
type: [String, Boolean], type: [String, Boolean],
default: false default: false,
}, },
cancelButton: { cancelButton: {
type: [String, Boolean], type: [String, Boolean],
default: "取消" default: '取消',
}, },
closeOnYes: { closeOnYes: {
type: Boolean, type: Boolean,
default: () => true default: () => true,
}, },
closeOnCancel: { closeOnCancel: {
type: Boolean, type: Boolean,
default: () => true default: () => true,
}, },
closeOnReject: { closeOnReject: {
type: Boolean, type: Boolean,
default: () => true default: () => true,
}, },
closeOnEsc: { closeOnEsc: {
type: Boolean, type: Boolean,
default: () => true default: () => true,
}, },
cache: { cache: {
// 维持窗口状态 // 维持窗口状态
type: Boolean, type: Boolean,
default: () => true default: () => true,
}, },
defaultStatus: { defaultStatus: {
type: String as PropType<ActionDialogResult>, type: String as PropType<ActionDialogResult>,
default: () => "cancel" default: () => 'cancel',
}, },
appendTo: { appendTo: {
type: [Object, String] as PropType<string | RendererElement | null | undefined>, type: [Object, String] as PropType<string | RendererElement | null | undefined>,
default: "body" default: 'body',
}, },
scale: { scale: {
type: Number, 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 }) { setup(props, { emit, slots }) {
const visible = ref(props.visible) 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) const result = ref<ActionDialogResult>(props.defaultStatus)
function close() { function close() {
visible.value = false visible.value = false
emit("close", result.value) emit('close', result.value)
} }
onKeyStroke("ESC", (e: Event) => { onKeyStroke('ESC', (e: Event) => {
if (visible.value && props.closeOnEsc) { if (visible.value && props.closeOnEsc) {
e.preventDefault() e.preventDefault()
close() close()
@@ -103,21 +103,21 @@ export default defineComponent({
}) })
function onOkClick() { function onOkClick() {
result.value = "ok" result.value = 'ok'
if (props.closeOnYes) { if (props.closeOnYes) {
close() close()
} }
} }
function onRejectClick() { function onRejectClick() {
result.value = "reject" result.value = 'reject'
if (props.closeOnReject) { if (props.closeOnReject) {
close() close()
} }
} }
function onCancelClick() { function onCancelClick() {
result.value = "cancel" result.value = 'cancel'
if (props.closeOnCancel) { if (props.closeOnCancel) {
close() close()
} }
@@ -133,28 +133,28 @@ export default defineComponent({
if (props.okButton) { if (props.okButton) {
buttons.push( buttons.push(
<CalcButton class="mx-2px" type="primary" onClick={onOkClick}> <CalcButton class="mx-2px" type="primary" onClick={onOkClick}>
{props.okButton === true ? "确定" : props.okButton} {props.okButton === true ? '确定' : props.okButton}
</CalcButton> </CalcButton>,
) )
} }
if (props.rejectButton) { if (props.rejectButton) {
buttons.push( buttons.push(
<CalcButton class="mx-2px" type="primary" onClick={onRejectClick}> <CalcButton class="mx-2px" type="primary" onClick={onRejectClick}>
{props.rejectButton === true ? "拒绝" : props.rejectButton} {props.rejectButton === true ? '拒绝' : props.rejectButton}
</CalcButton> </CalcButton>,
) )
} }
if (props.cancelButton) { if (props.cancelButton) {
buttons.push( buttons.push(
<CalcButton class="mx-2px" onClick={onCancelClick}> <CalcButton class="mx-2px" onClick={onCancelClick}>
{props.cancelButton === true ? "取消" : props.cancelButton} {props.cancelButton === true ? '取消' : props.cancelButton}
</CalcButton> </CalcButton>,
) )
} }
return <div class="flex pb-2 items-center justify-center">{buttons}</div> return <div class="flex pb-2 items-center justify-center">{buttons}</div>
} }
return renderSlot(slots, "action") return renderSlot(slots, 'action')
} }
return () => { return () => {
@@ -165,43 +165,43 @@ export default defineComponent({
drag={props.drag} drag={props.drag}
mask={props.mask} mask={props.mask}
modal={props.modal} modal={props.modal}
header={props.header == true ? "提示" : props.header} header={props.header == true ? '提示' : props.header}
appendTo={props.appendTo} appendTo={props.appendTo}
scale={props.scale} 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()} {renderAction()}
</CalcDialog> </CalcDialog>
) )
} }
} },
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.i-button { .i-button {
text-shadow: none; text-shadow: none;
color: #a89679; color: #a89679;
background: linear-gradient(#12396b, #081f3c); background: linear-gradient(#12396b, #081f3c);
border: 1px solid #554a3d; border: 1px solid #554a3d;
&:hover { &:hover {
color: #fee97d; color: #fee97d;
background: linear-gradient(#2d4e9f, #153051); background: linear-gradient(#2d4e9f, #153051);
// border: 1px solid #e3c18a; // border: 1px solid #e3c18a;
color: #d4d4b6; color: #d4d4b6;
background: linear-gradient(#195cab, #0b3260); background: linear-gradient(#195cab, #0b3260);
border: 1px solid #867760; 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;
}
} }
&:active {
color: #e9c556;
background: linear-gradient(#2d4e9f, #122438);
border: 1px solid #e3c18a;
color: #d4d4b6;
background: linear-gradient(#195cab, #0b3260);
border: 1px solid #918168;
}
}
</style> </style>
@@ -1,31 +1,40 @@
<script lang="tsx"> <script lang="tsx">
import { debouncedWatch, onClickOutside, useVModel } from "@vueuse/core" import { debouncedWatch, onClickOutside, useVModel } from '@vueuse/core'
import type { CSSProperties, PropType } from "vue"; import type { CSSProperties, PropType } from 'vue'
import { Teleport, Transition, computed, defineComponent, onDeactivated, ref, renderList, watch } 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({ export default defineComponent({
props: { props: {
modelValue: { modelValue: {
type: [String, Number] as PropType<string | number>, type: [String, Number] as PropType<string | number>,
default: () => "" default: () => '',
}, },
placeholder: { placeholder: {
type: String, type: String,
default: "" default: '',
}, },
handleChange: { handleChange: {
type: Function as PropType<HandleChangeFunction>, type: Function as PropType<HandleChangeFunction>,
default: () => () => [] default: () => () => [],
}, },
delay: { delay: {
type: Number, type: Number,
default: 300 default: 300,
} },
}, },
setup(props, { emit }) { setup(props, { emit }) {
const modelValue = useVModel(props, "modelValue") const modelValue = useVModel(props, 'modelValue')
const isOpen = ref(false) const isOpen = ref(false)
const triggerRef = ref<HTMLElement>() const triggerRef = ref<HTMLElement>()
@@ -39,7 +48,7 @@ export default defineComponent({
return { return {
left: `${dropdownPosition.value.x}px`, left: `${dropdownPosition.value.x}px`,
top: `${dropdownPosition.value.y}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 = { dropdownPosition.value = {
w: width, w: width,
x: left, x: left,
y: top + height + 2 y: top + height + 2,
} }
} }
} }
onClickOutside(triggerRef, () => (isOpen.value = false)) onClickOutside(triggerRef, () => (isOpen.value = false))
window.addEventListener("resize", onResize) window.addEventListener('resize', onResize)
window.addEventListener("scroll", onResize) window.addEventListener('scroll', onResize)
onDeactivated(() => { onDeactivated(() => {
window.removeEventListener("resize", onResize) window.removeEventListener('resize', onResize)
window.removeEventListener("scroll", onResize) window.removeEventListener('scroll', onResize)
}) })
function selectItem(item: string) { function selectItem(item: string) {
@@ -74,17 +83,21 @@ export default defineComponent({
const droplist = ref<string[]>([]) const droplist = ref<string[]>([])
debouncedWatch(modelValue, async value => (droplist.value = await props.handleChange(value)), { debouncedWatch(
debounce: delay modelValue,
}) async (value) => (droplist.value = await props.handleChange(value)),
{
debounce: delay,
},
)
function handleInput() { function handleInput() {
isOpen.value = droplist.value.length > 0 isOpen.value = droplist.value.length > 0
} }
function handleKeyPress(event: KeyboardEvent) { function handleKeyPress(event: KeyboardEvent) {
if (event.code == "Enter") { if (event.code == 'Enter') {
emit("enter", modelValue.value) emit('enter', modelValue.value)
} }
} }
@@ -92,12 +105,24 @@ export default defineComponent({
return ( return (
<div class="i-autocomplete"> <div class="i-autocomplete">
<div class="i-autocomplete-trigger" ref={triggerRef}> <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> </div>
<Teleport to="body"> <Teleport to="body">
<Transition name="dropdown" mode="out-in"> <Transition name="dropdown" mode="out-in">
<div class="i-autocomplete-dropdown" style={dropdownStyle.value} v-show={isOpen.value}> <div
{renderList(droplist.value, item => ( class="i-autocomplete-dropdown"
style={dropdownStyle.value}
v-show={isOpen.value}
>
{renderList(droplist.value, (item) => (
<div class="i-autocomplete-dropdown-item" onClick={selectItem(item)}> <div class="i-autocomplete-dropdown-item" onClick={selectItem(item)}>
{item} {item}
</div> </div>
@@ -108,115 +133,115 @@ export default defineComponent({
</div> </div>
) )
} }
} },
}) })
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
$text-color: #937639; $text-color: #937639;
.i-autocomplete { .i-autocomplete {
min-width: 80px; min-width: 80px;
width: 160px; width: 160px;
user-select: none; user-select: none;
outline: none; outline: none;
height: 24px; height: 24px;
min-height: 16px; min-height: 16px;
line-height: 100%; line-height: 100%;
// font-size: 12px; // font-size: 12px;
color: $text-color; color: $text-color;
background-color: rgba(0, 0, 0, 1); background-color: rgba(0, 0, 0, 1);
border: 1px solid #5b472a; border: 1px solid #5b472a;
border-radius: 2px; border-radius: 2px;
display: block; display: block;
.i-autocomplete-trigger { .i-autocomplete-trigger {
display: flex; display: flex;
height: 100%; height: 100%;
opacity: 0.8; opacity: 0.8;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
.i-autocomplete-input { .i-autocomplete-input {
padding-left: 8px; padding-left: 8px;
width: 100%; 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;
border: none; border: none;
outline: none; outline: none;
appearance: none; color: inherit;
display: block; // font-size: 12px;
overflow: hidden; background-color: black;
font-family: SimSun;
}
&.active { &.disabled {
background-color: $activeColor; color: gray;
}
&:hover:not(.active) { .i-select-down-icon {
// font-size: 12px; background-image: url('./img/select_down_disabled.png');
background-color: $hoverColor;
} }
} }
} }
.dropdown-active { .i-autocomplete-trigger:hover {
animation: dropdown-enter 0.2s ease-in; opacity: 1;
} }
}
.dropdown-active { .i-autocomplete-dropdown {
animation: dropdown-leave 0.2s ease-in; position: fixed;
} max-height: 160px;
overflow-y: auto;
background: black;
// font-size: 12px;
z-index: 888;
.dropdown-enter-active { $hoverColor: #002947;
animation: dropdown-enter 0.2s ease-in; $activeColor: color.scale($hoverColor, $lightness: 5%);
} color: $text-color;
.dropdown-leave-active { .i-autocomplete-dropdown-item {
animation: dropdown-leave 0.2s ease-in; // 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 { &.active {
animation: dropdown-enter 0.2s ease-in; background-color: $activeColor;
} }
.dropdown-leave-active { &:hover:not(.active) {
animation: dropdown-leave 0.2s ease-in; // 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> </style>
+74 -70
View File
@@ -1,116 +1,120 @@
<script lang="tsx"> <script lang="tsx">
import { defineComponent, renderSlot } from "vue" import { defineComponent, renderSlot } from 'vue'
import { RouterLink } from "vue-router" import { RouterLink } from 'vue-router'
export default defineComponent({ export default defineComponent({
name: "CalcButton", name: 'CalcButton',
props: { props: {
small: { small: {
type: Boolean, type: Boolean,
default: false default: false,
}, },
to: { to: {
type: String, type: String,
default: null default: null,
}, },
class: { class: {
type: [String], type: [String],
default: "" default: '',
}, },
icon: { icon: {
type: String type: String,
}, },
type: { type: {
type: String, type: String,
default: "primary" default: 'primary',
}, },
disabled: { disabled: {
type: Boolean, type: Boolean,
default: false default: false,
} },
}, },
emits: ["click"], emits: ['click'],
setup(props, { slots,emit }) { setup(props, { slots, emit }) {
return () => { return () => {
const proerpties = { const proerpties = {
to: props.to, to: props.to,
class: { class: {
[props.class]: !!props.class, [props.class]: !!props.class,
"i-button cursor-pointer outline-none text-shadow": true, 'i-button cursor-pointer outline-none text-shadow': true,
"small": props.small, small: props.small,
[`icon-${props.icon}`]: !!props.icon [`icon-${props.icon}`]: !!props.icon,
}, },
onClick: (e: MouseEvent) => { onClick: (e: MouseEvent) => {
if (props.to) { if (props.to) {
e.preventDefault() e.preventDefault()
} }
emit("click", e) emit('click', e)
}, },
disabled: props.disabled disabled: props.disabled,
} }
const content = renderSlot(slots, "default") const content = renderSlot(slots, 'default')
return props.to ? <RouterLink {...proerpties}>{content}</RouterLink> : <button {...proerpties}>{content}</button> return props.to ? (
<RouterLink {...proerpties}>{content}</RouterLink>
) : (
<button {...proerpties}>{content}</button>
)
} }
} },
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
.i-button { .i-button {
min-width: 56px; min-width: 56px;
height: 24px; height: 24px;
// line-height: 20px; // 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; color: #e9c556;
background: linear-gradient(#223768, #122438); background: linear-gradient(#2d4e9f, #122438);
border: 1px solid #755f44; border: 1px solid #e3c18a;
}
&:disabled {
color: #696969;
background: linear-gradient(#353535, #1c1c1c);
border: 1px solid #414141;
border-radius: 2px; 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 { &.small {
display: block; min-width: 28px;
text-decoration: none; height: 20px;
// line-height: 20px;
font-size: 12px;
padding-top: 0;
padding-bottom: 0;
} }
}
.i-href {
display: block;
text-decoration: none;
}
</style> </style>
+122 -119
View File
@@ -1,9 +1,9 @@
<script lang="tsx"> <script lang="tsx">
import { selectionEmits, selectionProps } from "hoci" import { selectionEmits, selectionProps } from 'hoci'
import type { CSSProperties, PropType } from "vue"; import type { CSSProperties, PropType } from 'vue'
import { Teleport, Transition, computed, defineComponent, onDeactivated, ref, watch } 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 { interface ICascaderItem {
label: string label: string
id?: string | number id?: string | number
@@ -13,33 +13,33 @@ interface ICascaderItem {
} }
export default defineComponent({ export default defineComponent({
name: "ICascader", name: 'ICascader',
props: { props: {
...selectionProps, ...selectionProps,
disabled: { disabled: {
type: Boolean, type: Boolean,
default: false default: false,
}, },
width: { width: {
type: Number, type: Number,
default: 120 default: 120,
}, },
emptyLabel: { emptyLabel: {
type: String type: String,
}, },
highlight: { highlight: {
type: String, type: String,
// warn remind // warn remind
default: "" default: '',
}, },
items: { items: {
type: Array as PropType<ICascaderItem[]>, type: Array as PropType<ICascaderItem[]>,
default: () => [] default: () => [],
} },
}, },
emits: [...selectionEmits, "select"], emits: [...selectionEmits, 'select'],
setup(props, { emit }) { setup(props, { emit }) {
const modelValue = useVModel(props, "modelValue") const modelValue = useVModel(props, 'modelValue')
const isOpen = ref(false) const isOpen = ref(false)
const triggerRef = ref<HTMLElement>() const triggerRef = ref<HTMLElement>()
@@ -60,13 +60,13 @@ export default defineComponent({
return { return {
left: `${dropdownPosition.value.x}px`, left: `${dropdownPosition.value.x}px`,
top: `${dropdownPosition.value.y}px`, top: `${dropdownPosition.value.y}px`,
width: `${dropdownPosition.value.w}px` width: `${dropdownPosition.value.w}px`,
} }
} else { } else {
return { return {
left: `${dropdownPosition.value.x}px`, left: `${dropdownPosition.value.x}px`,
bottom: `${dropdownPosition.value.z}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, w: width,
x: left, x: left,
y: top + height + 2, y: top + height + 2,
z: 0 z: 0,
} }
} else { } else {
dropdownPosition.value = { dropdownPosition.value = {
w: width, w: width,
x: left, x: left,
z: window.innerHeight - top + 2, z: window.innerHeight - top + 2,
y: 0 y: 0,
} }
} }
} }
} }
window.addEventListener('resize', onResize)
window.addEventListener("resize", onResize) window.addEventListener('scroll', onResize)
window.addEventListener("scroll", onResize)
onDeactivated(() => { onDeactivated(() => {
window.removeEventListener("resize", onResize) window.removeEventListener('resize', onResize)
window.removeEventListener("scroll", onResize) window.removeEventListener('scroll', onResize)
}) })
const selectItem = ref<ICascaderItem>() const selectItem = ref<ICascaderItem>()
@@ -108,7 +107,7 @@ export default defineComponent({
isOpen.value = false isOpen.value = false
} }
selectItem.value = item selectItem.value = item
emit("select", item.value) emit('select', item.value)
} }
function loadNode(item: ICascaderItem) { function loadNode(item: ICascaderItem) {
@@ -122,11 +121,15 @@ export default defineComponent({
return () => { return () => {
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 <div
class={{ class={{
"i-cascader-trigger": true, 'i-cascader-trigger': true,
"disabled": props.disabled disabled: props.disabled,
}} }}
ref={triggerRef} ref={triggerRef}
> >
@@ -151,122 +154,122 @@ export default defineComponent({
</div> </div>
) )
} }
} },
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
@use 'sass:color'; @use 'sass:color';
$text-color: #937639; $text-color: #937639;
.i-cascader { .i-cascader {
min-width: 80px; min-width: 80px;
width: 160px; width: 160px;
user-select: none; user-select: none;
outline: none; outline: none;
height: 16px; height: 16px;
min-height: 16px; min-height: 16px;
line-height: 100%; line-height: 100%;
// font-size: 12px; // font-size: 12px;
color: $text-color; color: $text-color;
background-color: rgba(0, 0, 0, 1); background-color: rgba(0, 0, 0, 1);
border: 1px solid #5b472a; border: 1px solid #5b472a;
border-radius: 2px; border-radius: 2px;
padding: 0;
padding-right: 3px;
margin: 0;
display: block;
.i-cascader-trigger {
padding: 0; padding: 0;
padding-right: 3px; display: flex;
margin: 0; height: 100%;
display: block; opacity: 0.9;
.i-cascader-trigger { justify-content: space-between;
padding: 0; align-items: center;
display: flex;
height: 100%;
opacity: 0.9;
justify-content: space-between; .i-cascader-label {
align-items: center; padding-left: 5px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
max-width: calc(100% - 20px);
}
.i-cascader-label { .i-cascader-down-icon {
padding-left: 5px; background-image: url('./img/select_down.png');
overflow: hidden; background-size: cover;
white-space: nowrap; background-repeat: no-repeat;
text-overflow: ellipsis; background-position: center;
max-width: calc(100% - 20px); 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 { .i-cascader-down-icon {
background-image: url("./img/select_down.png"); background-image: url('./img/select_down_disabled.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");
}
} }
} }
.i-cascader-trigger:hover {
opacity: 1;
}
} }
.remind { .i-cascader-trigger:hover {
color: #32e432 !important; opacity: 1;
border: 1px solid #aa8651;
} }
}
.i-cascader-dropdown { .remind {
position: fixed; color: #32e432 !important;
max-height: 160px; border: 1px solid #aa8651;
overflow-y: auto; }
overflow-x: hidden;
background: black;
// font-size: 12px;
z-index: 888;
$hoverColor: #002947; .i-cascader-dropdown {
$activeColor: color.scale($hoverColor, $lightness: 5%); position: fixed;
color: $text-color; max-height: 160px;
} overflow-y: auto;
overflow-x: hidden;
background: black;
// font-size: 12px;
z-index: 888;
.dropdown-active { $hoverColor: #002947;
animation: dropdown-enter 0.2s ease-in; $activeColor: color.scale($hoverColor, $lightness: 5%);
} color: $text-color;
}
.dropdown-active { .dropdown-active {
animation: dropdown-leave 0.2s ease-in; animation: dropdown-enter 0.2s ease-in;
} }
.dropdown-enter-active { .dropdown-active {
animation: dropdown-enter 0.2s ease-in; animation: dropdown-leave 0.2s ease-in;
} }
.dropdown-leave-active { .dropdown-enter-active {
animation: dropdown-leave 0.2s ease-in; animation: dropdown-enter 0.2s ease-in;
} }
.dropdown-enter-active { .dropdown-leave-active {
animation: dropdown-enter 0.2s ease-in; animation: dropdown-leave 0.2s ease-in;
} }
.dropdown-leave-active { .dropdown-enter-active {
animation: dropdown-leave 0.2s ease-in; animation: dropdown-enter 0.2s ease-in;
} }
.dropdown-leave-active {
animation: dropdown-leave 0.2s ease-in;
}
</style> </style>
@@ -1,25 +1,25 @@
<script lang="tsx"> <script lang="tsx">
import { defineComponent, provide } from "vue" import { defineComponent, provide } from 'vue'
import { selectionEmits, selectionProps, useSelectionList } from "hoci" import { selectionEmits, selectionProps, useSelectionList } from 'hoci'
import { CHECKBOX_GROUP } from "../shared" import { CHECKBOX_GROUP } from '../shared'
export default defineComponent({ export default defineComponent({
name: "ICheckboxGroup", name: 'ICheckboxGroup',
components: {}, components: {},
props: { props: {
...selectionProps, ...selectionProps,
activeClass: { activeClass: {
type: String, type: String,
default: "checked" default: 'checked',
}, },
unactiveClass: { unactiveClass: {
type: String, type: String,
default: "unchecked" default: 'unchecked',
}, },
multiple: { multiple: {
type: [Boolean, Number], type: [Boolean, Number],
default: true default: true,
} },
}, },
emits: [...selectionEmits], emits: [...selectionEmits],
@@ -29,45 +29,45 @@ export default defineComponent({
const { render } = useSelectionList(props, context) const { render } = useSelectionList(props, context)
return () => render() return () => render()
} },
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
.i-checkbox { .i-checkbox {
display: flex; display: flex;
align-items: center; align-items: center;
&.checked { &.checked {
.i-checkbox-icon {
background-image: url("@/assets/img/control/checkbox_checked.png");
}
}
.i-checkbox-icon { .i-checkbox-icon {
width: 12px; background-image: url('@/assets/img/control/checkbox_checked.png');
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;
} }
} }
.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> </style>
+19 -11
View File
@@ -1,33 +1,41 @@
<script lang="tsx"> <script lang="tsx">
import { useToggle, useVModel } from "@vueuse/core" import { useToggle, useVModel } from '@vueuse/core'
import { defineComponent, renderSlot } from "vue" import { defineComponent, renderSlot } from 'vue'
export default defineComponent({ export default defineComponent({
name: "ICollapse", name: 'ICollapse',
props: { props: {
title: { title: {
type: String type: String,
}, },
modelValue: { modelValue: {
type: Boolean, type: Boolean,
default: () => true default: () => true,
} },
}, },
setup(props, { slots, emit }) { setup(props, { slots, emit }) {
const modelValue = useVModel(props, "modelValue", emit, { passive: true }) const modelValue = useVModel(props, 'modelValue', emit, { passive: true })
const toggle = useToggle(modelValue) const toggle = useToggle(modelValue)
return () => { return () => {
return ( return (
<div class="rounded h-auto bg-hex-00000078 border-1 border-hex-ffffff28 my-1 overflow-hidden"> <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
<div class={["transition-all", "ease-out-in", "h-auto"].concat(modelValue.value ? ["max-h-200"] : ["max-h-0"])}> onClick={() => toggle()}
<div class="p-2">{renderSlot(slots, "default")}</div> 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>
</div> </div>
) )
} }
} },
}) })
</script> </script>
+79 -48
View File
@@ -1,63 +1,72 @@
<script lang="tsx"> <script lang="tsx">
import { onClickOutside, onKeyDown, syncRef, useDraggable, useVModel } from "@vueuse/core" import { onClickOutside, onKeyDown, syncRef, useDraggable, useVModel } from '@vueuse/core'
import type { PropType, RendererElement } from "vue" import type { PropType, RendererElement } from 'vue'
import { Teleport, Transition, computed, defineComponent, provide, ref, renderSlot, watch } from "vue" import {
import { provideShow } from "@/components/hooks/show" Teleport,
Transition,
computed,
defineComponent,
provide,
ref,
renderSlot,
watch,
} from 'vue'
import { provideShow } from '@/components/hooks/show'
export default defineComponent({ export default defineComponent({
name: "IDialog", name: 'IDialog',
props: { props: {
visible: { visible: {
type: Boolean, type: Boolean,
default: () => false default: () => false,
}, },
class: { class: {
type: String type: String,
}, },
mask: { mask: {
type: Boolean, type: Boolean,
default: () => false default: () => false,
}, },
// 模态框 // 模态框
modal: { modal: {
type: Boolean, type: Boolean,
default: () => false default: () => false,
}, },
header: { header: {
type: [String, Boolean], type: [String, Boolean],
default: () => "提示" default: () => '提示',
}, },
closeOnEsc: { closeOnEsc: {
type: Boolean, type: Boolean,
default: () => true default: () => true,
}, },
closeButton: { closeButton: {
type: Boolean, type: Boolean,
default: () => true default: () => true,
}, },
cache: { cache: {
// 维持窗口状态 // 维持窗口状态
type: Boolean, type: Boolean,
default: () => true default: () => true,
}, },
lazy: { lazy: {
type: Boolean, type: Boolean,
default: () => false default: () => false,
}, },
drag: { drag: {
type: String as PropType<"header" | "all" | "none">, type: String as PropType<'header' | 'all' | 'none'>,
default: () => "header" default: () => 'header',
}, },
appendTo: { appendTo: {
type: [Object, String] as PropType<string | RendererElement | null | undefined>, type: [Object, String] as PropType<string | RendererElement | null | undefined>,
default: "body" default: 'body',
}, },
scale: { scale: {
type: Number, type: Number,
default: 1 default: 1,
} },
}, },
emits: ["close", "update:visible"], emits: ['close', 'update:visible'],
setup(props, { emit, slots }) { setup(props, { emit, slots }) {
const dialogRef = ref<HTMLElement | null>(null) const dialogRef = ref<HTMLElement | null>(null)
const headerRef = ref<HTMLElement | null>(null) const headerRef = ref<HTMLElement | null>(null)
@@ -66,18 +75,18 @@ export default defineComponent({
provideShow(visible) provideShow(visible)
provide("dialogRef", dialogRef) provide('dialogRef', dialogRef)
const loaded = ref(!props.lazy) const loaded = ref(!props.lazy)
const stopLoadWatch = watch(visible, val => { const stopLoadWatch = watch(visible, (val) => {
if (val) { if (val) {
loaded.value = true loaded.value = true
stopLoadWatch() stopLoadWatch()
} }
}) })
syncRef(useVModel(props, "visible"), visible, { direction: "both" }) syncRef(useVModel(props, 'visible'), visible, { direction: 'both' })
onClickOutside(dialogRef, () => { onClickOutside(dialogRef, () => {
if (props.modal) { if (props.modal) {
@@ -87,17 +96,17 @@ export default defineComponent({
function close() { function close() {
visible.value = false visible.value = false
emit("close") emit('close')
} }
onKeyDown( onKeyDown(
"Escape", 'Escape',
() => { () => {
if (visible.value && props.closeOnEsc) { if (visible.value && props.closeOnEsc) {
close() close()
} }
}, },
{ target: document } { target: document },
) )
const init = ref(true) const init = ref(true)
@@ -105,24 +114,24 @@ export default defineComponent({
const { x, y, style } = useDraggable( const { x, y, style } = useDraggable(
computed(() => { computed(() => {
switch (props.drag) { switch (props.drag) {
case "header": case 'header':
return headerRef.value return headerRef.value
case "all": case 'all':
return dialogRef.value return dialogRef.value
case "none": case 'none':
default: default:
return null return null
} }
}), }),
{ {
initialValue: { x: 0, y: 0 } initialValue: { x: 0, y: 0 },
} },
) )
const isFixed = computed(() => x.value + y.value > 0) const isFixed = computed(() => x.value + y.value > 0)
const appendToRef = computed(() => { const appendToRef = computed(() => {
return props.appendTo || "body" return props.appendTo || 'body'
}) })
return () => { return () => {
@@ -130,19 +139,41 @@ export default defineComponent({
<Teleport to={appendToRef.value}> <Teleport to={appendToRef.value}>
<Transition name="dialog" mode="out-in"> <Transition name="dialog" mode="out-in">
{loaded.value && (props.cache || visible.value) && ( {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
<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`}> 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 && ( {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> <div class="text-xs text-shadow ">{props.header}</div>
{props.closeButton && ( {props.closeButton && (
<div class="flex top-0 right-0 bottom-0 items-center absolute"> <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> </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>
</div> </div>
)} )}
@@ -150,21 +181,21 @@ export default defineComponent({
</Teleport> </Teleport>
) )
} }
} },
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.dialog-enter-active { .dialog-enter-active {
animation: fade-in 400ms; animation: fade-in 400ms;
.dialog { .dialog {
animation: zoom-in 400ms; animation: zoom-in 400ms;
}
} }
.dialog-leave-active { }
animation: fade-in reverse 400ms; .dialog-leave-active {
.dialog { animation: fade-in reverse 400ms;
animation: zoom-out 400ms; .dialog {
} animation: zoom-out 400ms;
} }
}
</style> </style>
+137 -128
View File
@@ -1,33 +1,42 @@
<script lang="tsx"> <script lang="tsx">
import { selectionEmits, selectionProps, useSelectionList } from "hoci" import { selectionEmits, selectionProps, useSelectionList } from 'hoci'
import type { CSSProperties, PropType, RendererElement } from "vue"; import type { CSSProperties, PropType, RendererElement } from 'vue'
import { Teleport, Transition, computed, defineComponent, onDeactivated, ref, renderSlot, watch } 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({ export default defineComponent({
name: "IIconselect", name: 'IIconselect',
props: { props: {
...selectionProps, ...selectionProps,
disabled: { disabled: {
type: Boolean, type: Boolean,
default: false default: false,
}, },
width: { width: {
type: Number, type: Number,
default: 28 default: 28,
}, },
emptyLabel: { emptyLabel: {
type: String type: String,
}, },
columnNum: { columnNum: {
type: Number, type: Number,
default: 1 default: 1,
}, },
appendTo: { appendTo: {
type: [Object, String] as PropType<string | RendererElement | null | undefined>, type: [Object, String] as PropType<string | RendererElement | null | undefined>,
default: "body" default: 'body',
} },
}, },
emits: selectionEmits, emits: selectionEmits,
setup(props, context) { setup(props, context) {
@@ -36,9 +45,9 @@ export default defineComponent({
const { renderItem } = useSelectionList(() => { const { renderItem } = useSelectionList(() => {
return { return {
...props, ...props,
itemClass: "i-option", itemClass: 'i-option',
activeClass: "active", activeClass: 'active',
unactiveClass: "unactive" unactiveClass: 'unactive',
} }
}, context) }, context)
@@ -52,7 +61,7 @@ export default defineComponent({
watch(isOpen, onResize) watch(isOpen, onResize)
const appendToRef = computed(() => { const appendToRef = computed(() => {
return props.appendTo || "body" return props.appendTo || 'body'
}) })
function collapse() { function collapse() {
@@ -66,7 +75,7 @@ export default defineComponent({
return { return {
left: `${dropdownPosition.value.x}px`, left: `${dropdownPosition.value.x}px`,
top: `${dropdownPosition.value.y}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 = { dropdownPosition.value = {
w: width, w: width,
x: left - 2, x: left - 2,
y: top + height + 2 y: top + height + 2,
} }
} }
} }
onClickOutside(triggerRef, () => (isOpen.value = false)) onClickOutside(triggerRef, () => (isOpen.value = false))
window.addEventListener("resize", onResize) window.addEventListener('resize', onResize)
window.addEventListener("scroll", onResize) window.addEventListener('scroll', onResize)
onDeactivated(() => { onDeactivated(() => {
window.removeEventListener("resize", onResize) window.removeEventListener('resize', onResize)
window.removeEventListener("scroll", onResize) window.removeEventListener('scroll', onResize)
}) })
const { slots } = context const { slots } = context
@@ -101,8 +110,8 @@ export default defineComponent({
<div class="i-icon-select" onClick={collapse}> <div class="i-icon-select" onClick={collapse}>
<div <div
class={{ class={{
"i-select-trigger": true, 'i-select-trigger': true,
"disabled": props.disabled disabled: props.disabled,
}} }}
ref={triggerRef} ref={triggerRef}
> >
@@ -111,133 +120,133 @@ export default defineComponent({
<Teleport to={appendToRef.value}> <Teleport to={appendToRef.value}>
<Transition name="dropdown" mode="out-in"> <Transition name="dropdown" mode="out-in">
<div class="i-icon-select-dropdown" style={dropdownStyle.value} v-show={isOpen.value}> <div class="i-icon-select-dropdown" style={dropdownStyle.value} v-show={isOpen.value}>
{renderSlot(slots, "default")} {renderSlot(slots, 'default')}
</div> </div>
</Transition> </Transition>
</Teleport> </Teleport>
</div> </div>
) )
} }
} },
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
@use 'sass:color'; @use 'sass:color';
$text-color: #e9c556; $text-color: #e9c556;
.i-icon-select { .i-icon-select {
width: 28px; width: 28px;
min-width: 28px; min-width: 28px;
user-select: none; user-select: none;
outline: none; outline: none;
height: 28px; height: 28px;
line-height: 100%; line-height: 100%;
// font-size: 12px; // font-size: 12px;
color: $text-color; color: $text-color;
background-color: rgba(0, 0, 0, 1); background-color: rgba(0, 0, 0, 1);
border: 1px solid #5b472a; border: 1px solid #5b472a;
border-radius: 2px; border-radius: 2px;
padding: 0;
margin: 0;
display: block;
.i-select-trigger {
padding: 0; 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; display: flex;
flex-direction: row; height: 100%;
flex-wrap: wrap; opacity: 1;
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;
$hoverColor: #002947; justify-content: center;
$activeColor: color.scale($hoverColor, $lightness: 5%); align-items: center;
color: $text-color;
.i-option { &.disabled {
// font-size: 12px; color: gray;
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;
}
} }
} }
.dropdown-active { .i-select-trigger:hover {
animation: dropdown-enter 0.2s ease-in; opacity: 1;
} }
}
.dropdown-active { .i-icon-select-dropdown {
animation: dropdown-leave 0.2s ease-in; 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 { $hoverColor: #002947;
animation: dropdown-enter 0.2s ease-in; $activeColor: color.scale($hoverColor, $lightness: 5%);
} color: $text-color;
.dropdown-leave-active { .i-option {
animation: dropdown-leave 0.2s ease-in; // 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 { // &.active {
animation: dropdown-enter 0.2s ease-in; // background-color: $activeColor;
} // }
.dropdown-leave-active { &.active::before {
animation: dropdown-leave 0.2s ease-in; 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> </style>
+15 -16
View File
@@ -1,18 +1,17 @@
import type { App } from "vue" import type { App } from 'vue'
import Button from "./button/index.vue" import Button from './button/index.vue'
import Tabs from "./tabs/index.vue" import Tabs from './tabs/index.vue'
import Tab from "./tab/index.vue" import Tab from './tab/index.vue'
import Select from "./select/index.vue" import Select from './select/index.vue'
import Option from "./option/index.vue" import Option from './option/index.vue'
import Menu from "./menu/menu.vue" import Menu from './menu/menu.vue'
import Checkbox from "./checkbox/index.vue" import Checkbox from './checkbox/index.vue'
import Collapse from "./collapse/index.vue" import Collapse from './collapse/index.vue'
import Dialog from "./dialog/index.vue" import Dialog from './dialog/index.vue'
import Tooltip from "./tooltip/index.vue" import Tooltip from './tooltip/index.vue'
import Iconselect from "./iconselect/index.vue" import Iconselect from './iconselect/index.vue'
import Selection from "./selection/index.vue" import Selection from './selection/index.vue'
import Slider from "./slider/index.vue" import Slider from './slider/index.vue'
export const components = { export const components = {
Button, Button,
@@ -27,7 +26,7 @@ export const components = {
Tooltip, Tooltip,
Iconselect, Iconselect,
Selection, Selection,
Slider Slider,
} }
export type CalcComponents = typeof components export type CalcComponents = typeof components
+1 -1
View File
@@ -1,4 +1,4 @@
<script lang="tsx"> <script lang="tsx">
import { HiItem } from "hoci" import { HiItem } from 'hoci'
export default HiItem export default HiItem
</script> </script>
+51 -24
View File
@@ -1,23 +1,26 @@
<script lang="tsx"> <script lang="tsx">
import { computed, defineComponent, renderSlot } from "vue" import { computed, defineComponent, renderSlot } from 'vue'
import { classPropType } from "@/components/hooks/types" import { classPropType } from '@/components/hooks/types'
export default defineComponent({ export default defineComponent({
props: { props: {
loading: { loading: {
type: Boolean, type: Boolean,
default: () => false default: () => false,
}, },
class: { class: {
type: classPropType, type: classPropType,
default: () => "" default: () => '',
} },
}, },
setup(props, { slots }) { setup(props, { slots }) {
const loading = computed(() => props.loading) const loading = computed(() => props.loading)
return () => { return () => {
return ( return (
<div class="relative"> <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 <svg
class="i-loading-spin" class="i-loading-spin"
version="1.1" version="1.1"
@@ -29,41 +32,65 @@ export default defineComponent({
xml:space="preserve" xml:space="preserve"
> >
<g> <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="0" style="stop-color:transparent" />
<stop offset="1" style="stop-color:currentColor" /> <stop offset="1" style="stop-color:currentColor" />
</linearGradient> </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" /> <path
<linearGradient id="left" gradientUnits="userSpaceOnUse" x1="50" y1="0" x2="50" y2="180"> 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="0" style="stop-color:currentColor" />
<stop offset="1" style="stop-color:transparent" /> <stop offset="1" style="stop-color:transparent" />
</linearGradient> </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" /> <circle fill="currentColor" cx="100" cy="10" r="10" />
</g> </g>
</svg> </svg>
</div> </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> </div>
) )
} }
} },
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
.i-loading-spin { .i-loading-spin {
width: 32px; width: 32px;
height: 32px; height: 32px;
animation: spin infinite 1s linear; animation: spin infinite 1s linear;
} }
@keyframes spin { @keyframes spin {
0% { 0% {
transform: rotate(0deg); transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
} }
100% {
transform: rotate(360deg);
}
}
</style> </style>
+37 -35
View File
@@ -1,10 +1,10 @@
<script lang="tsx"> <script lang="tsx">
import { useToggle } from "@vueuse/core" import { useToggle } from '@vueuse/core'
import { defineComponent, ref, renderSlot } from "vue" import { defineComponent, ref, renderSlot } from 'vue'
export default defineComponent({ export default defineComponent({
name: "CalcMenu", name: 'CalcMenu',
props: ["label"], props: ['label'],
setup(props, { slots }) { setup(props, { slots }) {
const expand = ref(false) const expand = ref(false)
@@ -24,48 +24,50 @@ export default defineComponent({
</div> </div>
<div class="i-menu-label" v-trans={props.label}></div> <div class="i-menu-label" v-trans={props.label}></div>
</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> </div>
) )
} }
} },
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
.i-menu { .i-menu {
min-width: 120px; min-width: 120px;
width: 200px; width: 200px;
transition: all 0.3s ease; transition: all 0.3s ease;
.i-menu-content { .i-menu-content {
width: 100%; width: 100%;
height: 16px; height: 16px;
line-height: 16px; line-height: 16px;
display: flex; display: flex;
background-color: rgba(37, 37, 37, 0.4); background-color: rgba(37, 37, 37, 0.4);
padding: 4px; padding: 4px;
.i-menu-expand-icon { .i-menu-expand-icon {
width: 12px; width: 12px;
height: 12px; height: 12px;
color: rgb(255, 247, 148); color: rgb(255, 247, 148);
margin-right: 8px; margin-right: 8px;
}
.i-menu-label {
width: auto;
// font-size: 12px;
}
} }
.i-menu-items { .i-menu-label {
height: 0; width: auto;
overflow: hidden; // font-size: 12px;
&.expand {
height: 32px;
}
} }
} }
.i-menu-items {
height: 0;
overflow: hidden;
&.expand {
height: 32px;
}
}
}
</style> </style>
@@ -1,4 +1,4 @@
<script lang="tsx"> <script lang="tsx">
import { HiItem } from "hoci" import { HiItem } from 'hoci'
export default HiItem export default HiItem
</script> </script>
@@ -1,33 +1,35 @@
<script lang="tsx"> <script lang="tsx">
import { onKeyStroke } from "@vueuse/core" import { onKeyStroke } from '@vueuse/core'
import type { PropType } from "vue"; import type { PropType } from 'vue'
import { computed, defineComponent, onUnmounted } from "vue" import { computed, defineComponent, onUnmounted } from 'vue'
export default defineComponent({ export default defineComponent({
props: { props: {
page: { page: {
type: Number, type: Number,
default: () => 0 default: () => 0,
}, },
totalPage: { totalPage: {
type: Number, type: Number,
default: () => 0 default: () => 0,
}, },
minPage: { minPage: {
type: Number, type: Number,
default: () => 1 default: () => 1,
}, },
parent: { parent: {
type: Object as PropType<HTMLElement>, type: Object as PropType<HTMLElement>,
default: () => document default: () => document,
}, },
disabled: { disabled: {
type: Boolean, type: Boolean,
default: false default: false,
} },
}, },
setup(props, { emit }) { 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({ const page = computed({
get() { get() {
@@ -37,17 +39,16 @@ export default defineComponent({
return Math.max(Math.min(props.page, totalPage.value), props.minPage) return Math.max(Math.min(props.page, totalPage.value), props.minPage)
}, },
set(val) { set(val) {
emit("update:page", val) emit('update:page', val)
} },
}) })
function prev(e: Event) { function prev(e: Event) {
e.preventDefault() e.preventDefault()
if (page.value > 1 && !props.disabled) { if (page.value > 1 && !props.disabled) {
page.value-- page.value--
emit("prev") emit('prev')
emit("change", page.value) emit('change', page.value)
} }
} }
@@ -55,13 +56,13 @@ export default defineComponent({
e.preventDefault() e.preventDefault()
if (page.value < totalPage.value && !props.disabled) { if (page.value < totalPage.value && !props.disabled) {
page.value = page.value + 1 page.value = page.value + 1
emit("next") emit('next')
emit("change", page.value) emit('change', page.value)
} }
} }
const removePrev = onKeyStroke(["F1", "PageUp"], prev, { target: props.parent }) const removePrev = onKeyStroke(['F1', 'PageUp'], prev, { target: props.parent })
const removeNext = onKeyStroke(["F2", "PageDown"], next, { target: props.parent }) const removeNext = onKeyStroke(['F2', 'PageDown'], next, { target: props.parent })
onUnmounted(() => { onUnmounted(() => {
removePrev() removePrev()
@@ -71,15 +72,23 @@ export default defineComponent({
return () => { return () => {
return ( return (
<div class="flex space-x-1 h-10 w-full items-center justify-center"> <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"> <span class="text-center w-8">
{page.value}/{totalPage.value} {page.value}/{totalPage.value}
</span> </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> </div>
) )
} }
} },
}) })
</script> </script>
@@ -1,17 +1,17 @@
<script lang="tsx"> <script lang="tsx">
import { computed, defineComponent } from "vue" import { computed, defineComponent } from 'vue'
export default defineComponent({ export default defineComponent({
props: { props: {
value: { value: {
type: [Number, String], type: [Number, String],
default: 0 default: 0,
}, },
total: { total: {
type: [Number, String], type: [Number, String],
default: 100 default: 100,
} },
}, },
setup(props) { setup(props) {
const progress = computed(() => { const progress = computed(() => {
@@ -24,16 +24,28 @@ export default defineComponent({
return () => ( return () => (
<div class="border-solid bg-hex-171717 border-hex-272521 border-2"> <div class="border-solid bg-hex-171717 border-hex-272521 border-2">
<div class="border-transparent border-solid 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>
</div> </div>
) )
} },
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.calc-progress { .calc-progress {
background: linear-gradient(to bottom, #48d0eb 0%, #6cf4fe 15%, #4fbdfe 30%, #3080fe 40%, #1d5bfe 55%, #3283fd 70%, #000456 100%); background: linear-gradient(
} to bottom,
#48d0eb 0%,
#6cf4fe 15%,
#4fbdfe 30%,
#3080fe 40%,
#1d5bfe 55%,
#3283fd 70%,
#000456 100%
);
}
</style> </style>
+176 -153
View File
@@ -1,57 +1,67 @@
<script lang="tsx"> <script lang="tsx">
import { selectionEmits, selectionProps, useSelectionList } from "hoci" import { selectionEmits, selectionProps, useSelectionList } from 'hoci'
import type { CSSProperties, PropType, RendererElement } from "vue"; import type { CSSProperties, PropType, RendererElement } from 'vue'
import { Teleport, Transition, computed, defineComponent, onDeactivated, ref, renderList, renderSlot, watch } from "vue" import {
Teleport,
Transition,
computed,
defineComponent,
onDeactivated,
ref,
renderList,
renderSlot,
watch,
} from 'vue'
import { onClickOutside, useVModel } from "@vueuse/core" import { onClickOutside, useVModel } from '@vueuse/core'
import { classPropType, labelPropType } from "@/components/hooks/types" import { classPropType, labelPropType } from '@/components/hooks/types'
export default defineComponent({ export default defineComponent({
name: "ISelect", name: 'ISelect',
props: { props: {
...selectionProps, ...selectionProps,
disabled: { disabled: {
type: Boolean, type: Boolean,
default: false default: false,
}, },
width: { width: {
type: Number, type: Number,
default: 120 default: 120,
}, },
emptyLabel: { emptyLabel: {
type: labelPropType type: labelPropType,
}, },
highlight: { highlight: {
type: String, type: String,
// warn remind // warn remind
default: "" default: '',
}, },
labelClass: { labelClass: {
type: String, type: String,
default: "" default: '',
}, },
inputClass: { inputClass: {
type: classPropType, type: classPropType,
default: "" default: '',
}, },
appendTo: { appendTo: {
type: [Object, String] as PropType<string | RendererElement | null | undefined>, type: [Object, String] as PropType<string | RendererElement | null | undefined>,
default() { default() {
return "body" return 'body'
} },
} },
}, },
emits: [...selectionEmits], emits: [...selectionEmits],
setup(props, context) { setup(props, context) {
const modelValue = useVModel(props, "modelValue", context.emit, { passive: true }) const modelValue = useVModel(props, 'modelValue', context.emit, { passive: true })
const { renderItem } = useSelectionList(() => { const { renderItem } = useSelectionList(() => {
return { return {
...props, ...props,
modelValue: modelValue.value, modelValue: modelValue.value,
itemClass: "i-select-dropdown-item", itemClass: 'i-select-dropdown-item',
activeClass: "active", activeClass: 'active',
unactiveClass: "unactive" unactiveClass: 'unactive',
} }
}, context) }, context)
@@ -74,13 +84,13 @@ export default defineComponent({
return { return {
left: `${dropdownPosition.value.x}px`, left: `${dropdownPosition.value.x}px`,
top: `${dropdownPosition.value.y}px`, top: `${dropdownPosition.value.y}px`,
width: `${dropdownPosition.value.w}px` width: `${dropdownPosition.value.w}px`,
} }
} else { } else {
return { return {
left: `${dropdownPosition.value.x}px`, left: `${dropdownPosition.value.x}px`,
bottom: `${dropdownPosition.value.z}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, w: width,
x: left, x: left,
y: top + height + 2, y: top + height + 2,
z: 0 z: 0,
} }
} else { } else {
dropdownPosition.value = { dropdownPosition.value = {
w: width, w: width,
x: left, x: left,
z: window.innerHeight - top + 2, z: window.innerHeight - top + 2,
y: 0 y: 0,
} }
} }
} }
} }
const appendToRef = computed(() => { const appendToRef = computed(() => {
return props.appendTo || "body" return props.appendTo || 'body'
}) })
onClickOutside(triggerRef, () => (isOpen.value = false)) onClickOutside(triggerRef, () => (isOpen.value = false))
window.addEventListener("resize", onResize) window.addEventListener('resize', onResize)
window.addEventListener("scroll", onResize) window.addEventListener('scroll', onResize)
onDeactivated(() => { onDeactivated(() => {
window.removeEventListener("resize", onResize) window.removeEventListener('resize', onResize)
window.removeEventListener("scroll", onResize) window.removeEventListener('scroll', onResize)
}) })
const { slots } = context const { slots } = context
@@ -127,179 +136,193 @@ export default defineComponent({
const children = renderItem() const children = renderItem()
if (children) { if (children) {
if (!Array.isArray(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) { } else if (children.length > 0) {
return ( return (
<span class={["i-select-input space-x-1"].concat(props.inputClass)}> <span class={['i-select-input space-x-1'].concat(props.inputClass)}>
{renderList(children, item => ( {renderList(children, (item) => (
<span>{item}</span> <span>{item}</span>
))} ))}
</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 <span class="w-full pl-2 i-select-input i-select-empty-label">{emptyLabel}</span>
} }
return () => { return () => {
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 <div
class={{ class={{
"i-select-trigger": true, 'i-select-trigger': true,
"disabled": props.disabled disabled: props.disabled,
}} }}
ref={triggerRef} ref={triggerRef}
> >
{renderLabel()} {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> </div>
<Teleport to={appendToRef.value}> <Teleport to={appendToRef.value}>
<Transition name="dropdown" mode="out-in"> <Transition name="dropdown" mode="out-in">
<div class="i-select-dropdown" style={dropdownStyle.value} v-show={isOpen.value} ref={dropdownRef}> <div
{renderSlot(slots, "default")} class="i-select-dropdown"
style={dropdownStyle.value}
v-show={isOpen.value}
ref={dropdownRef}
>
{renderSlot(slots, 'default')}
</div> </div>
</Transition> </Transition>
</Teleport> </Teleport>
</div> </div>
) )
} }
} },
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
@use "sass:color"; @use 'sass:color';
$text-color: #937639; $text-color: #937639;
.i-select { .i-select {
min-width: 80px; min-width: 80px;
width: 160px; width: 160px;
user-select: none; user-select: none;
outline: none; outline: none;
height: 16px; height: 16px;
min-height: 16px; min-height: 16px;
line-height: 100%; line-height: 100%;
// font-size: 12px; // font-size: 12px;
color: $text-color; color: $text-color;
background-color: rgba(0, 0, 0, 1); background-color: rgba(0, 0, 0, 1);
border: 1px solid #5b472a; border: 1px solid #5b472a;
border-radius: 2px; border-radius: 2px;
padding: 0;
padding-right: 3px;
margin: 0;
display: block;
.i-select-trigger {
padding: 0; padding: 0;
padding-right: 3px; display: flex;
margin: 0; height: 100%;
display: block;
.i-select-trigger { justify-content: space-between;
padding: 0; align-items: center;
display: flex;
height: 100%;
justify-content: space-between; .i-select-input {
align-items: center; padding-left: 8px;
.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;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
max-width: calc(100% - 20px);
}
&.active { .i-select-empty-label {
// background-color: $activeColor; color: #5e5e5e;
color: #e9c556; }
}
&.active::after {
content: "✓";
position: absolute;
right: 5px;
}
&:hover:not(.active) { &:hover {
// font-size: 12px; .i-select-empty-label {
background-color: $hoverColor; color: #757575;
} }
} }
&.disabled {
color: gray;
}
} }
.dropdown-active { .i-select-trigger:hover {
animation: dropdown-enter 0.2s ease-in; opacity: 1;
} }
}
.dropdown-active { .warn {
animation: dropdown-leave 0.2s ease-in; color: red !important;
} border: 1px solid #aa8651;
}
.dropdown-enter-active { .remind {
animation: dropdown-enter 0.2s ease-in; color: #32e432 !important;
} border: 1px solid #aa8651;
}
.dropdown-leave-active { .i-select-dropdown {
animation: dropdown-leave 0.2s ease-in; position: fixed;
} max-height: 160px;
overflow-y: auto;
background: black;
// font-size: 12px;
z-index: 888;
.dropdown-enter-active { $hoverColor: #002947;
animation: dropdown-enter 0.2s ease-in; $activeColor: color.scale($hoverColor, $lightness: 5%);
} color: $text-color;
.dropdown-leave-active { .i-select-dropdown-item {
animation: dropdown-leave 0.2s ease-in; // 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> </style>
@@ -1,5 +1,5 @@
<script lang="tsx"> <script lang="tsx">
import { HiSelection } from "hoci" import { HiSelection } from 'hoci'
export default HiSelection export default HiSelection
</script> </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' import { useVModel, useDraggable, clamp } from '@vueuse/core'
export default defineComponent({ export default defineComponent({
name: "ISlider", name: 'ISlider',
props: { props: {
modelValue: { modelValue: {
type: Number, type: Number,
default: 0 default: 0,
}, },
min: { min: {
type: Number, type: Number,
default: 0 default: 0,
}, },
max: { max: {
type: Number, type: Number,
default: 100 default: 100,
}, },
step: { step: {
type: Number, type: Number,
default: 1 default: 1,
}, },
disabled: { disabled: {
type: Boolean, type: Boolean,
default: false default: false,
}, },
showPercent: { showPercent: {
type: Boolean, type: Boolean,
default: true default: true,
}, },
formatValue: { formatValue: {
type: Function, type: Function,
default: undefined default: undefined,
} },
}, },
emits: ['update:modelValue', 'change'], emits: ['update:modelValue', 'change'],
setup(props, { emit }) { setup(props, { emit }) {
@@ -47,17 +46,20 @@ export default defineComponent({
useDraggable(buttonRef, { useDraggable(buttonRef, {
onMove(position) { onMove(position) {
if (!runwayRef.value || props.disabled){ if (!runwayRef.value || props.disabled) {
return return
} }
const runwayRect = runwayRef.value?.getBoundingClientRect() const runwayRect = runwayRef.value?.getBoundingClientRect()
if (!runwayRect){ if (!runwayRect) {
return return
} }
// 使用runway的位置和宽度来计算百分比 // 使用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 const range = props.max - props.min
let value = (percentage * range) / 100 + props.min let value = (percentage * range) / 100 + props.min
@@ -70,20 +72,17 @@ export default defineComponent({
modelValue.value = value modelValue.value = value
emit('change', value) emit('change', value)
} },
}) })
return () => ( return () => (
<div <div
class={[ class={[
'w-full h-8 relative cursor-pointer flex items-center gap-3 py-2', '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 <div class="flex-1 h-1 bg-[#4D524D] relative" ref={runwayRef}>
class="flex-1 h-1 bg-[#4D524D] relative"
ref={runwayRef}
>
<div <div
class="h-1 bg-[#8E6D28] absolute left-0 top-0" class="h-1 bg-[#8E6D28] absolute left-0 top-0"
style={{ width: `${percentage.value}%` }} style={{ width: `${percentage.value}%` }}
@@ -93,26 +92,32 @@ export default defineComponent({
style={{ left: `calc(${percentage.value}% - 0.75rem)` }} style={{ left: `calc(${percentage.value}% - 0.75rem)` }}
ref={buttonRef} ref={buttonRef}
> >
<div class={[ <div
'w-4 h-4 absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2', class={[
'bg-gradient-to-b from-[#223768] to-[#122438]', 'w-4 h-4 absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2',
'border border-solid border-[#937639] rounded-full transition-all duration-200', 'bg-gradient-to-b from-[#223768] to-[#122438]',
'shadow-[0_2px_4px_rgba(0,0,0,0.2)]', 'border border-solid border-[#937639] rounded-full transition-all duration-200',
{ 'bg-gradient-to-b from-[#353535] to-[#1c1c1c] border-[#414141]': props.disabled } 'shadow-[0_2px_4px_rgba(0,0,0,0.2)]',
]}></div> { 'bg-gradient-to-b from-[#353535] to-[#1c1c1c] border-[#414141]': props.disabled },
]}
></div>
</div> </div>
</div> </div>
{props.showPercent && ( {props.showPercent && (
<div class={[ <div
'w-auto min-w-5 text-center text-sm font-medium', class={[
props.disabled ? 'text-[#696969]' : 'text-[#e9c556]' '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)}%`} ]}
>
{props.formatValue
? props.formatValue(modelValue.value)
: `${percentage.value.toFixed(0)}%`}
</div> </div>
)} )}
</div> </div>
) )
} },
}) })
</script> </script>
@@ -204,4 +209,3 @@ export default defineComponent({
font-weight: 500; font-weight: 500;
} }
</style> </style>
+6 -6
View File
@@ -1,16 +1,16 @@
<script lang="tsx"> <script lang="tsx">
import { defineComponent, renderSlot } from "vue" import { defineComponent, renderSlot } from 'vue'
import { HiItem, selectionProps } from "hoci" import { HiItem, selectionProps } from 'hoci'
export default defineComponent({ export default defineComponent({
name: "CalcTab", name: 'CalcTab',
props: { props: {
...selectionProps ...selectionProps,
}, },
setup(props, { slots }) { setup(props, { slots }) {
return () => { return () => {
return <HiItem {...props}>{renderSlot(slots, "default")}</HiItem> return <HiItem {...props}>{renderSlot(slots, 'default')}</HiItem>
} }
} },
}) })
</script> </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"> <script lang="tsx">
import { HiSelection, selectionProps } from "hoci" import { HiSelection, selectionProps } from 'hoci'
import type { PropType } from "vue"; import type { PropType } from 'vue'
import { computed, defineComponent, renderSlot } from "vue" import { computed, defineComponent, renderSlot } from 'vue'
import type { LocationQuery } from "vue-router"; import type { LocationQuery } from 'vue-router'
import { parseQuery, useRouter } from "vue-router" import { parseQuery, useRouter } from 'vue-router'
import type { BaseType } from "@/components/hooks/types" import type { BaseType } from '@/components/hooks/types'
export default defineComponent({ export default defineComponent({
name: "CalcTabs", name: 'CalcTabs',
props: { props: {
...selectionProps, ...selectionProps,
vertical: { vertical: {
type: Boolean type: Boolean,
}, },
route: { route: {
type: Boolean type: Boolean,
}, },
query: { query: {
type: [Object, String] as PropType<string | LocationQuery> type: [Object, String] as PropType<string | LocationQuery>,
} },
}, },
setup(props, { emit, slots }) { setup(props, { emit, slots }) {
const router = useRouter() const router = useRouter()
const routeQuery = computed(() => { const routeQuery = computed(() => {
if (typeof props.query == "string") { if (typeof props.query == 'string') {
return parseQuery(props.query) return parseQuery(props.query)
} }
return props.query ?? {} return props.query ?? {}
@@ -43,81 +43,86 @@ export default defineComponent({
val = decodeURIComponent(val.toString()) val = decodeURIComponent(val.toString())
router.push({ path: val, query: routeQuery.value }) router.push({ path: val, query: routeQuery.value })
} }
emit("update:modelValue", val) emit('update:modelValue', val)
emit("change", val) emit('change', val)
} },
}) })
return () => { return () => {
return ( return (
<HiSelection v-model={modelValue.value} class={{ "i-tabs": true, "vertical": !!props.vertical }} activeClass="active" itemClass={["i-tab cursor-pointer"].concat(props.itemClass)}> <HiSelection
{renderSlot(slots, "default")} 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> </HiSelection>
) )
} }
} },
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
.i-tabs { .i-tabs {
display: flex; display: flex;
list-style: none; list-style: none;
align-items: flex-end; align-items: flex-end;
border-bottom: 1px solid #4f4838; border-bottom: 1px solid #4f4838;
width: 100%; width: 100%;
font-size: 14px; font-size: 14px;
color: #c6b083;
margin: 0;
padding: 0;
a {
text-decoration: none;
color: #c6b083; color: #c6b083;
margin: 0; &:visited {
padding: 0; color: currentColor;
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;
}
} }
} }
&.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> </style>
+10 -7
View File
@@ -1,24 +1,27 @@
<script lang="tsx"> <script lang="tsx">
import { defineComponent } from "vue" import { defineComponent } from 'vue'
import { NTooltip } from "@/components/base" import { NTooltip } from '@/components/base'
import { tooltipProps } from "@/components/base/tooltip" import { tooltipProps } from '@/components/base/tooltip'
export default defineComponent({ export default defineComponent({
name: "ITooltip", name: 'ITooltip',
components: { NTooltip }, components: { NTooltip },
props: { props: {
...tooltipProps ...tooltipProps,
}, },
setup(props, { slots }) { setup(props, { slots }) {
return () => { return () => {
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} {slots}
</n-tooltip> </n-tooltip>
) )
} }
} },
}) })
</script> </script>
+42 -30
View File
@@ -1,55 +1,55 @@
<script lang="tsx"> <script lang="tsx">
import { HiSelection, selectionProps } from "hoci" import { HiSelection, selectionProps } from 'hoci'
import { useVModel } from "@vueuse/core" import { useVModel } from '@vueuse/core'
import type { PropType } from "vue"; import type { PropType } from 'vue'
import { computed, defineComponent, renderList, watch } from "vue" import { computed, defineComponent, renderList, watch } from 'vue'
import CalcTreeNode from "./node.vue" import CalcTreeNode from './node.vue'
import type { TreeNode } from "./types" import type { TreeNode } from './types'
import { labelPropType, valuePropType } from "@/components/hooks/types" import { labelPropType, valuePropType } from '@/components/hooks/types'
const Tree = defineComponent({ const Tree = defineComponent({
name: "CalcTree", name: 'CalcTree',
props: { props: {
...selectionProps, ...selectionProps,
modelValue: { modelValue: {
type: valuePropType, type: valuePropType,
default: () => null default: () => null,
}, },
activeItem: { activeItem: {
type: Object as PropType<TreeNode>, type: Object as PropType<TreeNode>,
default: () => null default: () => null,
}, },
idKey: { idKey: {
type: String, type: String,
default: "id" default: 'id',
}, },
label: { label: {
type: labelPropType type: labelPropType,
}, },
data: { data: {
type: Array as PropType<TreeNode[]>, type: Array as PropType<TreeNode[]>,
default: () => [] default: () => [],
}, },
depth: { depth: {
type: [Number, String] as PropType<number>, type: [Number, String] as PropType<number>,
default: () => 0 default: () => 0,
}, },
depthStyle: { depthStyle: {
type: Function as PropType<(depth: number) => string> type: Function as PropType<(depth: number) => string>,
} },
}, },
setup(props, { emit }) { setup(props, { emit }) {
const modelValue = useVModel(props, "modelValue") const modelValue = useVModel(props, 'modelValue')
function onSelect(item?: TreeNode) { function onSelect(item?: TreeNode) {
if (item) { if (item) {
emit("select", item) emit('select', item)
item.onSelect?.() item.onSelect?.()
} }
} }
function loadItem(total: TreeNode[], items: TreeNode[]) { function loadItem(total: TreeNode[], items: TreeNode[]) {
items?.forEach(item => { items?.forEach((item) => {
if (item.children) { if (item.children) {
loadItem(total, item.children) loadItem(total, item.children)
} }
@@ -62,31 +62,43 @@ const Tree = defineComponent({
return loadItem([], props.data) return loadItem([], props.data)
}) })
watch(modelValue, val => { watch(modelValue, (val) => {
val = val ?? props.defaultValue val = val ?? props.defaultValue
const item = children.value.find(item => item.value === val) const item = children.value.find((item) => item.value === val)
onSelect(item) onSelect(item)
}) })
return () => { return () => {
return ( return (
<HiSelection {...props} activeClass="i-tree-node-active" v-model={modelValue.value} class="i-tree"> <HiSelection
{renderList(props.data, item => { {...props}
return <CalcTreeNode {...item} onSelect={onSelect} depth-style={props.depthStyle} depth={props.depth} /> 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> </HiSelection>
) )
} }
} },
}) })
export default Tree export default Tree
</script> </script>
<style> <style>
.i-tree { .i-tree {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
} }
</style> </style>
+94 -85
View File
@@ -1,34 +1,34 @@
<script lang="tsx"> <script lang="tsx">
import { HiItem } from "hoci" import { HiItem } from 'hoci'
import type { PropType } from "vue"; import type { PropType } from 'vue'
import { Transition, computed, defineComponent, ref, renderList } from "vue" import { Transition, computed, defineComponent, ref, renderList } from 'vue'
import type { TreeNode } from "./types" import type { TreeNode } from './types'
const NTreeNode = defineComponent({ const NTreeNode = defineComponent({
props: { props: {
label: { label: {
type: String, type: String,
default: "" default: '',
}, },
value: { value: {
type: [String, Number, Boolean, Object, Array] as PropType<any>, type: [String, Number, Boolean, Object, Array] as PropType<any>,
default: () => null default: () => null,
}, },
children: { children: {
type: Array as PropType<TreeNode[]>, type: Array as PropType<TreeNode[]>,
default: () => [] default: () => [],
}, },
depth: { depth: {
type: [String, Number] as PropType<number>, type: [String, Number] as PropType<number>,
default: 0 default: 0,
}, },
depthStyle: { depthStyle: {
type: Function as PropType<(depth: number) => string> type: Function as PropType<(depth: number) => string>,
}, },
depthOffset: { depthOffset: {
type: String, type: String,
default: "12px" default: '12px',
} },
}, },
setup(props, { emit }) { setup(props, { emit }) {
const isOpen = ref(false) const isOpen = ref(false)
@@ -39,7 +39,7 @@ const NTreeNode = defineComponent({
} }
function onSelect(item: TreeNode) { function onSelect(item: TreeNode) {
return emit("select", item) return emit('select', item)
} }
const depthStyle = computed(() => { const depthStyle = computed(() => {
@@ -51,109 +51,118 @@ const NTreeNode = defineComponent({
return () => ( return () => (
<div class="w-full i-tree-item relative"> <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-active-mask"></div>
<div class="top-0 right-0 bottom-0 left-0 absolute i-tree-item-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> <div class="pl-1">{props.label}</div>
</HiItem> </HiItem>
<Transition name="dropdown" mode="out-in"> <Transition name="dropdown" mode="out-in">
<div v-show={isOpen.value}> <div v-show={isOpen.value}>
{renderList(props.children, item => { {renderList(props.children, (item) => {
return <NTreeNode onSelect={onSelect} {...item} depth={Number(props.depth) + 1} /> return <NTreeNode onSelect={onSelect} {...item} depth={Number(props.depth) + 1} />
})} })}
</div> </div>
</Transition> </Transition>
</div> </div>
) )
} },
}) })
export default NTreeNode export default NTreeNode
</script> </script>
<style lang="scss"> <style lang="scss">
.i-tree-item { .i-tree-item {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
cursor: pointer; cursor: pointer;
border: 1px solid transparent; border: 1px solid transparent;
background-color: black; background-color: black;
} }
.i-tree-item-label { .i-tree-item-label {
flex: 1; flex: 1;
height: 16px; height: 16px;
min-height: 16px; min-height: 16px;
color: #ddc593; color: #ddc593;
position: relative; position: relative;
&:hover { &:hover {
.i-tree-item-mask { .i-tree-item-mask {
background: url("./img/item_hover.png") no-repeat; background: url('./img/item_hover.png') no-repeat;
background-size: 100% 100%; 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%;
}
} }
} }
.icon-collapse-up { &.i-tree-node-active {
background-image: url("./img/collapse_up.png"); .i-tree-item-active-mask {
background-size: 100% 100%; background: url('./img/item_active.png') no-repeat;
background-repeat: no-repeat; background-size: 100% 100%;
background-position: center; }
min-width: 13px !important;
width: 13px !important;
height: 13px !important;
} }
}
.icon-collapse-down { .icon-collapse-up {
background-image: url("./img/collapse_down.png"); background-image: url('./img/collapse_up.png');
background-size: 100% 100%; background-size: 100% 100%;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
min-width: 13px !important; min-width: 13px !important;
width: 13px !important; width: 13px !important;
height: 13px !important; height: 13px !important;
} }
.i-tree-dropdown { .icon-collapse-down {
width: 100%; background-image: url('./img/collapse_down.png');
max-height: 0; background-size: 100% 100%;
overflow: hidden; background-repeat: no-repeat;
background-color: #fff; background-position: center;
border-radius: 0.5rem; min-width: 13px !important;
box-shadow: 0 0 0.5rem 0 rgba(0, 0, 0, 0.1); width: 13px !important;
transition: all 0.3s ease; height: 13px !important;
} }
.dropdown-active { .i-tree-dropdown {
animation: dropdown-enter 0.2s ease-in; 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 { .dropdown-active {
animation: dropdown-leave 0.2s ease-in; animation: dropdown-enter 0.2s ease-in;
} }
.dropdown-enter-active { .dropdown-active {
animation: dropdown-enter 0.2s ease-in; animation: dropdown-leave 0.2s ease-in;
} }
.dropdown-leave-active { .dropdown-enter-active {
animation: dropdown-leave 0.2s ease-in; animation: dropdown-enter 0.2s ease-in;
} }
.dropdown-enter-active { .dropdown-leave-active {
animation: dropdown-enter 0.2s ease-in; animation: dropdown-leave 0.2s ease-in;
} }
.dropdown-leave-active { .dropdown-enter-active {
animation: dropdown-leave 0.2s ease-in; animation: dropdown-enter 0.2s ease-in;
} }
.dropdown-leave-active {
animation: dropdown-leave 0.2s ease-in;
}
</style> </style>
@@ -36,11 +36,10 @@ const infoStyle = (part: string) => {
// x += 179 // x += 179
// index -= 5 // index -= 5
// } // }
if(part == '宠物'){ if (part == '宠物') {
x+= 5*39 x += 5 * 39
y+= 1*40 y += 1 * 40
} } else if (part.includes('宠物装备')) {
else if (part.includes('宠物装备')) {
x += 6 * 39 x += 6 * 39
y += (index - infoStore.avatarParts.findIndex((p) => p == '宠物装备-红')) * 40 y += (index - infoStore.avatarParts.findIndex((p) => p == '宠物装备-红')) * 40
} else { } else {
@@ -63,7 +62,7 @@ const choosePart = (part: string) => {
const partName = (part: string) => { const partName = (part: string) => {
if (part == '武器装扮') return '武器' if (part == '武器装扮') return '武器'
if(part == '快捷栏装备') return '快捷' if (part == '快捷栏装备') return '快捷'
return part.replace('宠物装备-', '') return part.replace('宠物装备-', '')
} }
</script> </script>
@@ -1,20 +1,21 @@
<template> <template>
<template v-if="props.equipment"> <template v-if="props.equipment">
<div class="relative" :class="{inactive: props.inactive}"> <div class="relative" :class="{ inactive: props.inactive }">
<div class="w-28px h-28px " :style="{ backgroundImage: `url(${getImageURL(props.equipment?.imageUrl)})` }"> <div
<img :src="getLocalImageURL(rarity)" /> class="w-28px h-28px"
</div> :style="{ backgroundImage: `url(${getImageURL(props.equipment?.imageUrl)})` }"
>
<img :src="getLocalImageURL(rarity)" />
</div>
</div> </div>
</template> </template>
<template v-else> <template v-else>
<div class="w-26px h-26px border-1px border-solid border-#7a7a7a/50"></div> <div class="w-26px h-26px border-1px border-solid border-#7a7a7a/50"></div>
</template> </template>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { IEquipment } from '@/api/info/type'; import type { IEquipment } from '@/api/info/type'
import { getImageURL } from '@/utils/images' import { getImageURL } from '@/utils/images'
const props = defineProps<{ const props = defineProps<{
@@ -22,8 +23,8 @@ const props = defineProps<{
inactive?: boolean inactive?: boolean
}>() }>()
const rarity = computed(()=>{ const rarity = computed(() => {
switch(props.equipment?.rarity){ switch (props.equipment?.rarity) {
case '稀有': case '稀有':
return 'rare' return 'rare'
case '神器': case '神器':
@@ -39,14 +40,13 @@ const rarity = computed(()=>{
} }
}) })
const getLocalImageURL = (rarity:string)=>{ const getLocalImageURL = (rarity: string) => {
return new URL(`./rarity/${rarity}.png`, import.meta.url).href return new URL(`./rarity/${rarity}.png`, import.meta.url).href
} }
</script>
</script>`` ``
<style lang="scss" scoped> <style lang="scss" scoped>
// @keyframes primeval { // @keyframes primeval {
// @for $i from 0 through 29 { // @for $i from 0 through 29 {
// #{percentage($i / 29)} { // #{percentage($i / 29)} {
@@ -78,5 +78,4 @@ const getLocalImageURL = (rarity:string)=>{
// filter: grayscale(100%); // filter: grayscale(100%);
} }
} }
</style> </style>
@@ -17,7 +17,7 @@
<Icon :equipment="props.equipment"></Icon> <Icon :equipment="props.equipment"></Icon>
</div> </div>
<div style="line-height: 15px"> <div style="line-height: 15px">
<div :class="[rarity,'pl-5px']">{{ props.equipment?.name }}</div> <div :class="[rarity, 'pl-5px']">{{ props.equipment?.name }}</div>
<div class="pl-5px text-#3ea74e">{{ props.equipment?.categorize }}</div> <div class="pl-5px text-#3ea74e">{{ props.equipment?.categorize }}</div>
</div> </div>
</div> </div>
@@ -28,29 +28,35 @@
<div :class="rarity">{{ props.equipment?.rarity }}</div> <div :class="rarity">{{ props.equipment?.rarity }}</div>
<div class="text-hex-7C6430">{{ props.equipment?.itemDetailType }}</div> <div class="text-hex-7C6430">{{ props.equipment?.itemDetailType }}</div>
</div> </div>
<!-- 装备基础信息 --> <!-- 装备基础信息 -->
<div class="divide"></div> <div class="divide"></div>
<div class="flex flex-col items-start my-1px" style="white-space: pre-wrap; line-height: 15px"> <div
<div class="text-#908150" v-html="mainAttr"></div> class="flex flex-col items-start my-1px"
style="white-space: pre-wrap; line-height: 15px"
>
<div class="text-#908150" v-html="mainAttr"></div>
</div> </div>
<!-- 装备属性 --> <!-- 装备属性 -->
<div class="divide"></div> <div class="divide"></div>
<div class="flex flex-col items-start my-1px" style="white-space: pre-wrap; line-height: 15px"> <div
<div class="text-#DED29A" v-html="detail"></div> class="flex flex-col items-start my-1px"
style="white-space: pre-wrap; line-height: 15px"
>
<div class="text-#DED29A" v-html="detail"></div>
</div> </div>
<template v-if="bufferDetail"> <template v-if="bufferDetail">
<div class="divide"></div> <div class="divide"></div>
<div class="flex flex-col items-start my-1px" style="white-space: pre-wrap; line-height: 15px"> <div
class="flex flex-col items-start my-1px"
style="white-space: pre-wrap; line-height: 15px"
>
<div class="text-#A48B2E mb-10px">辅助职业专属属性</div> <div class="text-#A48B2E mb-10px">辅助职业专属属性</div>
<div class="text-#DED29A" v-html="bufferDetail"></div> <div class="text-#DED29A" v-html="bufferDetail"></div>
</div> </div>
</template> </template>
</div> </div>
</div> </div>
<!-- 奶系专属 --> <!-- 奶系专属 -->
</template> </template>
@@ -65,29 +71,44 @@ const props = defineProps<{
const rarity = computed(() => rarityClass(props.equipment?.rarity ?? '')) const rarity = computed(() => rarityClass(props.equipment?.rarity ?? ''))
const mainAttr = computed(()=>{ const mainAttr = computed(() => {
const attrs :(keyof IEquipment)[] = ['STR', 'INT', 'Vitality', 'Spirit','AtkP','AtkM','AtkI','SkillAttack','Attack','Buffer'] const attrs: (keyof IEquipment)[] = [
let info = "" 'STR',
'INT',
'Vitality',
'Spirit',
'AtkP',
'AtkM',
'AtkI',
'SkillAttack',
'Attack',
'Buffer',
]
let info = ''
for (const attr of attrs) { for (const attr of attrs) {
const item = props.equipment?.[attr] as number[] const item = props.equipment?.[attr] as number[]
if (item?.[0]) { if (item?.[0]) {
info += `${formatAttr(attr,item?.[0])}\n` info += `${formatAttr(attr, item?.[0])}\n`
} }
} }
return info; return info
}) })
const detail = computed(()=>props.equipment?.detail.replace(/\[([^\[\]技能]+?)\]/g, '<span style="color:#3d9147">[$1]</span>')) const detail = computed(() =>
props.equipment?.detail.replace(
/\[([^\[\]技能]+?)\]/g,
'<span style="color:#3d9147">[$1]</span>',
),
)
const bufferDetail = computed(()=> const bufferDetail = computed(() => {
{
const detail = props.equipment?.bufferDetail const detail = props.equipment?.bufferDetail
if(!detail || detail == '-') return undefined if (!detail || detail == '-') return undefined
return props.equipment?.bufferDetail.replace(/\[([^\[\]技能]+?)\]/g, '<span style="color:#3d9147">[$1]</span>') return props.equipment?.bufferDetail.replace(
} ) /\[([^\[\]技能]+?)\]/g,
'<span style="color:#3d9147">[$1]</span>',
)
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -22,16 +22,14 @@
</template> </template>
<div <div
v-if="props.withSubWeapon" v-if="props.withSubWeapon"
class="pos-absolute text-white/50 w-26px h-26px border-1px border-solid border-#7a7a7a/50 flex items-center justify-center" class="pos-absolute text-white/50 w-26px h-26px border-1px border-solid border-#7a7a7a/50 flex items-center justify-center"
style="left:150px;top:5px;z-index:8;" style="left: 150px; top: 5px; z-index: 8"
@click="choosePart('副武器')" @click="choosePart('副武器')"
> >
<template v-if="detail?.['副武器']?.id"> <template v-if="detail?.['副武器']?.id">
<EquipmentIcon :equipment="equ(detail['副武器'].id)" /> <EquipmentIcon :equipment="equ(detail['副武器'].id)" />
</template> </template>
<template v-else> <template v-else> 副武 </template>
副武
</template>
</div> </div>
</div> </div>
</template> </template>
@@ -58,10 +56,10 @@ const props = defineProps({
detail: { detail: {
type: Object as PropType<Record<string, IConfigEquip>>, type: Object as PropType<Record<string, IConfigEquip>>,
}, },
withSubWeapon:{ withSubWeapon: {
type: Boolean, type: Boolean,
default: false, default: false,
} },
}) })
const partModelValue = useVModel(props, 'part') const partModelValue = useVModel(props, 'part')
const infoStore = useInfoStore() const infoStore = useInfoStore()
@@ -1,11 +1,16 @@
<template> <template>
<calc-cascader class="!h-5 flex-1" v-model="selected" :items="items" placeholder="请输入名称搜索"></calc-cascader> <calc-cascader
class="!h-5 flex-1"
v-model="selected"
:items="items"
placeholder="请输入名称搜索"
></calc-cascader>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { TreeNode } from "@/components/calc/tree/types" import type { TreeNode } from '@/components/calc/tree/types'
import { useInfoStore } from "@/stores" import { useInfoStore } from '@/stores'
import { syncRef, useVModel } from "@vueuse/core" import { syncRef, useVModel } from '@vueuse/core'
const props = defineProps({ const props = defineProps({
partList: { partList: {
@@ -24,9 +29,9 @@ const else_parts = ['称号', '宠物']
const emit = defineEmits([]) const emit = defineEmits([])
const choose_part = useVModel(props, "partList",emit,{passive:true}) const choose_part = useVModel(props, 'partList', emit, { passive: true })
const selected = ref("") const selected = ref('')
const infoStore = useInfoStore() const infoStore = useInfoStore()
@@ -1,50 +1,56 @@
<template> <template>
<div class="flex gap-8px"> <div class="flex gap-8px">
<template v-for="(rarity,_) in rarities" :key="_"> <template v-for="(rarity, _) in rarities" :key="_">
<div class="w-14px h-14px shadow-[0px_0px_3px_#ffffff_inset,0px_0px_1px_#ffffff40]" :style="{background: rarity.background}" @click="choose(rarity.value)"> <div
<div class="w-14px h-14px" :class="{'active': value === rarity.value}"></div> class="w-14px h-14px shadow-[0px_0px_3px_#ffffff_inset,0px_0px_1px_#ffffff40]"
:style="{ background: rarity.background }"
@click="choose(rarity.value)"
>
<div class="w-14px h-14px" :class="{ active: value === rarity.value }"></div>
</div> </div>
</template> </template>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
const props = defineProps({}) const props = defineProps({})
const value = defineModel<string>() const value = defineModel<string>()
const rarities = [{ const rarities = [
value: '稀有', {
background: '#d06eff', value: '稀有',
},{ background: '#d06eff',
value: '神器', },
background: '#fe11ff', {
},{ value: '神器',
value: '传说', background: '#fe11ff',
background: '#fc8711', },
},{ {
value: '史诗', value: '传说',
background: '#feca11', background: '#fc8711',
},{ },
value: '太初', {
background: 'linear-gradient(180deg, #70faa7 0%, #7bfefe 100%)', value: '史诗',
}] background: '#feca11',
},
{
value: '太初',
background: 'linear-gradient(180deg, #70faa7 0%, #7bfefe 100%)',
},
]
const choose = (rarity: string) => { const choose = (rarity: string) => {
if (value.value === rarity) { if (value.value === rarity) {
value.value = undefined value.value = undefined
} } else {
else{
value.value = rarity value.value = rarity
} }
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.active {
.active{
background: url('@/assets/img/control/item_checked.png') no-repeat center; background: url('@/assets/img/control/item_checked.png') no-repeat center;
background-size: 12px 10px; background-size: 12px 10px;
} }
@@ -13,8 +13,13 @@
" "
> >
<!-- 装备图标 装备名称 --> <!-- 装备图标 装备名称 -->
<div class="flex w-full items-center justify-center gap-5px" > <div class="flex w-full items-center justify-center gap-5px">
<div><img :src="getImageURL('equipment' + props.suit?.imageUrl.replace('/suit/','/suit/icon/'))" alt="装备图标"></div> <div>
<img
:src="getImageURL('equipment' + props.suit?.imageUrl.replace('/suit/', '/suit/icon/'))"
alt="装备图标"
/>
</div>
<div :class="rarity">{{ props.suit?.name }}</div> <div :class="rarity">{{ props.suit?.name }}</div>
<div :class="rarity">{{ props.suit?.point }}点</div> <div :class="rarity">{{ props.suit?.point }}点</div>
</div> </div>
@@ -42,7 +47,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { IEquipment, ISuit } from '@/api/info/type' import type { IEquipment, ISuit } from '@/api/info/type'
import {getImageURL} from '@/utils/images' import { getImageURL } from '@/utils/images'
import { formatAttr, rarityClass } from '@/utils' import { formatAttr, rarityClass } from '@/utils'
const props = defineProps<{ const props = defineProps<{
+50 -30
View File
@@ -1,10 +1,10 @@
import { h, reactive, renderList } from "vue" import { h, reactive, renderList } from 'vue'
import { createSharedComposable } from "@vueuse/core" import { createSharedComposable } from '@vueuse/core'
import ActionDialog from "@/components/calc/action-dialog/index.vue" import ActionDialog from '@/components/calc/action-dialog/index.vue'
import type { JSX } from "vue/jsx-runtime" import type { JSX } from 'vue/jsx-runtime'
export type ConfirmDialogOption = Omit<ShowDialogOption, "okButton" | "cancelButton"> export type ConfirmDialogOption = Omit<ShowDialogOption, 'okButton' | 'cancelButton'>
export type AlertDialogOption = Omit<ShowDialogOption, "okButton" | "cancelButton"> export type AlertDialogOption = Omit<ShowDialogOption, 'okButton' | 'cancelButton'>
export type ElementLike = JSX.Element | string | ElementLike[] export type ElementLike = JSX.Element | string | ElementLike[]
function randomId() { function randomId() {
@@ -54,7 +54,7 @@ export interface ShowDialogOption {
rejectButton?: string | boolean rejectButton?: string | boolean
// 默认状态 // 默认状态
defaultStatus?: DialogResult["status"] defaultStatus?: DialogResult['status']
mask?: boolean mask?: boolean
@@ -70,7 +70,7 @@ export interface DialogInstance {
export interface DialogResult { export interface DialogResult {
id: DialogID id: DialogID
status: "ok" | "reject" | "cancel" | "none" status: 'ok' | 'reject' | 'cancel' | 'none'
data?: any data?: any
readonly isOk: boolean readonly isOk: boolean
readonly isCancel: boolean readonly isCancel: boolean
@@ -87,23 +87,23 @@ export const useDialog = createSharedComposable(() => {
return id return id
} }
function close(id: DialogID, status = "cancel", duration = 400) { function close(id: DialogID, status = 'cancel', duration = 400) {
const dialog = dialogs.get(id) const dialog = dialogs.get(id)
if (!dialog) { if (!dialog) {
return return
} }
dialog.resolve({ dialog.resolve({
id, id,
status: "ok", status: 'ok',
get isOk() { get isOk() {
return status == "ok" return status == 'ok'
}, },
get isCancel() { get isCancel() {
return status == "cancel" return status == 'cancel'
}, },
get isReject() { get isReject() {
return status == "reject" return status == 'reject'
} },
}) })
dialog.visible = false dialog.visible = false
const fn = () => dialogs.delete(id) const fn = () => dialogs.delete(id)
@@ -115,14 +115,27 @@ export const useDialog = createSharedComposable(() => {
} }
function render() { function render() {
return renderList(dialogs.values(), e => e.render(e.visible)) return renderList(dialogs.values(), (e) => e.render(e.visible))
} }
async function show(option: ShowDialogOption = {}) { async function show(option: ShowDialogOption = {}) {
return new Promise<DialogResult>(resolve => { return new Promise<DialogResult>((resolve) => {
const { id = randomId(), header = true, content, action, timeout, okButton = false, cancelButton = false, rejectButton = false, defaultStatus = "none", duration = 400, mask = false, scale = 1.0 } = option const {
id = randomId(),
header = true,
content,
action,
timeout,
okButton = false,
cancelButton = false,
rejectButton = false,
defaultStatus = 'none',
duration = 400,
mask = false,
scale = 1.0,
} = option
const onClose = (status: DialogResult["status"] = defaultStatus) => { const onClose = (status: DialogResult['status'] = defaultStatus) => {
close(id, status, duration) close(id, status, duration)
} }
@@ -141,23 +154,30 @@ export const useDialog = createSharedComposable(() => {
visible, visible,
onClose, onClose,
mask, mask,
scale scale,
}, },
{ {
default() { default() {
switch (typeof content) { switch (typeof content) {
case "function": case 'function':
return content() return content()
case "string": case 'string':
return h("div", { class: "w-full justify-center text-hex-d4d6b6 text-center", style: "margin:10px" }, content) return h(
'div',
{
class: 'w-full justify-center text-hex-d4d6b6 text-center',
style: 'margin:10px',
},
content,
)
default: default:
return content return content
} }
}, },
action: typeof action == "function" || !action ? action : () => action action: typeof action == 'function' || !action ? action : () => action,
} },
) )
} },
}) })
if (timeout) { if (timeout) {
@@ -169,18 +189,18 @@ export const useDialog = createSharedComposable(() => {
async function alert(option: AlertDialogOption = {}) { async function alert(option: AlertDialogOption = {}) {
return show({ return show({
...option, ...option,
defaultStatus: "ok", defaultStatus: 'ok',
okButton: true, okButton: true,
cancelButton: false cancelButton: false,
}) })
} }
async function confirm(option: ConfirmDialogOption = {}) { async function confirm(option: ConfirmDialogOption = {}) {
return show({ return show({
...option, ...option,
defaultStatus: "cancel", defaultStatus: 'cancel',
okButton: true, okButton: true,
cancelButton: true cancelButton: true,
}) })
} }
return { return {
@@ -189,6 +209,6 @@ export const useDialog = createSharedComposable(() => {
render, render,
show, show,
close, close,
randomId randomId,
} }
}) })
+6 -6
View File
@@ -1,8 +1,8 @@
import type { Fn } from "@vueuse/core" import type { Fn } from '@vueuse/core'
import type { InjectionKey, Ref } from "vue"; import type { InjectionKey, Ref } from 'vue'
import { inject, provide, ref, watch } from "vue" import { inject, provide, ref, watch } from 'vue'
const IsOpenSymbol: InjectionKey<Ref<boolean>> = Symbol("is-open") const IsOpenSymbol: InjectionKey<Ref<boolean>> = Symbol('is-open')
export function isShow() { export function isShow() {
return inject(IsOpenSymbol, ref(false)) return inject(IsOpenSymbol, ref(false))
@@ -15,12 +15,12 @@ interface OnShowOption {
export function onShow(fn: Fn, { immediate = false }: OnShowOption = {}) { export function onShow(fn: Fn, { immediate = false }: OnShowOption = {}) {
return watch( return watch(
isShow(), isShow(),
val => { (val) => {
if (val) { if (val) {
fn() fn()
} }
}, },
{ immediate } { immediate },
) )
} }
+4 -2
View File
@@ -1,4 +1,4 @@
import type { PropType } from "vue" import type { PropType } from 'vue'
export type ClassType = string | string[] export type ClassType = string | string[]
@@ -8,4 +8,6 @@ export const valuePropType = [String, Number, Object, Boolean, Array] as PropTyp
export const classPropType = [String, Array] as PropType<string | string[]> export const classPropType = [String, Array] as PropType<string | string[]>
export const labelPropType = [String, Function] as PropType<string | ((val?: BaseType) => string) | null> export const labelPropType = [String, Function] as PropType<
string | ((val?: BaseType) => string) | null
>
+1 -1
View File
@@ -1,7 +1,7 @@
import '@/style/index.scss' import '@/style/index.scss'
import { createApp } from 'vue' import { createApp } from 'vue'
import { pinia } from "@/stores" import { pinia } from '@/stores'
import App from '@/pages/IndexView.vue' import App from '@/pages/IndexView.vue'
@@ -1,34 +1,34 @@
<template> <template>
<div class="mx-10px"> <div class="mx-10px">
<calc-collapse title="武器/称号/宠物/秘宝"> <calc-collapse title="武器/称号/宠物/秘宝">
<calc-tabs v-model="anotherTab" class="!mb-2"> <calc-tabs v-model="anotherTab" class="!mb-2">
<calc-tab :value="0">武器</calc-tab> <calc-tab :value="0">武器</calc-tab>
<calc-tab :value="1">称号/宠物/秘宝</calc-tab> <calc-tab :value="1">称号/宠物/秘宝</calc-tab>
<calc-tab :value="2" v-if="infoStore.infos?.alter.includes('vegabond')">副武器</calc-tab> <calc-tab :value="2" v-if="infoStore.infos?.alter.includes('vegabond')">副武器</calc-tab>
</calc-tabs> </calc-tabs>
<Weapon v-show="anotherTab === 0"></Weapon> <Weapon v-show="anotherTab === 0"></Weapon>
<Else v-show="anotherTab === 1"></Else> <Else v-show="anotherTab === 1"></Else>
<SubWeapon v-show="anotherTab === 2"></SubWeapon> <SubWeapon v-show="anotherTab === 2"></SubWeapon>
</calc-collapse> </calc-collapse>
<calc-collapse title="套装/通宝"> <calc-collapse title="套装/通宝">
<calc-tabs v-model="equTab" class="!mb-2"> <calc-tabs v-model="equTab" class="!mb-2">
<calc-tab :value="0">套装</calc-tab> <calc-tab :value="0">套装</calc-tab>
<calc-tab :value="1">通宝</calc-tab> <calc-tab :value="1">通宝</calc-tab>
</calc-tabs> </calc-tabs>
<Suit v-show="equTab === 0"></Suit> <Suit v-show="equTab === 0"></Suit>
<Universal v-show="equTab === 1"></Universal> <Universal v-show="equTab === 1"></Universal>
</calc-collapse> </calc-collapse>
<calc-collapse title="融合石"> <calc-collapse title="融合石">
<calc-tabs v-model="stoneTab" class="!mb-2"> <calc-tabs v-model="stoneTab" class="!mb-2">
<calc-tab :value="0">套装</calc-tab> <calc-tab :value="0">套装</calc-tab>
<calc-tab :value="1">通宝</calc-tab> <calc-tab :value="1">通宝</calc-tab>
<calc-tab :value="2">其他</calc-tab> <calc-tab :value="2">其他</calc-tab>
</calc-tabs> </calc-tabs>
<Stone v-show="stoneTab == 0"></Stone> <Stone v-show="stoneTab == 0"></Stone>
<StoneUniversal v-show="stoneTab == 1"></StoneUniversal> <StoneUniversal v-show="stoneTab == 1"></StoneUniversal>
<StoneOld v-show="stoneTab == 2"></StoneOld> <StoneOld v-show="stoneTab == 2"></StoneOld>
</calc-collapse> </calc-collapse>
</div> </div>
</template> </template>
<script lang="ts" setup name="Index"> <script lang="ts" setup name="Index">
@@ -46,7 +46,4 @@ const infoStore = useInfoStore()
const equTab = ref(0) const equTab = ref(0)
const stoneTab = ref(0) const stoneTab = ref(0)
const anotherTab = ref(0) const anotherTab = ref(0)
</script> </script>
@@ -3,7 +3,11 @@
<template> <template>
<div class="flex flex-wrap gap-4px content-start" :style="{ width: `${rowCount * 32}px` }"> <div class="flex flex-wrap gap-4px content-start" :style="{ width: `${rowCount * 32}px` }">
<template v-for="(equ, _) in equs" :key="_"> <template v-for="(equ, _) in equs" :key="_">
<EquipmentIcon :equipment="equ" :inactive="configStore.config.equips[equ?.itemDetailType ?? '']?.id != equ?.id" @click="configStore.chooseEqu(equ)"/> <EquipmentIcon
:equipment="equ"
:inactive="configStore.config.equips[equ?.itemDetailType ?? '']?.id != equ?.id"
@click="configStore.chooseEqu(equ)"
/>
</template> </template>
</div> </div>
</template> </template>
@@ -23,30 +27,23 @@ const infoStore = useInfoStore()
const configStore = useConfigStore() const configStore = useConfigStore()
const equs = computed(() => { const equs = computed(() => {
const items = infoStore.equips.filter( const items = infoStore.equips.filter((equip) => ['称号', '宠物'].includes(equip.itemDetailType))
(equip) => ['称号','宠物'].includes(equip.itemDetailType),
)
const res: (IEquipment | undefined)[] = [] const res: (IEquipment | undefined)[] = []
Array.from(new Set(items.map((item) => item.itemDetailType))).forEach((type) => { Array.from(new Set(items.map((item) => item.itemDetailType))).forEach((type) => {
const equList = items.filter((item) => item.itemDetailType === type) const equList = items.filter((item) => item.itemDetailType === type)
equList.sort((a, b) => { equList.sort((a, b) => {
if (a.itemType !== b.itemType) { if (a.itemType !== b.itemType) {
return b.itemType.localeCompare(a.itemType) return b.itemType.localeCompare(a.itemType)
} }
const rarityOrder = ['传说', '神器', '稀有'] const rarityOrder = ['传说', '神器', '稀有']
return rarityOrder.indexOf(a.rarity) - rarityOrder.indexOf(b.rarity) return rarityOrder.indexOf(a.rarity) - rarityOrder.indexOf(b.rarity)
}) })
res.push( res.push(...equList)
...equList
)
const remind = rowCount - (res.length % rowCount) const remind = rowCount - (res.length % rowCount)
res.push(...Array(remind + rowCount).fill(undefined)) res.push(...Array(remind + rowCount).fill(undefined))
}) })
const treasure = infoStore.equips.filter( const treasure = infoStore.equips.filter((equip) => equip.categorize.includes('秘宝'))
(equip) => equip.categorize.includes('秘宝'), res.push(...treasure, ...Array(rowCount - treasure.length).fill(undefined))
)
res.push(...treasure,...Array(rowCount-treasure.length).fill(undefined))
return res return res
}) })
</script> </script>
@@ -3,35 +3,39 @@
<template> <template>
<div> <div>
<div class="flex flex-col gap-10px pb-10px"> <div class="flex flex-col gap-10px pb-10px">
<!-- 套装选择tab --> <!-- 套装选择tab -->
<div class="flex flex-wrap w-100%"> <div class="flex flex-wrap w-100%">
<template v-for="(suit, _) in suits" :key="_"> <template v-for="(suit, _) in suits" :key="_">
<div class="flex items-center justify-center"> <div class="flex items-center justify-center">
<img <img
class="w-35px h-35px object-contain" class="w-35px h-35px object-contain"
:src="getImageURL('equipment' + suit.imageUrl)" :src="getImageURL('equipment' + suit.imageUrl)"
:alt="suit.name" :alt="suit.name"
:style="{ filter: curSuit === suit.value ? '' : 'grayscale(100%)' }" :style="{ filter: curSuit === suit.value ? '' : 'grayscale(100%)' }"
@click="curSuit = suit.value" @click="curSuit = suit.value"
@dblclick="handleDoubleClick" @dblclick="handleDoubleClick"
/>
</div>
</template>
</div>
<EquipmentRaritySelect v-model="curRarity" />
</div>
<div class="flex flex-wrap gap-4px content-start" :style="{ width: `${rowCount * 32}px` }">
<template v-for="(equip, _) in suitEquipments" :key="_">
<template v-if="equip?.rarity == curRarity || !curRarity">
<EquipmentIcon
:equipment="equip"
:inactive="configStore.config.equips[equip?.itemDetailType ?? '']?.fusion != equip?.id"
@click="configStore.chooseEqu(equip, true)"
/> />
</div> </template>
<template v-else>
<EquipmentIcon />
</template>
</template> </template>
</div> </div>
<EquipmentRaritySelect v-model="curRarity" />
</div>
<div class="flex flex-wrap gap-4px content-start" :style="{width: `${rowCount * 32}px`}">
<template v-for="(equip,_) in suitEquipments" :key="_">
<template v-if="equip?.rarity == curRarity || !curRarity">
<EquipmentIcon :equipment="equip" :inactive="configStore.config.equips[equip?.itemDetailType ?? '']?.fusion != equip?.id" @click="configStore.chooseEqu(equip,true)"/>
</template>
<template v-else>
<EquipmentIcon/>
</template>
</template>
</div>
</div> </div>
</template> </template>
@@ -48,7 +52,7 @@ const curSuit = ref(16201)
const curRarity = ref() const curRarity = ref()
const props = defineProps<{ const props = defineProps<{
rowCount?:number rowCount?: number
}>() }>()
const rowCount = Math.max(props.rowCount ?? 0, 14) const rowCount = Math.max(props.rowCount ?? 0, 14)
@@ -64,30 +68,30 @@ const suits = computed(() => {
}) })
const suitEquipments = computed(() => { const suitEquipments = computed(() => {
const equs = infoStore.stones.filter((a) => a.suit.includes(curSuit.value.toString()) && a.suit.length === 1 ) const equs = infoStore.stones.filter(
(a) => a.suit.includes(curSuit.value.toString()) && a.suit.length === 1,
)
const res: (IEquipment | undefined)[] = [] const res: (IEquipment | undefined)[] = []
const raritiyList = ['神器','传说','史诗'] const raritiyList = ['神器', '传说', '史诗']
raritiyList.forEach((rarity) => { raritiyList.forEach((rarity) => {
const equ : (IEquipment | undefined)[] = equs.filter((a) => a.rarity === rarity) const equ: (IEquipment | undefined)[] = equs.filter((a) => a.rarity === rarity)
equ.splice(5,0,undefined) equ.splice(5, 0, undefined)
equ.splice(9,0,undefined) equ.splice(9, 0, undefined)
res.push( res.push(...equ, ...Array(Math.max(0, rowCount - equ.length)).fill(undefined))
...equ,
...Array(Math.max(0,rowCount - equ.length)).fill(undefined),
)
}) })
return res return res
}) })
const handleDoubleClick = () => { const handleDoubleClick = () => {
if(!curRarity.value) return if (!curRarity.value) return
if(curRarity.value == '太初') return if (curRarity.value == '太初') return
suitEquipments.value.filter(a=>!!a && a.rarity == curRarity.value).splice(0,11).forEach((a) => { suitEquipments.value
configStore.chooseEqu(a,true) .filter((a) => !!a && a.rarity == curRarity.value)
}) .splice(0, 11)
.forEach((a) => {
configStore.chooseEqu(a, true)
})
} }
</script> </script>
@@ -1,9 +1,13 @@
`<!-- 每排总计6+4+4,共计14个排列 --> `<!-- 每排总计6+4+4,共计14个排列 -->
<template> <template>
<div class="flex flex-wrap gap-4px content-start" :style="{width: `${rowCount * 32}px`}"> <div class="flex flex-wrap gap-4px content-start" :style="{ width: `${rowCount * 32}px` }">
<template v-for="(equip,_) in equs" :key="_"> <template v-for="(equip, _) in equs" :key="_">
<EquipmentIcon :inactive="configStore.config.equips[equip?.itemDetailType ?? '']?.fusion != equip?.id" :equipment="equip" @click="configStore.chooseEqu(equip,true)" /> <EquipmentIcon
:inactive="configStore.config.equips[equip?.itemDetailType ?? '']?.fusion != equip?.id"
:equipment="equip"
@click="configStore.chooseEqu(equip, true)"
/>
</template> </template>
</div> </div>
</template> </template>
@@ -15,7 +19,7 @@ import { getImageURL } from '@/utils/images'
import EquipmentIcon from '@/components/dnf/Equipment/Icon/index.vue' import EquipmentIcon from '@/components/dnf/Equipment/Icon/index.vue'
const props = defineProps<{ const props = defineProps<{
rowCount ?:number rowCount?: number
}>() }>()
const rowCount = Math.max(props.rowCount ?? 0, 14) const rowCount = Math.max(props.rowCount ?? 0, 14)
@@ -23,24 +27,22 @@ const infoStore = useInfoStore()
const configStore = useConfigStore() const configStore = useConfigStore()
const equs = computed(() => { const equs = computed(() => {
const items = infoStore.stones.filter( const items = infoStore.stones.filter((equip) => equip.categorize.includes('神界'))
(equip) => equip.categorize.includes('神界'),
)
const res: (IEquipment | undefined)[] = [] const res: (IEquipment | undefined)[] = []
res.push( res.push(
...items.filter((a) => a.rarity === '太初'), ...items.filter((a) => a.rarity === '太初'),
...Array(Math.max(0,rowCount - items.filter((a) => a.rarity === '太初').length)).fill(undefined), ...Array(Math.max(0, rowCount - items.filter((a) => a.rarity === '太初').length)).fill(
); undefined,
),
)
['手镯','项链','戒指','','辅助装备','魔法石','耳环',''].forEach((cat) => { ;['手镯', '项链', '戒指', '', '辅助装备', '魔法石', '耳环', ''].forEach((cat) => {
const equ: (IEquipment | undefined)[] = items.filter((a) => a.rarity === '史诗' && a.itemDetailType == cat) const equ: (IEquipment | undefined)[] = items.filter(
res.push( (a) => a.rarity === '史诗' && a.itemDetailType == cat,
...equ,
...Array(Math.max(0, 7 - equ.length)).fill(undefined),
) )
res.push(...equ, ...Array(Math.max(0, 7 - equ.length)).fill(undefined))
}) })
// const categorize = Array.from(new Set(items.map((item) => item.categorize))) // const categorize = Array.from(new Set(items.map((item) => item.categorize)))
// raritiyList.forEach((rarity) => { // raritiyList.forEach((rarity) => {
// categorize.forEach((cat) => { // categorize.forEach((cat) => {
@@ -1,9 +1,13 @@
<!-- 每排总计6+4+4,共计14个排列 --> <!-- 每排总计6+4+4,共计14个排列 -->
<template> <template>
<div class="flex flex-wrap gap-4px content-start" :style="{width: `${rowCount * 32}px`}"> <div class="flex flex-wrap gap-4px content-start" :style="{ width: `${rowCount * 32}px` }">
<template v-for="(equip,_) in equs" :key="_"> <template v-for="(equip, _) in equs" :key="_">
<EquipmentIcon :inactive="configStore.config.equips[equip?.itemDetailType ?? '']?.fusion != equip?.id" :equipment="equip" @click="configStore.chooseEqu(equip,true)" /> <EquipmentIcon
:inactive="configStore.config.equips[equip?.itemDetailType ?? '']?.fusion != equip?.id"
:equipment="equip"
@click="configStore.chooseEqu(equip, true)"
/>
</template> </template>
</div> </div>
</template> </template>
@@ -15,7 +19,7 @@ import { getImageURL } from '@/utils/images'
import EquipmentIcon from '@/components/dnf/Equipment/Icon/index.vue' import EquipmentIcon from '@/components/dnf/Equipment/Icon/index.vue'
const props = defineProps<{ const props = defineProps<{
rowCount ?:number rowCount?: number
}>() }>()
const rowCount = Math.max(props.rowCount ?? 0, 14) const rowCount = Math.max(props.rowCount ?? 0, 14)
@@ -23,20 +27,17 @@ const infoStore = useInfoStore()
const configStore = useConfigStore() const configStore = useConfigStore()
const equs = computed(() => { const equs = computed(() => {
const items = infoStore.stones.filter( const items = infoStore.stones.filter((equip) => equip.categorize.includes('通宝'))
(equip) => equip.categorize.includes('通宝'),
)
const res: (IEquipment | undefined)[] = [] const res: (IEquipment | undefined)[] = []
const raritiyList = ['神器','史诗'] const raritiyList = ['神器', '史诗']
const categorize = Array.from(new Set(items.map((item) => item.categorize))) const categorize = Array.from(new Set(items.map((item) => item.categorize)))
raritiyList.forEach((rarity) => { raritiyList.forEach((rarity) => {
categorize.forEach((cat) => { categorize.forEach((cat) => {
const equ : (IEquipment | undefined)[] = items.filter((a) => a.rarity === rarity && a.categorize === cat) const equ: (IEquipment | undefined)[] = items.filter(
equ.splice(5,0,undefined) (a) => a.rarity === rarity && a.categorize === cat,
res.push(
...equ,
...Array(Math.max(0,rowCount - equ.length)).fill(undefined),
) )
equ.splice(5, 0, undefined)
res.push(...equ, ...Array(Math.max(0, rowCount - equ.length)).fill(undefined))
}) })
}) })
return res return res
@@ -3,7 +3,11 @@
<template> <template>
<div class="flex flex-wrap gap-4px content-start" :style="{ width: `${rowCount * 32}px` }"> <div class="flex flex-wrap gap-4px content-start" :style="{ width: `${rowCount * 32}px` }">
<template v-for="(weapon, _) in weapons" :key="_"> <template v-for="(weapon, _) in weapons" :key="_">
<EquipmentIcon :equipment="weapon" :inactive="configStore.config.equips['副武器']?.id != weapon?.id" @click="configStore.chooseEqu(weapon,false,true)"/> <EquipmentIcon
:equipment="weapon"
:inactive="configStore.config.equips['副武器']?.id != weapon?.id"
@click="configStore.chooseEqu(weapon, false, true)"
/>
</template> </template>
</div> </div>
</template> </template>
@@ -40,5 +44,4 @@ const weapons = computed(() => {
}) })
return res return res
}) })
</script> </script>
@@ -3,35 +3,39 @@
<template> <template>
<div> <div>
<div class="flex flex-col gap-10px pb-10px"> <div class="flex flex-col gap-10px pb-10px">
<!-- 套装选择tab --> <!-- 套装选择tab -->
<div class="flex flex-wrap w-100%"> <div class="flex flex-wrap w-100%">
<template v-for="(suit, _) in suits" :key="_"> <template v-for="(suit, _) in suits" :key="_">
<div class="flex items-center justify-center"> <div class="flex items-center justify-center">
<img <img
class="w-35px h-35px object-contain" class="w-35px h-35px object-contain"
:src="getImageURL('equipment' + suit.imageUrl)" :src="getImageURL('equipment' + suit.imageUrl)"
:alt="suit.name" :alt="suit.name"
:style="{ filter: curSuit === suit.value ? '' : 'grayscale(100%)' }" :style="{ filter: curSuit === suit.value ? '' : 'grayscale(100%)' }"
@click="curSuit = suit.value" @click="curSuit = suit.value"
@dblclick="handleDoubleClick" @dblclick="handleDoubleClick"
/>
</div>
</template>
</div>
<EquipmentRaritySelect v-model="curRarity" />
</div>
<div class="flex flex-wrap gap-4px content-start" :style="{ width: `${rowCount * 32}px` }">
<template v-for="(equip, _) in suitEquipments" :key="_">
<template v-if="equip?.rarity == curRarity || !curRarity">
<EquipmentIcon
:equipment="equip"
:inactive="configStore.config.equips[equip?.itemDetailType ?? '']?.id != equip?.id"
@click="configStore.chooseEqu(equip)"
/> />
</div> </template>
<template v-else>
<EquipmentIcon />
</template>
</template> </template>
</div> </div>
<EquipmentRaritySelect v-model="curRarity" />
</div>
<div class="flex flex-wrap gap-4px content-start" :style="{width: `${rowCount * 32}px`}">
<template v-for="(equip,_) in suitEquipments" :key="_">
<template v-if="equip?.rarity == curRarity || !curRarity">
<EquipmentIcon :equipment="equip" :inactive="configStore.config.equips[equip?.itemDetailType ?? '']?.id != equip?.id" @click="configStore.chooseEqu(equip)"/>
</template>
<template v-else>
<EquipmentIcon/>
</template>
</template>
</div>
</div> </div>
</template> </template>
@@ -48,7 +52,7 @@ const curSuit = ref(16201)
const curRarity = ref() const curRarity = ref()
const props = defineProps<{ const props = defineProps<{
rowCount?:number rowCount?: number
}>() }>()
const rowCount = Math.max(props.rowCount ?? 0, 14) const rowCount = Math.max(props.rowCount ?? 0, 14)
@@ -64,30 +68,44 @@ const suits = computed(() => {
}) })
const suitEquipments = computed(() => { const suitEquipments = computed(() => {
const equs = infoStore.equips.filter((a) => a.suit.includes(curSuit.value.toString()) && a.suit.length === 1 ) const equs = infoStore.equips.filter(
(a) => a.suit.includes(curSuit.value.toString()) && a.suit.length === 1,
)
const res: (IEquipment | undefined)[] = [] const res: (IEquipment | undefined)[] = []
const raritiyList = ['神器','传说','史诗'] const raritiyList = ['神器', '传说', '史诗']
raritiyList.forEach((rarity) => { raritiyList.forEach((rarity) => {
const equ : (IEquipment | undefined)[] = equs.filter((a) => a.rarity === rarity && !a.name.includes('黑牙')) const equ: (IEquipment | undefined)[] = equs.filter(
equ.splice(5,0,undefined) (a) => a.rarity === rarity && !a.name.includes('黑牙'),
equ.splice(9,0,undefined)
res.push(
...equ,
...Array(Math.max(0,rowCount - equ.length)).fill(undefined),
) )
equ.splice(5, 0, undefined)
equ.splice(9, 0, undefined)
res.push(...equ, ...Array(Math.max(0, rowCount - equ.length)).fill(undefined))
}) })
const remind = equs.filter(a=>a.rarity == '太初' || a.name.includes('黑牙')).sort((a,b)=>(a.rarity == '太初' ? 1 : 0) - (b.rarity == '太初' ? 1 : 0)) const remind = equs
res.push(undefined,undefined,...remind.filter(a=>a.itemDetailType == '项链'),undefined,...remind.filter(a=>a.itemDetailType == '手镯'),undefined,...remind.filter(a=>a.itemDetailType == '戒指'),...Array(Math.max(0,rowCount - remind.length -4)).fill(undefined)) .filter((a) => a.rarity == '太初' || a.name.includes('黑牙'))
.sort((a, b) => (a.rarity == '太初' ? 1 : 0) - (b.rarity == '太初' ? 1 : 0))
res.push(
undefined,
undefined,
...remind.filter((a) => a.itemDetailType == '项链'),
undefined,
...remind.filter((a) => a.itemDetailType == '手镯'),
undefined,
...remind.filter((a) => a.itemDetailType == '戒指'),
...Array(Math.max(0, rowCount - remind.length - 4)).fill(undefined),
)
return res return res
}) })
const handleDoubleClick = () => { const handleDoubleClick = () => {
if(!curRarity.value) return if (!curRarity.value) return
if(curRarity.value == '太初') return if (curRarity.value == '太初') return
suitEquipments.value.filter(a=>!!a && a.rarity == curRarity.value).splice(0,11).forEach((a) => { suitEquipments.value
configStore.chooseEqu(a) .filter((a) => !!a && a.rarity == curRarity.value)
}) .splice(0, 11)
.forEach((a) => {
configStore.chooseEqu(a)
})
} }
</script> </script>
@@ -1,9 +1,13 @@
<!-- 每排总计6+4+4,共计14个排列 --> <!-- 每排总计6+4+4,共计14个排列 -->
<template> <template>
<div class="flex flex-wrap gap-4px content-start" :style="{width: `${rowCount * 32}px`}"> <div class="flex flex-wrap gap-4px content-start" :style="{ width: `${rowCount * 32}px` }">
<template v-for="(equip,_) in equs" :key="_"> <template v-for="(equip, _) in equs" :key="_">
<EquipmentIcon :inactive="configStore.config.equips[equip?.itemDetailType ?? '']?.id != equip?.id" :equipment="equip" @click="configStore.chooseEqu(equip)" /> <EquipmentIcon
:inactive="configStore.config.equips[equip?.itemDetailType ?? '']?.id != equip?.id"
:equipment="equip"
@click="configStore.chooseEqu(equip)"
/>
</template> </template>
</div> </div>
</template> </template>
@@ -15,7 +19,7 @@ import { getImageURL } from '@/utils/images'
import EquipmentIcon from '@/components/dnf/Equipment/Icon/index.vue' import EquipmentIcon from '@/components/dnf/Equipment/Icon/index.vue'
const props = defineProps<{ const props = defineProps<{
rowCount ?:number rowCount?: number
}>() }>()
const rowCount = Math.max(props.rowCount ?? 0, 14) const rowCount = Math.max(props.rowCount ?? 0, 14)
@@ -27,15 +31,12 @@ const equs = computed(() => {
(equip) => equip.categorize === '通宝装备' || equip.categorize === '稀有装备 套装', (equip) => equip.categorize === '通宝装备' || equip.categorize === '稀有装备 套装',
) )
const res: (IEquipment | undefined)[] = [] const res: (IEquipment | undefined)[] = []
const raritiyList = ['稀有','神器','传说','史诗'] const raritiyList = ['稀有', '神器', '传说', '史诗']
raritiyList.forEach((rarity) => { raritiyList.forEach((rarity) => {
const equ : (IEquipment | undefined)[] = items.filter((a) => a.rarity === rarity) const equ: (IEquipment | undefined)[] = items.filter((a) => a.rarity === rarity)
equ.splice(5,0,undefined) equ.splice(5, 0, undefined)
equ.splice(9,0,undefined) equ.splice(9, 0, undefined)
res.push( res.push(...equ, ...Array(Math.max(0, rowCount - equ.length)).fill(undefined))
...equ,
...Array(Math.max(0,rowCount - equ.length)).fill(undefined),
)
}) })
return res return res
}) })
@@ -3,7 +3,11 @@
<template> <template>
<div class="flex flex-wrap gap-4px content-start" :style="{ width: `${rowCount * 32}px` }"> <div class="flex flex-wrap gap-4px content-start" :style="{ width: `${rowCount * 32}px` }">
<template v-for="(weapon, _) in weapons" :key="_"> <template v-for="(weapon, _) in weapons" :key="_">
<EquipmentIcon :equipment="weapon" :inactive="configStore.config.equips['武器']?.id != weapon?.id" @click="configStore.chooseEqu(weapon)"/> <EquipmentIcon
:equipment="weapon"
:inactive="configStore.config.equips['武器']?.id != weapon?.id"
@click="configStore.chooseEqu(weapon)"
/>
</template> </template>
</div> </div>
</template> </template>
@@ -39,5 +43,4 @@ const weapons = computed(() => {
}) })
return res return res
}) })
</script> </script>
@@ -1,5 +1,8 @@
<template> <template>
<div class="w-340px bg-black flex flex-col h-90vh"> <div class="w-340px bg-black flex flex-col h-90vh">
<calc-dialog lazy header="装备条件" v-model:visible="triggerVisible">
<options />
</calc-dialog>
<!-- 头部 选择是装备还是融合石 --> <!-- 头部 选择是装备还是融合石 -->
<div class="flex bg-#101012 h-40px text-#6C5E4A font-bold"> <div class="flex bg-#101012 h-40px text-#6C5E4A font-bold">
<div <div
@@ -30,7 +33,7 @@
品质:<EquipmentRaritySelect v-model="curRarity" /> 品质:<EquipmentRaritySelect v-model="curRarity" />
</div> </div>
</div> </div>
<div class="flex flex-1 items-center"> <div class="flex flex-1 items-center justify-between">
<div class="flex items-center"> <div class="flex items-center">
套装: 套装:
<div class="flex items-center justify-center gap-2px"> <div class="flex items-center justify-center gap-2px">
@@ -47,26 +50,27 @@
</template> </template>
</div> </div>
</div> </div>
<calc-button
icon="set"
title="装备触发条件"
@click="triggerVisible = !triggerVisible"
></calc-button>
</div> </div>
</div> </div>
<div class="overflow-y-auto gap-1 p-1 flex flex-col items-center flex-1 h-0"> <div class="overflow-y-auto gap-1 p-1 flex flex-col items-center flex-1 h-0">
<template v-for="item in equs" :key="item.name"> <template v-for="item in equs" :key="item.name">
<template v-if="item.equs.length > 0"> <template v-if="item.equs.length > 0">
<div class="item-head w-full">{{ item.name }}</div> <div class="item-head w-full">{{ item.name }}</div>
<div class="flex flex-wrap gap-4px justify-around w-full"> <div class="flex flex-wrap gap-4px justify-around w-full">
<template v-for="(equ, _) in item.equs" :key="_"> <template v-for="(equ, _) in item.equs" :key="_">
<EquipmentIcon <EquipmentIcon :inactive="!isActive(equ)" :equipment="equ" @click="chooseEqu(equ)" />
:inactive="!isActive(equ)" </template>
:equipment="equ" </div>
@click="chooseEqu(equ)"
/>
</template>
</div>
</template>
</template> </template>
</div> </template>
</div>
</div> </div>
<div class="px-5px flex flex-col"> <div class="px-5px flex flex-col h-90vh">
<div class="flex flex-col overflow-hidden bg-black" v-if="curEquInfo"> <div class="flex flex-col overflow-hidden bg-black" v-if="curEquInfo">
<div class="item-head">基础信息</div> <div class="item-head">基础信息</div>
<div class="flex-1 overflow-y-auto overflow-x-hidden bg-black w-240px"> <div class="flex-1 overflow-y-auto overflow-x-hidden bg-black w-240px">
@@ -127,6 +131,11 @@ import EquipmentIcon from '@/components/dnf/Equipment/Icon/index.vue'
import Info from '@/components/dnf/Equipment/Info/index.vue' import Info from '@/components/dnf/Equipment/Info/index.vue'
import SuitInfo from '@/components/dnf/Equipment/SuitInfo/index.vue' import SuitInfo from '@/components/dnf/Equipment/SuitInfo/index.vue'
import { formatEqu, formatStone } from './formatData' import { formatEqu, formatStone } from './formatData'
import options from './components/options.vue'
const props = defineProps<{
alter: string
}>()
const infoStore = useInfoStore() const infoStore = useInfoStore()
const type = ref('equ') const type = ref('equ')
@@ -137,6 +146,7 @@ const curSuitLv = ref({
rarity: '史诗', rarity: '史诗',
lv: 1, lv: 1,
}) })
const triggerVisible = ref(false)
const raritiyList = ['稀有', '神器', '传说', '史诗', '太初'] const raritiyList = ['稀有', '神器', '传说', '史诗', '太初']
@@ -0,0 +1,19 @@
<template>
<div class="flex flex-col gap-2 w-250px p-2">
<div v-for="option in info.options" :key="option.id" class="flex items-center gap-2">
<div class="w-60px text-end">{{ option.name }}</div>
<calc-select class="flex-1 !h-20px" :appendTo="dialogRef" v-model="config.config.options[option.id.toString()]">
<calc-option v-for="(item, index) in option.options" :key="index" :value="index">
{{ item }}
</calc-option>
</calc-select>
</div>
</div>
</template>
<script lang="ts" setup>
import { useConfigStore, useInfoStore } from '@/stores'
const dialogRef = inject("dialogRef", ref(null))
const config = useConfigStore()
const info = useInfoStore()
</script>
@@ -1,9 +1,8 @@
import type { IEquipment } from "@/api/info/type" import type { IEquipment } from '@/api/info/type'
export const rowCount = 10 export const rowCount = 10
export const formatEqu = (total: IEquipment[],subweapons?:string[],curSuit export const formatEqu = (total: IEquipment[], subweapons?: string[], curSuit?: string) => {
?:string) => {
// 武器 // 武器
const item_weapon = total.filter((a) => a.itemType == '武器') ?? [] const item_weapon = total.filter((a) => a.itemType == '武器') ?? []
const weapons: (IEquipment | undefined)[] = [] const weapons: (IEquipment | undefined)[] = []
@@ -25,14 +24,14 @@ export const formatEqu = (total: IEquipment[],subweapons?:string[],curSuit
// # region 副武器 // # region 副武器
const subWeapons: (IEquipment | undefined)[] = [] const subWeapons: (IEquipment | undefined)[] = []
const item_sub_weapon = ( const item_sub_weapon = (total.filter((a) => subweapons?.includes(a.itemDetailType)) ?? []).sort(
total.filter((a) => subweapons?.includes(a.itemDetailType)) ?? [] (a, b) => {
).sort((a, b) => { if (a.categorize == b.categorize) {
if (a.categorize == b.categorize) { return a.itemDetailType.localeCompare(b.itemDetailType)
return a.itemDetailType.localeCompare(b.itemDetailType) }
} return b.categorize.localeCompare(a.categorize)
return b.categorize.localeCompare(a.categorize) },
}) )
if (item_sub_weapon.length > 0) { if (item_sub_weapon.length > 0) {
subWeapons.push( subWeapons.push(
...item_sub_weapon.map((a) => ({ ...a, itemType: '副武器' })), ...item_sub_weapon.map((a) => ({ ...a, itemType: '副武器' })),
@@ -130,7 +129,7 @@ export const formatEqu = (total: IEquipment[],subweapons?:string[],curSuit
] ]
} }
export const formatStone = (total: IEquipment[],curSuit?:string) => { export const formatStone = (total: IEquipment[], curSuit?: string) => {
// 套装 // 套装
const item_suits = total.filter((equip) => equip.suit.length == 1) const item_suits = total.filter((equip) => equip.suit.length == 1)
const suits: (IEquipment | undefined)[] = [] const suits: (IEquipment | undefined)[] = []
@@ -215,10 +214,10 @@ export const formatStone = (total: IEquipment[],curSuit?:string) => {
const items_sj = total.filter((equip) => equip.categorize.includes('神界')) const items_sj = total.filter((equip) => equip.categorize.includes('神界'))
const sj: (IEquipment | undefined)[] = [] const sj: (IEquipment | undefined)[] = []
// const raritiy = Array.from(new Set(items_suits_rzs.map((item) => item.rarity))) // const raritiy = Array.from(new Set(items_suits_rzs.map((item) => item.rarity)))
sj.push(...items_sj, ...Array(rowCount * 2 - (items_sj.length%rowCount)).fill(undefined)) sj.push(...items_sj, ...Array(rowCount * 2 - (items_sj.length % rowCount)).fill(undefined))
// const temp = items_suits_rzs.filter((item) => item.itemDetailType === part) // const temp = items_suits_rzs.filter((item) => item.itemDetailType === part)
// const remind = temp.length % rowCount // const remind = temp.length % rowCount
// rzs.push(...temp, ...Array(temp.length == 0 ? rowCount-remind : remind).fill(undefined)) // rzs.push(...temp, ...Array(temp.length == 0 ? rowCount-remind : remind).fill(undefined))
return [ return [
{ {
name: `${curSuit} 套装`, name: `${curSuit} 套装`,
@@ -101,23 +101,27 @@ export default defineComponent({
</div> </div>
) : ( ) : (
<> <>
<div class="equ-profile-item"> <div class="equ-profile-item">
<div class="row-name">品级</div> <div class="row-name">品级</div>
<calc-select v-model={id.value} class="flex-1 !h-20px"> <calc-select v-model={id.value} class="flex-1 !h-20px">
{renderList(basicInfoStore.avatars[part.value] ?? [], (item) => ( {renderList(basicInfoStore.avatars[part.value] ?? [], (item) => (
<calc-option value={item.id}>{item.suit}</calc-option> <calc-option value={item.id}>{item.suit}</calc-option>
))} ))}
</calc-select> </calc-select>
</div> </div>
<div class="equ-profile-item"> <div class="equ-profile-item">
<div class="row-name">属性</div> <div class="row-name">属性</div>
<calc-select v-model={option.value} class="flex-1 !h-20px"> <calc-select v-model={option.value} class="flex-1 !h-20px">
<calc-option value={0}>无</calc-option> <calc-option value={0}>无</calc-option>
{renderList(basicInfoStore.avatars[part.value].find(a=>a.id == id.value)?.options ?? [], (item) => ( {renderList(
<calc-option value={item}>{item}</calc-option> basicInfoStore.avatars[part.value].find((a) => a.id == id.value)?.options ??
))} [],
</calc-select> (item) => (
</div> <calc-option value={item}>{item}</calc-option>
),
)}
</calc-select>
</div>
</> </>
)} )}
{has_socket.value ? ( {has_socket.value ? (
@@ -1,28 +1,32 @@
<template> <template>
<div> <div>
<template v-for="key in ['0','1','2','3']" :key="key"> <template v-for="key in ['0', '1', '2', '3']" :key="key">
<div class="flex gap-2 mt-5px"> <div class="flex gap-2 mt-5px">
<calc-select class="flex-1 !h-20px" v-model="configStore.config.jades[key].id"> <calc-select class="flex-1 !h-20px" v-model="configStore.config.jades[key].id">
<calc-option :value="0">无</calc-option> <calc-option :value="0">无</calc-option>
<calc-option v-for="(item, index) in infoStore.jades" :key="item.id" :value="item.id"> <calc-option v-for="(item, index) in infoStore.jades" :key="item.id" :value="item.id">
{{item.name}} {{ item.name }}
</calc-option> </calc-option>
</calc-select> </calc-select>
<!-- <calc-slider <!-- <calc-slider
v-model="configStore.config.jades[key].value" v-model="configStore.config.jades[key].value"
:formatValue="formatValue(configStore.config.jades[key].id)" :formatValue="formatValue(configStore.config.jades[key].id)"
:min="Number(currentJade(configStore.config.jades[key].id)?.min)" :min="Number(currentJade(configStore.config.jades[key].id)?.min)"
:max="Number(currentJade(configStore.config.jades[key].id)?.max)" :max="Number(currentJade(configStore.config.jades[key].id)?.max)"
:step="Number(currentJade(configStore.config.jades[key].id)?.pre)" :step="Number(currentJade(configStore.config.jades[key].id)?.pre)"
></calc-slider> --> ></calc-slider> -->
<calc-select class="flex-1 !h-20px" v-model="configStore.config.jades[key].value"> <calc-select class="flex-1 !h-20px" v-model="configStore.config.jades[key].value">
<calc-option v-for="(item, index) in valueList(configStore.config.jades[key].id)" :key="item.id" :value="item.id"> <calc-option
{{item.value}} v-for="(item, index) in valueList(configStore.config.jades[key].id)"
</calc-option> :key="item.id"
</calc-select> :value="item.id"
</div> >
</template> {{ item.value }}
</div> </calc-option>
</calc-select>
</div>
</template>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -40,50 +44,50 @@ const currentJade = computed(() => {
return undefined return undefined
} else { } else {
const res = [] const res = []
const cur = infoStore.jades?.find(e => e.id == id) as IJade const cur = infoStore.jades?.find((e) => e.id == id) as IJade
return cur return cur
} }
} }
}) })
const formatValue = (id: number) => { const formatValue = (id: number) => {
return (value:number) =>{ return (value: number) => {
if(currentJade.value(id)?.unit.includes('等级')){ if (currentJade.value(id)?.unit.includes('等级')) {
return `$等级 +${value}` return `$等级 +${value}`
}
return `${getFloat(value, 1)}${currentJade.value(id)?.unit}`
} }
return `${getFloat(value, 1)}${currentJade.value(id)?.unit}`
} }
}
const valueList = computed(() => { const valueList = computed(() => {
return function (id: number = -1) { return function (id: number = -1) {
if (id == -1) { if (id == -1) {
return undefined return undefined
} else {
const res = []
const cur = infoStore.jades?.find((e) => e.id == id) as IJade
if (cur) {
const { max, min, unit, pre } = cur
//
if (unit.includes('等级')) {
res.push({
id: 1,
value: unit,
})
} else { } else {
const res = [] for (let i = Number(max) as number; i >= Number(min); ) {
const cur = infoStore.jades?.find(e => e.id == id) as IJade if (i != 0) {
if (cur) {
const { max, min, unit, pre } = cur
//
if (unit.includes('等级')) {
res.push({ res.push({
id: 1, id: i,
value: unit value: (i > 0 ? '+' : '') + getFloat(i, 1) + unit,
}) })
} else {
for (let i = Number(max) as number; i >= Number(min);) {
if (i != 0) {
res.push({
id: i,
value: (i > 0 ? "+" : "") + getFloat(i, 1) + unit
})
}
i = Number((i - Number(pre)).toFixed(1))
}
} }
return res i = Number((i - Number(pre)).toFixed(1))
} }
} }
return res
} }
}) }
}
})
</script> </script>
+18 -18
View File
@@ -1,26 +1,26 @@
<template> <template>
<div class="w-420px"> <div class="w-420px">
<calc-collapse class="w-100%" title="装备/时装打造(点击部位切换)"> <calc-collapse class="w-100%" title="装备/时装打造(点击部位切换)">
<calc-tabs v-model="partTab" class="!mb-2"> <calc-tabs v-model="partTab" class="!mb-2">
<calc-tab :value="0">装备栏</calc-tab> <calc-tab :value="0">装备栏</calc-tab>
<calc-tab :value="1">时装栏</calc-tab> <calc-tab :value="1">时装栏</calc-tab>
</calc-tabs> </calc-tabs>
<equipment v-if="partTab === 0"></equipment> <equipment v-if="partTab === 0"></equipment>
<clothes v-if="partTab === 1"></clothes> <clothes v-if="partTab === 1"></clothes>
</calc-collapse> </calc-collapse>
<calc-collapse class="w-100%" title="辟邪玉"> <calc-collapse class="w-100%" title="辟邪玉">
<jade></jade> <jade></jade>
</calc-collapse> </calc-collapse>
<calc-collapse class="w-100%" title="其它"> <calc-collapse class="w-100%" title="其它">
<sundry></sundry> <sundry></sundry>
</calc-collapse> </calc-collapse>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import equipment from './components/Equipments.vue' import equipment from './components/Equipments.vue'
import clothes from './components/Avatars.vue'; import clothes from './components/Avatars.vue'
import jade from './components/Jades.vue'; import jade from './components/Jades.vue'
import sundry from './components/Sundry.vue'; import sundry from './components/Sundry.vue'
const partTab = ref(0) const partTab = ref(0)
</script> </script>
+1 -1
View File
@@ -81,7 +81,7 @@ onUnmounted(() => {
align-items: center; align-items: center;
z-index: 2; z-index: 2;
height: 40px; height: 40px;
gap : 10px; gap: 10px;
} }
.content { .content {
+13 -4
View File
@@ -11,8 +11,17 @@
:equs="configStore.config.equips" :equs="configStore.config.equips"
></Info> ></Info>
</div> </div>
<Skill v-if="!configStore.result.state.buffer" :data="configStore.result.state.skills" :standard="infoStore.standard"></Skill> <Skill
<BufferSkill v-else :data="configStore.result.state.skills" :stadard="infoStore.standard" :standard="infoStore.standard"></BufferSkill> v-if="!configStore.result.state.buffer"
:data="configStore.result.state.skills"
:standard="infoStore.standard"
></Skill>
<BufferSkill
v-else
:data="configStore.result.state.skills"
:stadard="infoStore.standard"
:standard="infoStore.standard"
></BufferSkill>
</div> </div>
</template> </template>
@@ -27,8 +36,8 @@ const configStore = useConfigStore()
const infoStore = useInfoStore() const infoStore = useInfoStore()
const setStandard = () => { const setStandard = () => {
if(!configStore?.result?.state?.uuid) return if (!configStore?.result?.state?.uuid) return
infoStore.setStandard(configStore.result.state,configStore.skillCountConfig) infoStore.setStandard(configStore.result.state, configStore.skillCountConfig)
} }
const clearStandard = () => { const clearStandard = () => {
@@ -33,7 +33,9 @@
</div> </div>
<div>{{ skill.name }}</div> <div>{{ skill.name }}</div>
</div> </div>
<div class="w-50px flex items-center justify-center">{{ typeof skill.cd === 'number' ? `${skill.cd.toFixed(1)}s` : '-' }}</div> <div class="w-50px flex items-center justify-center">
{{ typeof skill.cd === 'number' ? `${skill.cd.toFixed(1)}s` : '-' }}
</div>
<component :is="formatDamage(skill)"></component> <component :is="formatDamage(skill)"></component>
</div> </div>
</div> </div>
@@ -82,7 +84,7 @@ const data = computed(() => {
// @ts-ignore // @ts-ignore
bValue = b[sort.value] bValue = b[sort.value]
} }
const key = ['MAIN', 'ATK','STRINT', 'ratio'].findIndex((a) => sort.value == a) const key = ['MAIN', 'ATK', 'STRINT', 'ratio'].findIndex((a) => sort.value == a)
aValue = a.buff?.[key] aValue = a.buff?.[key]
bValue = b.buff?.[key] bValue = b.buff?.[key]
@@ -149,46 +151,52 @@ const formatDamage = (skill: IResultSkill) => {
'div', 'div',
{ {
class: 'flex items-center justify-center', class: 'flex items-center justify-center',
},[0, 1, 2, 3].map((index) => { },
const value = skill.buff?.[index] - standardSkill.buff?.[index] [0, 1, 2, 3].map((index) => {
return h( const value = skill.buff?.[index] - standardSkill.buff?.[index]
'div', return h(
{ 'div',
style: { {
display: 'flex', style: {
alignItems: 'center', display: 'flex',
justifyContent: 'center', alignItems: 'center',
width: '80px', justifyContent: 'center',
color: value >= 0 ? '#37fa38' : '#ff0000', width: '80px',
color: value >= 0 ? '#37fa38' : '#ff0000',
},
}, },
}, `${value >= 0 ? '+' : ''}${
`${value >= 0 ? '+':''}${index == 3 index == 3
? `${Math.round(value).toLocaleString()}%` ? `${Math.round(value).toLocaleString()}%`
: Math.round(value).toLocaleString()}` : Math.round(value).toLocaleString()
) }`,
})) )
}),
)
} else { } else {
return h( return h(
'div', 'div',
{ {
class: 'flex items-center justify-center', class: 'flex items-center justify-center',
},[0, 1, 2, 3].map((index) => { },
const value = skill.buff?.[index] [0, 1, 2, 3].map((index) => {
return h( const value = skill.buff?.[index]
'div', return h(
{ 'div',
style: { {
display: 'flex', style: {
alignItems: 'center', display: 'flex',
justifyContent: 'center', alignItems: 'center',
width: '80px', justifyContent: 'center',
width: '80px',
},
}, },
}, index == 3
index == 3 ? `${Math.round(value).toLocaleString()}%`
? `${Math.round(value).toLocaleString()}%` : Math.round(value).toLocaleString(),
: Math.round(value).toLocaleString(), )
) }),
})) )
} }
} }
</script> </script>
@@ -3,7 +3,7 @@
<div class="h-177px"> <div class="h-177px">
<EquList <EquList
:detail="props.equs" :detail="props.equs"
:with-sub-weapon="(infoStore.infos?.subweapons?.length ?? 0)> 0" :with-sub-weapon="(infoStore.infos?.subweapons?.length ?? 0) > 0"
></EquList> ></EquList>
</div> </div>
<div class="flex flex-wrap border-y-1px my-1px border-y-solid border-#FFFF/10 gap-1% px-1%"> <div class="flex flex-wrap border-y-1px my-1px border-y-solid border-#FFFF/10 gap-1% px-1%">
@@ -12,7 +12,7 @@
]" ]"
:key="key" :key="key"
class="item-head" class="item-head"
:style="{width: `${header.width}px`}" :style="{ width: `${header.width}px` }"
@click="header.sortKey && sortValue(header.sortKey)" @click="header.sortKey && sortValue(header.sortKey)"
> >
{{ header.label }} {{ header.label }}
@@ -44,13 +44,18 @@
<component :is="formatDamage(skill)"></component> <component :is="formatDamage(skill)"></component>
</div> </div>
</div> </div>
<div v-if="!totalStandard" class="h-40px flex items-center justify-center text-18px border-t-1px border-t-solid border-t-#FFFF/15">{{showTotal.toFixed(0)}}</div> <div
v-if="!totalStandard"
class="h-40px flex items-center justify-center text-18px border-t-1px border-t-solid border-t-#FFFF/15"
>
{{ showTotal.toFixed(0) }}
</div>
<div <div
v-else v-else
class="h-40px flex items-center justify-center text-18px border-t-1px border-t-solid border-t-#FFFF/15" class="h-40px flex items-center justify-center text-18px border-t-1px border-t-solid border-t-#FFFF/15"
:style="{color:showTotal >= 0 ? '#37fa38' : '#ff0000'}" :style="{ color: showTotal >= 0 ? '#37fa38' : '#ff0000' }"
> >
{{showTotal.toFixed(2)}}% {{ showTotal.toFixed(2) }}%
</div> </div>
</div> </div>
</div> </div>
@@ -84,11 +89,11 @@ const data = computed(() => {
// @ts-ignore // @ts-ignore
bValue = b[sort.value] bValue = b[sort.value]
} }
if( sort.value == 'total'){ if (sort.value == 'total') {
aValue = a.damage * skillCount(a).value aValue = a.damage * skillCount(a).value
bValue = b.damage * skillCount(b).value bValue = b.damage * skillCount(b).value
} }
if(sort.value == 'dps'){ if (sort.value == 'dps') {
aValue = a.damage / a.cd aValue = a.damage / a.cd
bValue = b.damage / b.cd bValue = b.damage / b.cd
} }
@@ -152,7 +157,7 @@ const totalStandard = computed(() => {
const showTotal = computed(() => { const showTotal = computed(() => {
if (!totalStandard.value || totalStandard.value == 0) return totalDamage.value if (!totalStandard.value || totalStandard.value == 0) return totalDamage.value
return (totalDamage.value - totalStandard.value) / totalStandard.value * 100 return ((totalDamage.value - totalStandard.value) / totalStandard.value) * 100
}) })
const formatDamage = (skill: IResultSkill) => { const formatDamage = (skill: IResultSkill) => {
@@ -101,7 +101,7 @@ export default defineComponent({
<div <div
class={[ class={[
'w-36px absolute h-48px flex flex-col box-border items-center py-3px z-2', 'w-36px absolute h-48px flex flex-col box-border items-center py-3px z-2',
(skill.learnLv == 100 && skill.bind) ? '' : 'skill', skill.learnLv == 100 && skill.bind ? '' : 'skill',
]} ]}
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
@@ -10,25 +10,26 @@
} }
.passive { .passive {
filter: grayscale(100%) invert(3%) sepia(14%) saturate(874%) hue-rotate(65deg) brightness(105%) contrast(102%); filter: grayscale(100%) invert(3%) sepia(14%) saturate(874%) hue-rotate(65deg) brightness(105%)
contrast(102%);
} }
.skill{ .skill {
background-image: url('./img/skill.png'); background-image: url('./img/skill.png');
background-size: 100% 100%; background-size: 100% 100%;
} }
.skill-active{ .skill-active {
background-image: url('./img/skillActive.png'); background-image: url('./img/skillActive.png');
background-size: 100% 100%; background-size: 100% 100%;
} }
.awake_50{ .awake_50 {
background-image: url('./img/awake_50.png'); background-image: url('./img/awake_50.png');
background-size: 100% 100%; background-size: 100% 100%;
} }
.awake_85{ .awake_85 {
background-image: url('./img/awake_85.png'); background-image: url('./img/awake_85.png');
background-size: 100% 100%; background-size: 100% 100%;
} }
+5 -2
View File
@@ -1,12 +1,15 @@
<template> <template>
<div> <div>
<div class="h-90vh w-auto overflow-y-auto overflow-x-hidden"> <div class="h-90vh w-auto overflow-y-auto overflow-x-hidden">
<SkillTree :skills="infoStore.skills" v-model:lvInfo="configStore.config.skills" v-model:bindAwake="configStore.config.bindAwake"></SkillTree> <SkillTree
:skills="infoStore.skills"
v-model:lvInfo="configStore.config.skills"
v-model:bindAwake="configStore.config.bindAwake"
></SkillTree>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup name="Index"> <script lang="ts" setup name="Index">
import { useInfoStore } from '@/stores/info' import { useInfoStore } from '@/stores/info'
import SkillTree from './components/SkillTree/index' import SkillTree from './components/SkillTree/index'
+9 -8
View File
@@ -18,21 +18,20 @@ export default defineComponent(async () => {
function choose_job(child: IAlterInfo) { function choose_job(child: IAlterInfo) {
return async () => { return async () => {
if (child.comment == '首页') { if (child.comment == '首页') {
return window.open("https://dnftools.com","_blank") return window.open('https://dnftools.com', '_blank')
} }
if (child.name == 'sponsor') { if (child.name == 'sponsor') {
return window.open((child.url ?? ''),"_blank") return window.open(child.url ?? '', '_blank')
} }
if (child.open) { if (child.open) {
const options = child.options ?? [] const options = child.options ?? []
if (options.length > 0) { if (options.length > 0) {
return return
} else {
return window.open('/character/' + (child.value || child.name), '_blank')
} }
else{ }
return window.open("/character/" + (child.value || child.name),"_blank")
}
} }
}
// router.push("/character/" + alter) // router.push("/character/" + alter)
} }
@@ -40,12 +39,14 @@ export default defineComponent(async () => {
// let checkTimer: NodeJS.Timeout // let checkTimer: NodeJS.Timeout
const adventure = await api.adventures() const adventure = await api.adventures()
return () => ( return () => (
<Fragment> <Fragment>
<div class="bg-no-repeat h-auto min-h-full pt-8 pb-12 pl-4" style={`background-image: url('${getImageURL('home.jpg')}');background-size: 100% 100%;`}> <div
class="bg-no-repeat h-auto min-h-full pt-8 pb-12 pl-4"
style={`background-image: url('${getImageURL('home.jpg')}');background-size: 100% 100%;`}
>
{renderList(adventure, (job) => ( {renderList(adventure, (job) => (
<div class="flex flex-row"> <div class="flex flex-row">
<div <div
+2 -2
View File
@@ -3,12 +3,12 @@
<Suspense> <Suspense>
<RouterView></RouterView> <RouterView></RouterView>
</Suspense> </Suspense>
<render/> <render />
</div> </div>
</template> </template>
<script lang="ts" name="index" setup> <script lang="ts" name="index" setup>
import { useDialog } from '@/components/hooks/dialog'; import { useDialog } from '@/components/hooks/dialog'
const { render } = useDialog() const { render } = useDialog()
</script> </script>
+12 -11
View File
@@ -7,22 +7,22 @@ const routes: RouteRecordRaw[] = [
{ {
path: '/', path: '/',
name: 'home', name: 'home',
component: (() => import('@/pages/Home/Index.vue')), component: () => import('@/pages/Home/Index.vue'),
}, },
{ {
path:'/character/:alter', path: '/character/:alter',
redirect: (to) => { redirect: (to) => {
return { path: `/character/equipment/${to.params.alter}` } return { path: `/character/equipment/${to.params.alter}` }
}, },
props: true, props: true,
name:'character', name: 'character',
component: (() => import('@/pages/Character/Index.vue')), component: () => import('@/pages/Character/Index.vue'),
children: [ children: [
{ {
path: '/character/equipment/:alter', path: '/character/equipment/:alter',
name: 'equipment', name: 'equipment',
props: true, props: true,
component: (() => import('@/pages/Character/EquipmentDictionary/Index.vue')), component: () => import('@/pages/Character/EquipmentDictionary/Index.vue'),
}, },
// { // {
// path: '/character/equipment/:alter', // path: '/character/equipment/:alter',
@@ -34,15 +34,16 @@ const routes: RouteRecordRaw[] = [
path: '/character/skill/:alter', path: '/character/skill/:alter',
name: 'skill', name: 'skill',
props: true, props: true,
component: (() => import('@/pages/Character/Skill/index.vue')), component: () => import('@/pages/Character/Skill/index.vue'),
},{ },
{
path: '/character/forge/:alter', path: '/character/forge/:alter',
name: 'forge', name: 'forge',
props: true, props: true,
component: (() => import('@/pages/Character/Forge/index.vue')), component: () => import('@/pages/Character/Forge/index.vue'),
} },
] ],
} },
] ]
const router = createRouter({ const router = createRouter({
+17 -5
View File
@@ -54,9 +54,10 @@ export interface IConfig {
sundry: Record<string, any> sundry: Record<string, any>
bindAwake: number bindAwake: number
/** 系统奶 */ /** 系统奶 */
DSB : boolean DSB: boolean
/** 奶 */ /** 奶 */
BUFF : boolean BUFF: boolean
options: Record<string, number>
} }
const defaultEqusConfig = { const defaultEqusConfig = {
@@ -92,6 +93,7 @@ export const useConfigStore = defineStore('configStore', () => {
bindAwake: 50, bindAwake: 50,
DSB: false, DSB: false,
BUFF: false, BUFF: false,
options:{}
}) })
const skillCountConfig = ref<IResultSkillCount[]>([]) const skillCountConfig = ref<IResultSkillCount[]>([])
@@ -120,6 +122,7 @@ export const useConfigStore = defineStore('configStore', () => {
bindAwake: 50, bindAwake: 50,
DSB: false, DSB: false,
BUFF: false, BUFF: false,
options:{}
} }
if (!config.value?.equips) config.value.equips = {} if (!config.value?.equips) config.value.equips = {}
if (!config.value?.skills) config.value.skills = {} if (!config.value?.skills) config.value.skills = {}
@@ -127,6 +130,7 @@ export const useConfigStore = defineStore('configStore', () => {
if (!config.value?.avatar) config.value.avatar = {} if (!config.value?.avatar) config.value.avatar = {}
if (!config.value?.sundry) config.value.sundry = {} if (!config.value?.sundry) config.value.sundry = {}
if (!config.value?.bindAwake) config.value.bindAwake = 50 if (!config.value?.bindAwake) config.value.bindAwake = 50
if (!config.value?.options) config.value.options = {}
const part = [...infoStore.parts, '宠物', '副武器'].filter( const part = [...infoStore.parts, '宠物', '副武器'].filter(
(a) => !config.value?.equips.hasOwnProperty(a), (a) => !config.value?.equips.hasOwnProperty(a),
@@ -147,6 +151,11 @@ export const useConfigStore = defineStore('configStore', () => {
config.value && (config.value.avatar[p] = { ...defaultAvatarConfig }) config.value && (config.value.avatar[p] = { ...defaultAvatarConfig })
}) })
infoStore.options.forEach((option) => {
if(Object.keys(config.value.options).includes(option.id.toString())) return
config.value.options[option.id.toString()] = 0
})
skillIDs.forEach((id) => { skillIDs.forEach((id) => {
config.value && config.value &&
(config.value.skills[id] = { (config.value.skills[id] = {
@@ -168,7 +177,10 @@ export const useConfigStore = defineStore('configStore', () => {
const alter = infoStore.infos?.alter const alter = infoStore.infos?.alter
if (alter) { if (alter) {
localStorage.setItem(`dcalc/${alter}/config`, JSON.stringify(config.value)) localStorage.setItem(`dcalc/${alter}/config`, JSON.stringify(config.value))
localStorage.setItem(`dcalc/${alter}/config/skillCount`, JSON.stringify(skillCountConfig.value)) localStorage.setItem(
`dcalc/${alter}/config/skillCount`,
JSON.stringify(skillCountConfig.value),
)
} }
} }
@@ -203,7 +215,7 @@ export const useConfigStore = defineStore('configStore', () => {
skills: [], skills: [],
info: [], info: [],
suits: [], suits: [],
buffer:false buffer: false,
}, },
{ resetOnExecute: false }, { resetOnExecute: false },
) )
@@ -215,6 +227,6 @@ export const useConfigStore = defineStore('configStore', () => {
calc, calc,
result, result,
chooseEqu, chooseEqu,
skillCountConfig skillCountConfig,
} }
}) })
+3 -3
View File
@@ -1,6 +1,6 @@
import { createPinia } from "pinia" import { createPinia } from 'pinia'
export * from "./info" export * from './info'
export * from "./config" export * from './config'
export const pinia = createPinia() export const pinia = createPinia()
+16 -10
View File
@@ -26,26 +26,31 @@ export const useInfoStore = defineStore('infoStore', () => {
const sundries = computed(() => infos.value?.sundry ?? {}) const sundries = computed(() => infos.value?.sundry ?? {})
const options = computed(() => infos.value?.options ?? [])
const standardUUID = ref<string>() const standardUUID = ref<string>()
const setStandard = (result?: IResult,skillCount?:{ const setStandard = (
count: number; result?: IResult,
mode: string; skillCount?: {
name: string; count: number
}[]) => { mode: string
name: string
}[],
) => {
if (!result) { if (!result) {
!!standardUUID.value && sessionStorage.removeItem(standardUUID.value) !!standardUUID.value && sessionStorage.removeItem(standardUUID.value)
standardUUID.value = undefined standardUUID.value = undefined
} else { } else {
standardUUID.value = result.uuid standardUUID.value = result.uuid
sessionStorage.setItem(result.uuid, JSON.stringify({skills:result.skills,skillCount})) sessionStorage.setItem(result.uuid, JSON.stringify({ skills: result.skills, skillCount }))
} }
} }
const standard = computed(()=>{ const standard = computed(() => {
const value = sessionStorage.getItem(standardUUID.value ?? '') const value = sessionStorage.getItem(standardUUID.value ?? '')
if(!value) return undefined if (!value) return undefined
else return JSON.parse(value) as {skills:IResultSkill[],skillCount:IResultSkillCount[]} else return JSON.parse(value) as { skills: IResultSkill[]; skillCount: IResultSkillCount[] }
}) })
const parts = [ const parts = [
@@ -107,6 +112,7 @@ export const useInfoStore = defineStore('infoStore', () => {
jades, jades,
setStandard, setStandard,
standard, standard,
sundries sundries,
options
} }
}) })
+95 -40
View File
@@ -1,26 +1,54 @@
@use "./animation.scss"; @use './animation.scss';
.text-shadow { .text-shadow {
text-shadow: 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, text-shadow:
0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000,
0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000,
0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000,
0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000,
0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000,
0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000; 0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000;
} }
@font-face { @font-face {
font-family: "dcalc"; font-family: 'dcalc';
src: url("@/assets/fonts/dcalc.ttf"); src: url('@/assets/fonts/dcalc.ttf');
} }
:root { :root {
font-size: 16px; font-size: 16px;
--cursor-pointer: url("@/assets/cursor/basic.cur"), --cursor-pointer:
url("@/assets/cursor/cursor.png"), pointer !important; url('@/assets/cursor/basic.cur'), url('@/assets/cursor/cursor.png'), pointer !important;
--cursor-active: url("@/assets/cursor/basic_press.cur"), --cursor-active:
url("@/assets/cursor/cursor.png"), pointer !important; url('@/assets/cursor/basic_press.cur'), url('@/assets/cursor/cursor.png'), pointer !important;
} }
html, html,
@@ -39,16 +67,44 @@ body,
* { * {
// font-family: "ArkPixel", "Consola", sans-serif; // font-family: "ArkPixel", "Consola", sans-serif;
font-family: "dcalc", "SimSun"; font-family: 'dcalc', 'SimSun';
// font-family: "STSong"; // font-family: "STSong";
font-size: 12px; font-size: 12px;
text-shadow: 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, text-shadow:
0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000,
0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000,
0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000,
0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000,
0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000,
0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000, 0 0 1px #000; 0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000,
0 0 1px #000;
// image-rendering:pixelated; // image-rendering:pixelated;
// text-rendering: geometricPrecision; // text-rendering: geometricPrecision;
// letter-spacing: -0.5px; // letter-spacing: -0.5px;
@@ -91,12 +147,12 @@ body,
::-webkit-scrollbar-button:vertical:single-button:start { ::-webkit-scrollbar-button:vertical:single-button:start {
width: 11px; width: 11px;
// 添加标题栏后调整相关位置 // 添加标题栏后调整相关位置
background: url("@/assets/img/icon/up.png") 0px 1px no-repeat; background: url('@/assets/img/icon/up.png') 0px 1px no-repeat;
} }
::-webkit-scrollbar-button:vertical:single-button:end { ::-webkit-scrollbar-button:vertical:single-button:end {
width: 11px; width: 11px;
background: url("@/assets/img/icon/down.png") 0px 1px no-repeat; background: url('@/assets/img/icon/down.png') 0px 1px no-repeat;
} }
.divider { .divider {
@@ -140,10 +196,10 @@ body,
.name { .name {
padding: 0px 5px 0px 5px; padding: 0px 5px 0px 5px;
&::after { &::after {
content: ""; content: '';
width: 210px; width: 210px;
height: 8px; height: 8px;
background: url("@/assets/img/underline.png"); background: url('@/assets/img/underline.png');
display: block; display: block;
background-size: contain; background-size: contain;
left: 5px; left: 5px;
@@ -197,7 +253,7 @@ body,
.app-header { .app-header {
// -webkit-app-region: drag; // -webkit-app-region: drag;
background-image: url("@/assets/img/control/title.png"); background-image: url('@/assets/img/control/title.png');
background-size: 100% 100%; background-size: 100% 100%;
color: #9d8c5b; color: #9d8c5b;
border-top: 1px solid #6c81a7; border-top: 1px solid #6c81a7;
@@ -206,7 +262,7 @@ body,
.min-icon { .min-icon {
-webkit-app-region: no-drag; -webkit-app-region: no-drag;
background: url("@/assets/img/icon/min.png") no-repeat; background: url('@/assets/img/icon/min.png') no-repeat;
background-size: 10px 10px; background-size: 10px 10px;
background-position: center; background-position: center;
@@ -214,7 +270,7 @@ body,
.close-icon { .close-icon {
-webkit-app-region: no-drag; -webkit-app-region: no-drag;
background: url("@/assets/img/icon/exit.png") no-repeat; background: url('@/assets/img/icon/exit.png') no-repeat;
background-size: 10px 10px; background-size: 10px 10px;
background-position: center; background-position: center;
} }
@@ -258,7 +314,7 @@ body,
} }
.primeval { .primeval {
background-image: -webkit-linear-gradient(top, #28d931,#33acea); background-image: -webkit-linear-gradient(top, #28d931, #33acea);
color: transparent; color: transparent;
background-clip: text; background-clip: text;
text-shadow: none; text-shadow: none;
@@ -371,33 +427,32 @@ body,
// mix-blend-mode: color-dodge; // mix-blend-mode: color-dodge;
// } // }
.lvInfo{ .lvInfo {
background-image: url("@/assets/img/specificity/lv.png"); background-image: url('@/assets/img/specificity/lv.png');
background-repeat: no-repeat !important; background-repeat: no-repeat !important;
background-size: auto auto !important; background-size: auto auto !important;
width: 30px; width: 30px;
height: 16px; height: 16px;
z-index: 10; z-index: 10;
color:#4E3D1B color: #4e3d1b;
} }
.lvInfoActive{ .lvInfoActive {
background-image: url("@/assets/img/specificity/lv_active.png"); background-image: url('@/assets/img/specificity/lv_active.png');
background-repeat: no-repeat !important; background-repeat: no-repeat !important;
background-size: auto auto !important; background-size: auto auto !important;
width: 30px; width: 30px;
height: 16px; height: 16px;
z-index: 10; z-index: 10;
color:#FEB300 color: #feb300;
} }
.lvInfoMaster {
.lvInfoMaster{ background-image: url('@/assets/img/specificity/lv_master.png');
background-image: url("@/assets/img/specificity/lv_master.png");
background-repeat: no-repeat !important; background-repeat: no-repeat !important;
background-size: auto auto !important; background-size: auto auto !important;
width: 30px; width: 30px;
height: 16px; height: 16px;
z-index: 10; z-index: 10;
color:#FEB300 color: #feb300;
} }
-2
View File
@@ -36,13 +36,11 @@
// 极诣·契魔者 // 极诣·契魔者
.demon_slayer { .demon_slayer {
background: url(https://bbscdn.df.nexon.com/pg/characters/img/main/mc_02_02.png) no-repeat; background: url(https://bbscdn.df.nexon.com/pg/characters/img/main/mc_02_02.png) no-repeat;
} }
// 极诣·流浪武士 // 极诣·流浪武士
.vegabond { .vegabond {
background: url(https://bbscdn.df.nexon.com/pg/characters/img/main/mc_02_03.png) no-repeat; background: url(https://bbscdn.df.nexon.com/pg/characters/img/main/mc_02_03.png) no-repeat;
} }
// 极诣·刃影 // 极诣·刃影
+41 -36
View File
@@ -1,5 +1,11 @@
@mixin icon-style($icon-name, $width: 20px, $height: 20px, $size: 100% 100%, $position: center center) { @mixin icon-style(
background-image: url("@/assets/img/icon/#{$icon-name}.png") !important; $icon-name,
$width: 20px,
$height: 20px,
$size: 100% 100%,
$position: center center
) {
background-image: url('@/assets/img/icon/#{$icon-name}.png') !important;
background-repeat: no-repeat !important; background-repeat: no-repeat !important;
background-size: $size !important; background-size: $size !important;
background-position: $position !important; background-position: $position !important;
@@ -7,117 +13,116 @@
height: $height !important; height: $height !important;
&:hover { &:hover {
background-image: url("@/assets/img/icon/#{$icon-name}_hover.png") !important; background-image: url('@/assets/img/icon/#{$icon-name}_hover.png') !important;
} }
&:active { &:active {
background-image: url("@/assets/img/icon/#{$icon-name}_active.png") !important; background-image: url('@/assets/img/icon/#{$icon-name}_active.png') !important;
} }
&:disabled { &:disabled {
background-image: url("@/assets/img/icon/#{$icon-name}_disabled.png") !important; background-image: url('@/assets/img/icon/#{$icon-name}_disabled.png') !important;
} }
} }
.icon-reset { .icon-reset {
@include icon-style("reset"); @include icon-style('reset');
} }
.icon-set { .icon-set {
@include icon-style("set"); @include icon-style('set');
} }
.icon-prev { .icon-prev {
@include icon-style("prev", 16px, 16px); @include icon-style('prev', 16px, 16px);
} }
.icon-next { .icon-next {
@include icon-style("next", 16px, 16px); @include icon-style('next', 16px, 16px);
} }
.icon-dropdown { .icon-dropdown {
@include icon-style("dropdown", 16px, 16px); @include icon-style('dropdown', 16px, 16px);
} }
.icon-search { .icon-search {
@include icon-style("search"); @include icon-style('search');
} }
.icon-increase { .icon-increase {
@include icon-style("increase"); @include icon-style('increase');
} }
.icon-reduce { .icon-reduce {
@include icon-style("reduce"); @include icon-style('reduce');
} }
.icon-plus { .icon-plus {
@include icon-style("plus"); @include icon-style('plus');
} }
.icon-detail { .icon-detail {
@include icon-style("detail"); @include icon-style('detail');
} }
.icon-home { .icon-home {
@include icon-style("home"); @include icon-style('home');
} }
.icon-reflesh { .icon-reflesh {
@include icon-style("reflesh"); @include icon-style('reflesh');
} }
.icon-update { .icon-update {
@include icon-style("update"); @include icon-style('update');
} }
.icon-share { .icon-share {
@include icon-style("share"); @include icon-style('share');
} }
.icon-import { .icon-import {
@include icon-style("import"); @include icon-style('import');
} }
.icon-menu-drug { .icon-menu-drug {
@include icon-style("menu/drug", 20px, 20px, auto 100%); @include icon-style('menu/drug', 20px, 20px, auto 100%);
} }
.icon-menu-specificity { .icon-menu-specificity {
@include icon-style("menu/specificity", 20px, 20px, auto 100%); @include icon-style('menu/specificity', 20px, 20px, auto 100%);
} }
.icon-menu-favourite { .icon-menu-favourite {
@include icon-style("menu/favourite", 20px, 20px, auto 100%); @include icon-style('menu/favourite', 20px, 20px, auto 100%);
} }
.icon-menu-recommend { .icon-menu-recommend {
@include icon-style("menu/recommend", 20px, 20px, auto 100%); @include icon-style('menu/recommend', 20px, 20px, auto 100%);
} }
.icon-menu-set { .icon-menu-set {
@include icon-style("menu/set", 20px, 20px, auto 100%); @include icon-style('menu/set', 20px, 20px, auto 100%);
} }
.icon-max{ .icon-max {
@include icon-style("max"); @include icon-style('max');
} }
.icon-min{ .icon-min {
@include icon-style("min"); @include icon-style('min');
} }
.icon-increase-little { .icon-increase-little {
@include icon-style("increase",16px, 16px); @include icon-style('increase', 16px, 16px);
} }
.icon-reduce-little { .icon-reduce-little {
@include icon-style("reduce", 16px, 16px); @include icon-style('reduce', 16px, 16px);
} }
.icon-max-little{ .icon-max-little {
@include icon-style("max", 16px, 16px); @include icon-style('max', 16px, 16px);
} }
.icon-min-little{ .icon-min-little {
@include icon-style("min", 16px, 16px); @include icon-style('min', 16px, 16px);
} }
+3 -3
View File
@@ -1,3 +1,3 @@
@use "./app.scss"; @use './app.scss';
@use "./icon.scss"; @use './icon.scss';
@use "./char-img.scss" @use './char-img.scss';
+1 -1
View File
@@ -14,6 +14,6 @@ export const getImageURL = (image: string) => {
return `${baseURL}${image}` return `${baseURL}${image}`
} }
export const getLocalImageURL = (url:string)=>{ export const getLocalImageURL = (url: string) => {
return new URL(url, import.meta.url).href return new URL(url, import.meta.url).href
} }
+91 -86
View File
@@ -2,9 +2,9 @@ export function in_range(num: number, start: number, end: number) {
return start <= num && num <= end return start <= num && num <= end
} }
export function to_percent(num?: number, defaultValue: number = 0, suffix = "", withPlus = false) { export function to_percent(num?: number, defaultValue: number = 0, suffix = '', withPlus = false) {
num = num ?? defaultValue num = num ?? defaultValue
return (num > 0 && withPlus ? "+" : "") + num?.multiply(100).round(2).toString().concat(suffix) return (num > 0 && withPlus ? '+' : '') + num?.multiply(100).round(2).toString().concat(suffix)
} }
export function get_num(num?: number, defaultValue: number = 0) { export function get_num(num?: number, defaultValue: number = 0) {
@@ -13,14 +13,14 @@ export function get_num(num?: number, defaultValue: number = 0) {
export function format_string(str: string, params: any = {}, ...args: any[]) { export function format_string(str: string, params: any = {}, ...args: any[]) {
let num = 0 let num = 0
str = str.replace(/{}/g, match => { str = str.replace(/{}/g, (match) => {
return args[num++] ?? match return args[num++] ?? match
}) })
str = str.replace(/{\d+}/g, match => { str = str.replace(/{\d+}/g, (match) => {
const i = Number.parseInt(match.slice(1, -1)) const i = Number.parseInt(match.slice(1, -1))
return args[i] ?? match return args[i] ?? match
}) })
str = str.replace(/{\w+}/g, match => { str = str.replace(/{\w+}/g, (match) => {
const name = match.slice(1, -1) const name = match.slice(1, -1)
return params[name] ?? match return params[name] ?? match
}) })
@@ -29,11 +29,11 @@ export function format_string(str: string, params: any = {}, ...args: any[]) {
export function format_bytes(bytes: number, decimals = 2) { export function format_bytes(bytes: number, decimals = 2) {
if (bytes === 0) { if (bytes === 0) {
return "0 Bytes" return '0 Bytes'
} }
const k = 1024 const k = 1024
const dm = decimals < 0 ? 0 : decimals const dm = decimals < 0 ? 0 : decimals
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const i = Math.floor(Math.log(bytes) / Math.log(k)) const i = Math.floor(Math.log(bytes) / Math.log(k))
@@ -58,14 +58,14 @@ export function cartesian<T = any>(...arr: T[][]): T[][] {
return arr.reduce( return arr.reduce(
(a, b) => { (a, b) => {
const ret: T[][] = [] const ret: T[][] = []
a.forEach(a => { a.forEach((a) => {
b.forEach(b => { b.forEach((b) => {
ret.push(a.concat([b])) ret.push(a.concat([b]))
}) })
}) })
return ret return ret
}, },
[[]] as T[][] [[]] as T[][],
) )
} }
@@ -95,7 +95,7 @@ export function debounce_last(func: Function, wait: number) {
} }
export function padding(num: number | string, length: number): string { export function padding(num: number | string, length: number): string {
if ((`${num}`).length >= length) { if (`${num}`.length >= length) {
return `${num}` return `${num}`
} }
return padding(`0${num}`, length) return padding(`0${num}`, length)
@@ -106,7 +106,10 @@ export function format_float(f: number, digit: number = 2) {
return (f * m).round(10) / m return (f * m).round(10) / m
} }
export function mapRecord<T extends keyof any, K>(status: Record<T, K>, callback: (value: K, key: T) => K) { export function mapRecord<T extends keyof any, K>(
status: Record<T, K>,
callback: (value: K, key: T) => K,
) {
const new_status = {} as Record<T, K> const new_status = {} as Record<T, K>
for (const key in status) { for (const key in status) {
const value = callback(status[key], key) const value = callback(status[key], key)
@@ -117,7 +120,7 @@ export function mapRecord<T extends keyof any, K>(status: Record<T, K>, callback
return new_status return new_status
} }
Array.prototype.max = function<T>(callback: (t: T) => number): [number, T | undefined] { Array.prototype.max = function <T>(callback: (t: T) => number): [number, T | undefined] {
let rs: [number, T | undefined] = [-1, undefined] let rs: [number, T | undefined] = [-1, undefined]
let max = -1 let max = -1
for (let i = 0; i < this.length; i++) { for (let i = 0; i < this.length; i++) {
@@ -131,7 +134,7 @@ Array.prototype.max = function<T>(callback: (t: T) => number): [number, T | unde
return rs return rs
} }
Array.prototype.min = function<T>(callback: (t: T) => number): [number, T | undefined] { Array.prototype.min = function <T>(callback: (t: T) => number): [number, T | undefined] {
let rs: [number, T | undefined] = [-1, undefined] let rs: [number, T | undefined] = [-1, undefined]
let min = -1 let min = -1
for (let i = 0; i < this.length; i++) { for (let i = 0; i < this.length; i++) {
@@ -172,19 +175,19 @@ export function toObj(data: any): any {
const temp = {} const temp = {}
for (const key in data) { for (const key in data) {
try { try {
if (typeof data[key] === "number" || typeof data[key] === "string") { if (typeof data[key] === 'number' || typeof data[key] === 'string') {
Object.defineProperty(temp, key, { Object.defineProperty(temp, key, {
value: data[key], value: data[key],
writable: true, writable: true,
enumerable: true, enumerable: true,
configurable: true configurable: true,
}) })
} else { } else {
Object.defineProperty(temp, key, { Object.defineProperty(temp, key, {
value: RecordToObj(data[key]), value: RecordToObj(data[key]),
writable: true, writable: true,
enumerable: true, enumerable: true,
configurable: true configurable: true,
}) })
} }
} catch { } catch {
@@ -192,7 +195,7 @@ export function toObj(data: any): any {
value: data[key], value: data[key],
writable: true, writable: true,
enumerable: true, enumerable: true,
configurable: true configurable: true,
}) })
} }
} }
@@ -202,7 +205,7 @@ export function toObj(data: any): any {
export function toMap(data: any, except: string[] = []) { export function toMap(data: any, except: string[] = []) {
const temp = {} const temp = {}
for (const key in data) { for (const key in data) {
if (typeof data[key] === "object" && data[key].constructor != Array && !except.includes(key)) { if (typeof data[key] === 'object' && data[key].constructor != Array && !except.includes(key)) {
// 需要转换部分,不能是Array,也会被转换 // 需要转换部分,不能是Array,也会被转换
const sub = {} const sub = {}
for (const subkey in data[key]) { for (const subkey in data[key]) {
@@ -210,21 +213,21 @@ export function toMap(data: any, except: string[] = []) {
value: new Map(Object.entries(data[key][subkey])), value: new Map(Object.entries(data[key][subkey])),
writable: true, writable: true,
enumerable: true, enumerable: true,
configurable: true configurable: true,
}) })
} }
Object.defineProperty(temp, key, { Object.defineProperty(temp, key, {
value: sub, value: sub,
writable: true, writable: true,
enumerable: true, enumerable: true,
configurable: true configurable: true,
}) })
} else { } else {
Object.defineProperty(temp, key, { Object.defineProperty(temp, key, {
value: data[key], value: data[key],
writable: true, writable: true,
enumerable: true, enumerable: true,
configurable: true configurable: true,
}) })
} }
} }
@@ -238,28 +241,31 @@ export function RecordToObj(todo: Record<string, Map<any, any>>) {
value: Object.fromEntries(todo[key]), value: Object.fromEntries(todo[key]),
writable: true, writable: true,
enumerable: true, enumerable: true,
configurable: true configurable: true,
}) })
} }
return temp return temp
} }
export function getUuid() { export function getUuid() {
if (typeof crypto === "object") { if (typeof crypto === 'object') {
if (typeof crypto.randomUUID === "function") { if (typeof crypto.randomUUID === 'function') {
return crypto.randomUUID() return crypto.randomUUID()
} }
if (typeof crypto.getRandomValues === "function" && typeof Uint8Array === "function") { if (typeof crypto.getRandomValues === 'function' && typeof Uint8Array === 'function') {
const callback = (c: any) => { const callback = (c: any) => {
const num = Number(c) const num = Number(c)
return (num ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4)))).toString(16) return (num ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4)))).toString(
16,
)
} }
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, callback) return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, callback)
} }
} }
let timestamp = new Date().getTime() let timestamp = new Date().getTime()
let perforNow = (typeof performance !== "undefined" && performance.now && performance.now() * 1000) || 0 let perforNow =
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, c => { (typeof performance !== 'undefined' && performance.now && performance.now() * 1000) || 0
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
let random = Math.random() * 16 let random = Math.random() * 16
if (timestamp > 0) { if (timestamp > 0) {
random = Math.trunc((timestamp + random) % 16) random = Math.trunc((timestamp + random) % 16)
@@ -268,90 +274,89 @@ export function getUuid() {
random = Math.trunc((perforNow + random) % 16) random = Math.trunc((perforNow + random) % 16)
perforNow = Math.floor(perforNow / 16) perforNow = Math.floor(perforNow / 16)
} }
return (c === "x" ? random : (random & 0x3) | 0x8).toString(16) return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16)
}) })
} }
export function rarityClass(type: string) { export function rarityClass(type: string) {
switch (type) { switch (type) {
case "太初": case '太初':
return "primeval" return 'primeval'
case "史诗": case '史诗':
return "epic" return 'epic'
case "神话": case '神话':
return "myth" return 'myth'
case "智慧产物": case '智慧产物':
return "epic" return 'epic'
case "神器": case '神器':
return "artifact" return 'artifact'
case "稀有": case '稀有':
return "rare" return 'rare'
case "传说": case '传说':
return "legend" return 'legend'
case "勇者": case '勇者':
return "chronicle" return 'chronicle'
case "高级": case '高级':
return "advanced" return 'advanced'
default: default:
return "epic" return 'epic'
} }
} }
export function formatAttr(type:string,value:number){ export function formatAttr(type: string, value: number) {
switch(type){ switch (type) {
case "STR": case 'STR':
return `力量 ${value}` return `力量 ${value}`
case "INT": case 'INT':
return `智力 ${value}` return `智力 ${value}`
case "Vitality": case 'Vitality':
return `体力 ${value}` return `体力 ${value}`
case "Spirit": case 'Spirit':
return `精神 ${value}` return `精神 ${value}`
case "AtkP": case 'AtkP':
return `物理攻击力 ${value}` return `物理攻击力 ${value}`
case "AtkM": case 'AtkM':
return `魔法攻击力 ${value}` return `魔法攻击力 ${value}`
case "AtkI": case 'AtkI':
return `独立攻击力 ${value}` return `独立攻击力 ${value}`
case "SkillAttack": case 'SkillAttack':
return `技能伤害 +${(value * 100).toFixed(2)}%` return `技能伤害 +${(value * 100).toFixed(2)}%`
case "Attack": case 'Attack':
return `攻击强化 +${(value).toFixed(1)}%` return `攻击强化 +${value.toFixed(1)}%`
case "Buffer": case 'Buffer':
return `增益量 ${(value)}` return `增益量 ${value}`
default: default:
return `${type} ${value}` return `${type} ${value}`
} }
} }
export const decrypt = (encryptedData: string) => {
try {
encryptedData = decodeURIComponent(encryptedData)
.split('')
.map((a) => {
if (a == '=') {
return a
} else {
return String.fromCharCode(a.charCodeAt(0) - 1)
}
})
.join('')
const decryptedData = atob(encryptedData)
export const decrypt = (encryptedData:string)=>{ const len = decryptedData.length
try{ const decryptedBinaryOffset = decryptedData.slice(0, len - 13)
encryptedData = decodeURIComponent(encryptedData).split("").map(a=>{ const decryptedTimestamp = decryptedData.slice(len - 13, len)
if(a=='='){
return a
}
else{
return String.fromCharCode(a.charCodeAt(0) - 1)
}
}).join("")
const decryptedData = atob(encryptedData);
const len = decryptedData.length;
const decryptedBinaryOffset = decryptedData.slice(0, len-13);
const decryptedTimestamp = decryptedData.slice(len-13, len);
return { return {
uid:parseInt(decryptedBinaryOffset,2), uid: parseInt(decryptedBinaryOffset, 2),
time:parseInt(decryptedTimestamp) time: parseInt(decryptedTimestamp),
} }
} } catch (e) {
catch(e){
console.log(e) console.log(e)
return { return {
uid:-1, uid: -1,
time:0 time: 0,
} }
} }
} }
+40 -28
View File
@@ -1,8 +1,8 @@
import type { MaybeRef } from "@vueuse/core" import type { MaybeRef } from '@vueuse/core'
import querystring from "query-string" import querystring from 'query-string'
import { h, isRef } from "vue" import { h, isRef } from 'vue'
import { useRouter } from "vue-router" import { useRouter } from 'vue-router'
import { useDialog } from "@/components/hooks/dialog" import { useDialog } from '@/components/hooks/dialog'
interface UseOpenOption { interface UseOpenOption {
/* /*
@@ -16,7 +16,7 @@ interface UseOpenOption {
/** /**
* 是否打开新页面 * 是否打开新页面
*/ */
target?: "_blank" | "_self" | false target?: '_blank' | '_self' | false
title?: string title?: string
} }
@@ -25,13 +25,27 @@ export function useOpenWindow(opt?: UseOpenOption & { immediate?: boolean }) {
const { show, close, randomId } = useDialog() const { show, close, randomId } = useDialog()
const router = useRouter() const router = useRouter()
return async (maybeUrl: MaybeRef<string> | (() => string), { width = 0, height = 0, target = "_self", query = {}, title = "DNF计算器 & Colg", scale = 1.0 }: UseOpenOption = {}) => { return async (
let url = isRef(maybeUrl) ? maybeUrl.value : typeof maybeUrl === "function" ? maybeUrl() : maybeUrl maybeUrl: MaybeRef<string> | (() => string),
{
width = 0,
height = 0,
target = '_self',
query = {},
title = 'DNF计算器 & Colg',
scale = 1.0,
}: UseOpenOption = {},
) => {
let url = isRef(maybeUrl)
? maybeUrl.value
: typeof maybeUrl === 'function'
? maybeUrl()
: maybeUrl
if (!url) { if (!url) {
return return
} }
if (url.startsWith("/")) { if (url.startsWith('/')) {
url = router.resolve(url).href url = router.resolve(url).href
} }
query.r = `${Math.random()}` query.r = `${Math.random()}`
@@ -40,50 +54,48 @@ export function useOpenWindow(opt?: UseOpenOption & { immediate?: boolean }) {
let _target = target let _target = target
if (!_target) { if (!_target) {
const rs = await show({ const rs = await show({
content: "请确认打开新页面的方式", content: '请确认打开新页面的方式',
okButton: "当前窗口", okButton: '当前窗口',
rejectButton: "新窗口", rejectButton: '新窗口',
cancelButton: true, cancelButton: true,
defaultStatus: "cancel" defaultStatus: 'cancel',
}) })
if (rs.isOk) { if (rs.isOk) {
_target = "_self" _target = '_self'
} else if (rs.isReject) { } else if (rs.isReject) {
_target = "_blank" _target = '_blank'
} }
} }
if (_target == "_blank" || width * height < 1) { if (_target == '_blank' || width * height < 1) {
window.open(url, _target as string) window.open(url, _target as string)
} else if (_target == '_self') {
} else if (_target == "_self") {
const id = randomId() const id = randomId()
const onClose = (e: MessageEvent) => { const onClose = (e: MessageEvent) => {
if (e.data == "close") { if (e.data == 'close') {
close(id) close(id)
} }
} }
window.addEventListener("message", onClose) window.addEventListener('message', onClose)
const temp = await show({ const temp = await show({
id, id,
header: title, header: title,
mask: true, mask: true,
scale, scale,
content() { content() {
return h("iframe", { return h('iframe', {
name: Math.random().toString(36).slice(2), name: Math.random().toString(36).slice(2),
referrerpolicy: "no-referrer", referrerpolicy: 'no-referrer',
frameborder: 0, frameborder: 0,
src: url, src: url,
style: { style: {
width: `${width}px`, width: `${width}px`,
height: `${height}px` height: `${height}px`,
} },
}) })
} },
}) })
window.removeEventListener("message", onClose) window.removeEventListener('message', onClose)
return temp return temp
} }
} catch (err) { } catch (err) {
+1 -1
View File
@@ -1,6 +1,6 @@
import { defineConfig } from 'unocss' import { defineConfig } from 'unocss'
// import { presetAttributify } from 'unocss' // import { presetAttributify } from 'unocss'
import { blackA, green } from '@radix-ui/colors' // import { blackA, green } from '@radix-ui/colors'
export default defineConfig({ export default defineConfig({
// presets: [presetAttributify()], // presets: [presetAttributify()],