Skip to content

Commit 1e38172

Browse files
committed
feat: semi
1 parent 834a7d8 commit 1e38172

File tree

18 files changed

+505
-183
lines changed

18 files changed

+505
-183
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,12 @@
3434
"@rsdoctor/rspack-plugin": "^1.5.2",
3535
"@rspack/cli": "^1.7.6",
3636
"@rspack/core": "^1.7.6",
37-
"@swc/core": "^1.15.13",
37+
"@swc/core": "^1.15.17",
3838
"@swc/jest": "^0.2.39",
3939
"@types/chrome": "0.1.37",
4040
"@types/decompress": "^4.2.7",
4141
"@types/jest": "^30.0.0",
42-
"@types/node": "^25.3.1",
42+
"@types/node": "^25.3.2",
4343
"@types/punycode": "^2.1.4",
4444
"@vue/babel-plugin-jsx": "^2.0.1",
4545
"babel-loader": "^10.0.0",

src/pages/app/Layout/menu/item.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ export const menuGroups = (): MenuGroup[] => [{
7676
}, {
7777
title: msg => msg.menu.limit,
7878
route: '/behavior/limit',
79-
icon: Timer
79+
icon: Timer,
80+
mobile: true,
8081
}]
8182
}, {
8283
title: msg => msg.menu.additional,

src/pages/app/components/Limit/LimitFilter.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,19 @@ import SwitchFilterItem from "@app/components/common/filter/SwitchFilterItem"
1212
import { t } from "@app/locale"
1313
import { OPTION_ROUTE } from "@app/router/constants"
1414
import { Delete, Open, Operation, Plus, SetUp, TurnOff, WarningFilled } from "@element-plus/icons-vue"
15+
import { useXsState } from '@hooks/useMediaSize'
1516
import Flex from "@pages/components/Flex"
1617
import { getAppPageUrl } from "@util/constant/url"
1718
import { ElIcon, ElText, ElTooltip } from 'element-plus'
18-
import { defineComponent, ref, Ref, watch } from "vue"
19+
import { computed, defineComponent, ref, Ref, watch } from "vue"
1920
import DropdownButton, { type DropdownButtonItem } from "../common/DropdownButton"
2021
import { useLimitAction, useLimitBatch, useLimitFilter } from "./context"
2122

2223
const optionPageUrl = getAppPageUrl(OPTION_ROUTE, { i: 'limit' })
2324

2425
type BatchOpt = 'delete' | 'enable' | 'disable'
2526

26-
const useCreateTip = (empty: Ref<boolean>) => {
27+
const useCreateTip = (empty: Ref<boolean>, isXs: Ref<boolean>) => {
2728
const tipVisible = ref(false)
2829
let initialized = false
2930
watch(empty, emptyVal => {
@@ -33,12 +34,14 @@ const useCreateTip = (empty: Ref<boolean>) => {
3334
setTimeout(closeTip, 10000)
3435
})
3536
const closeTip = () => tipVisible.value = false
36-
return { tipVisible, closeTip }
37+
const finalVisible = computed(() => !isXs.value && tipVisible.value)
38+
return { tipVisible: finalVisible, closeTip }
3739
}
3840

3941
const _default = defineComponent(() => {
4042
const { create, test, empty } = useLimitAction()
41-
const { tipVisible, closeTip } = useCreateTip(empty)
43+
const isXs = useXsState()
44+
const { tipVisible, closeTip } = useCreateTip(empty, isXs)
4245
const filter = useLimitFilter()
4346
const { batchDelete, batchDisable, batchEnable } = useLimitBatch()
4447

@@ -75,20 +78,22 @@ const _default = defineComponent(() => {
7578
onSearch={val => filter.url = val}
7679
/>
7780
<SwitchFilterItem
81+
v-show={!isXs.value}
7882
historyName="onlyEnabled"
7983
label={t(msg => msg.limit.filterDisabled)}
8084
defaultValue={filter.onlyEnabled}
8185
onChange={val => filter.onlyEnabled = val}
8286
/>
8387
</Flex>
84-
<Flex gap={10}>
85-
<DropdownButton items={batchItems} />
88+
<Flex gap={10} align='center'>
89+
<DropdownButton v-show={!isXs.value} items={batchItems} />
8690
<ButtonFilterItem
8791
text={msg => msg.limit.button.test}
8892
icon={Operation}
8993
onClick={test}
9094
/>
9195
<ButtonFilterItem
96+
v-show={!isXs.value}
9297
text={msg => msg.base.option}
9398
icon={SetUp}
9499
onClick={() => createTabAfterCurrent(optionPageUrl)}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/**
2+
* Copyright (c) 2021 Hengyang Zhang
3+
*
4+
* This software is released under the MIT License.
5+
* https://opensource.org/licenses/MIT
6+
*/
7+
import { t } from "@app/locale"
8+
import { CircleCheck, Clock, Delete, EditPen, Lock } from "@element-plus/icons-vue"
9+
import { css } from '@emotion/css'
10+
import Flex from "@pages/components/Flex"
11+
import { ElButton, ElCard, ElCheckboxButton, ElCheckboxGroup, ElDivider, ElIcon, ElMessageBox, ElTag, useNamespace } from "element-plus"
12+
import { createStringUnionGuard } from 'typescript-guard'
13+
import { type Component, computed, defineComponent, FunctionalComponent, h, type StyleValue } from "vue"
14+
import { verifyCanModify } from "../common"
15+
import { useLimitAction, useLimitTable } from "../context"
16+
import Rule from "./Rule"
17+
18+
type Props = {
19+
value: timer.limit.Item
20+
}
21+
22+
type Flag = keyof timer.limit.Rule & ('enabled' | 'locked' | 'allowDelay')
23+
const isFlag = createStringUnionGuard<Flag>('allowDelay', 'enabled', 'locked')
24+
25+
const FLAG_GROUP: [Flag, Component][] = [
26+
['enabled', CircleCheck],
27+
['allowDelay', Clock],
28+
['locked', Lock],
29+
]
30+
31+
const FlagRadioGroup = defineComponent<ModelValue<Record<Flag, boolean>>>(props => {
32+
const selected = computed(() => Object.entries(props.modelValue).filter(([_, v]) => !!v).map(e => e[0] as Flag))
33+
return () => (
34+
<ElCheckboxGroup
35+
modelValue={selected.value}
36+
onChange={selected => {
37+
const newVal = { ...props.modelValue }
38+
selected.forEach(s => isFlag(s) && (newVal[s] = true))
39+
props.onChange?.(newVal)
40+
}}
41+
>
42+
{FLAG_GROUP.map(([value, icon]) => (
43+
<ElCheckboxButton value={value}>
44+
<span style={{ margin: '-6px' } satisfies StyleValue}>
45+
<ElIcon size='small'>{h(icon)}</ElIcon>
46+
</span>
47+
</ElCheckboxButton>
48+
))}
49+
</ElCheckboxGroup>
50+
)
51+
}, {
52+
props: ['modelValue', 'onChange']
53+
})
54+
55+
const CARD_PADDING = 10
56+
57+
const useStyle = () => {
58+
const cardNs = useNamespace('card')
59+
const cardCls = css`
60+
.${cardNs.e('body')} {
61+
padding: ${CARD_PADDING}px;
62+
}
63+
`
64+
return cardCls
65+
}
66+
67+
const ALL_WEEKDAYS = t(msg => msg.calendar.weekDays).split('|')
68+
69+
const Divider: FunctionalComponent<{}> = () => {
70+
const marginInline = `${-CARD_PADDING}px`
71+
const width = `calc(100% + ${CARD_PADDING * 2}px)`
72+
return <ElDivider style={{ marginBlock: '6px', marginInline, width } satisfies StyleValue} />
73+
}
74+
75+
const _default = defineComponent<Props>(props => {
76+
const { deleteRow } = useLimitTable()
77+
const { modify } = useLimitAction()
78+
79+
const handleModify = () => verifyCanModify(props.value)
80+
.then(() => modify(props.value))
81+
.catch(() => {/** Do nothing */ })
82+
83+
const handleDelete = () => verifyCanModify(props.value)
84+
.then(() => ElMessageBox.confirm(t(msg => msg.limit.message.deleteConfirm, { name: props.value.name })))
85+
.then(() => deleteRow(props.value))
86+
.catch(() => {/** Do nothing */ })
87+
88+
const renderEffectiveTag = (weekdays: number[] | undefined) => {
89+
if (!weekdays || weekdays.length === 0 || weekdays.length === 7) {
90+
return (
91+
<ElTag size="small" type="success">
92+
{t(msg => msg.calendar.range.everyday)}
93+
</ElTag>
94+
)
95+
}
96+
97+
if (weekdays.length === 1) {
98+
return (
99+
<ElTag size="small">
100+
{ALL_WEEKDAYS?.[weekdays[0]]}
101+
</ElTag>
102+
)
103+
}
104+
105+
const firstDay = ALL_WEEKDAYS?.[weekdays[0]]
106+
const allDays = weekdays.map(w => ALL_WEEKDAYS?.[w]).join('、')
107+
108+
return <ElTag size="small">{firstDay}...${allDays.length}</ElTag>
109+
}
110+
111+
const clz = useStyle()
112+
113+
return () => (
114+
<ElCard class={clz} shadow="hover">
115+
<Flex column gap={8}>
116+
<Flex justify="space-between" align="center">
117+
<Flex align="center" gap={8}>
118+
<span style={{ fontWeight: 'bold' }}>
119+
{props.value.name || '-'}
120+
</span>
121+
{renderEffectiveTag(props.value.weekdays)}
122+
</Flex>
123+
<ElButton
124+
size="small"
125+
type='danger'
126+
text
127+
icon={Delete}
128+
onClick={handleDelete}
129+
/>
130+
</Flex>
131+
<Divider />
132+
{/* Sites */}
133+
<Flex gap={2}>
134+
{props.value.cond.map((c, idx) => <ElTag key={idx} type='info'>{c}</ElTag>)}
135+
</Flex>
136+
<Divider />
137+
{/** Content */}
138+
<Rule value={props.value} />
139+
<Divider />
140+
{/* Footer Button */}
141+
<Flex justify='between'>
142+
<FlagRadioGroup modelValue={props.value} onChange={() => {/** todo */ }} />
143+
<ElButton text icon={EditPen} onClick={handleModify} size='small' />
144+
</Flex>
145+
</Flex>
146+
</ElCard >
147+
)
148+
}, { props: ['value'] })
149+
150+
export default _default
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { t } from '@app/locale'
2+
import Flex from '@pages/components/Flex'
3+
import { isEffective, meetLimit, meetTimeLimit, period2Str } from '@util/limit'
4+
import { formatPeriodCommon, MILL_PER_SECOND } from '@util/time'
5+
import { ElDescriptions, ElDescriptionsItem, ElTag } from 'element-plus'
6+
import { computed, defineComponent, FunctionalComponent, toRefs } from 'vue'
7+
import { DAILY_WEEKLY_TAG_TYPE, PERIOD_TAG_TYPE, VISIT_TAG_TYPE } from '../common'
8+
9+
type Props = {
10+
value: timer.limit.Item
11+
}
12+
13+
const TimeCountPair: FunctionalComponent<{ time?: number, count?: number }> = ({ time, count }) => {
14+
if (!time && !count) return null
15+
return (
16+
<Flex gap={2} wrap>
17+
{!!time && (
18+
<ElTag size="small" type={DAILY_WEEKLY_TAG_TYPE}>{formatPeriodCommon(time * MILL_PER_SECOND, true)}</ElTag>
19+
)}
20+
{!!count && (
21+
<ElTag size="small" type={DAILY_WEEKLY_TAG_TYPE}>{`${count} ${t(msg => msg.limit.item.visits)}`}</ElTag>
22+
)}
23+
</Flex>
24+
)
25+
}
26+
27+
type WastePairProps = {
28+
time?: number
29+
waste: number
30+
count?: number
31+
visit?: number
32+
delayCount?: number
33+
allowDelay?: boolean
34+
}
35+
36+
const WastePair: FunctionalComponent<WastePairProps> = props => {
37+
const timeType = computed(() => meetTimeLimit(props.time, props.waste, props.allowDelay, props.delayCount) ? 'danger' : 'info')
38+
const visitType = computed(() => meetLimit(props.count, props.visit) ? 'danger' : 'info')
39+
40+
return (
41+
<Flex gap={2}>
42+
<ElTag size="small" type={timeType.value}>
43+
{formatPeriodCommon(props.waste)}
44+
</ElTag>
45+
<ElTag size="small" type={visitType.value}>
46+
{props.visit ?? 0} {t(msg => msg.limit.item.visits)}
47+
</ElTag>
48+
</Flex>
49+
)
50+
}
51+
52+
const Rule = defineComponent<Props>(({ value }) => {
53+
const {
54+
time, count, waste, visit,
55+
weekly, weeklyCount, weeklyWaste, weeklyVisit,
56+
visitTime, periods,
57+
weekdays,
58+
allowDelay, delayCount, weeklyDelayCount,
59+
} = toRefs(value)
60+
61+
return () => <>
62+
<ElDescriptions border size='small' column={1} labelWidth={130}>
63+
<ElDescriptionsItem label={t(msg => msg.limit.item.daily)} v-show={time?.value || count?.value}>
64+
<TimeCountPair time={time?.value} count={count?.value} />
65+
</ElDescriptionsItem>
66+
<ElDescriptionsItem label={t(msg => msg.limit.item.weekly)} v-show={weekly?.value || weeklyCount?.value}>
67+
<TimeCountPair time={weekly?.value} count={weeklyCount?.value} />
68+
</ElDescriptionsItem>
69+
{!!visitTime?.value && (
70+
<ElDescriptionsItem label={t(msg => msg.limit.item.visitTime)}>
71+
<ElTag size="small" type={VISIT_TAG_TYPE}>{formatPeriodCommon(visitTime.value * MILL_PER_SECOND, true)}</ElTag>
72+
</ElDescriptionsItem>
73+
)}
74+
{!!periods?.value?.length && (
75+
<ElDescriptionsItem label={t(msg => msg.limit.item.period)}>
76+
<Flex gap={2} wrap>
77+
{periods.value.map((p, idx) => (
78+
<ElTag key={idx} size="small" type={PERIOD_TAG_TYPE}>{period2Str(p)}</ElTag>
79+
))}
80+
</Flex>
81+
</ElDescriptionsItem>
82+
)}
83+
</ElDescriptions>
84+
<ElDescriptions border size='small' column={1} labelWidth={130}>
85+
<ElDescriptionsItem label={t(msg => msg.calendar.range.today)}>
86+
{isEffective(weekdays?.value) ? (
87+
<WastePair
88+
waste={waste?.value}
89+
time={time?.value}
90+
count={count?.value}
91+
visit={visit?.value}
92+
allowDelay={allowDelay?.value}
93+
delayCount={delayCount?.value}
94+
/>
95+
) : (
96+
<ElTag type="info" size="small">
97+
{t(msg => msg.limit.item.notEffective)}
98+
</ElTag>
99+
)}
100+
</ElDescriptionsItem>
101+
<ElDescriptionsItem label={t(msg => msg.calendar.range.thisWeek)}>
102+
<WastePair
103+
waste={weeklyWaste?.value}
104+
time={weekly?.value}
105+
count={weeklyCount?.value}
106+
visit={weeklyVisit?.value}
107+
allowDelay={allowDelay?.value}
108+
delayCount={weeklyDelayCount?.value}
109+
/>
110+
</ElDescriptionsItem>
111+
</ElDescriptions>
112+
</>
113+
}, { props: ['value'] })
114+
115+
export default Rule
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Copyright (c) 2021 Hengyang Zhang
3+
*
4+
* This software is released under the MIT License.
5+
* https://opensource.org/licenses/MIT
6+
*/
7+
import Flex from '@pages/components/Flex'
8+
import { defineComponent } from "vue"
9+
import { useLimitTable } from "../context"
10+
import Card from "./Card"
11+
12+
const _default = defineComponent(() => {
13+
const { list } = useLimitTable()
14+
15+
return () => (
16+
<Flex column padding={8} gap={10} height="100%" style={{ overflow: 'auto' }}>
17+
{list.value.map(row => <Card key={row.id} value={row} />)}
18+
</Flex>
19+
)
20+
})
21+
22+
export default _default

0 commit comments

Comments
 (0)