Skip to content

Commit dd76b76

Browse files
committed
feat: semi
1 parent 73c4d25 commit dd76b76

File tree

11 files changed

+154
-28
lines changed

11 files changed

+154
-28
lines changed

src/database/site-cate-database.ts

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ type Item = {
1515
* Name
1616
*/
1717
n: string
18+
/**
19+
* Auto rules
20+
*/
21+
a?: string[]
1822
}
1923

2024
type Items = Record<number, Item>
@@ -49,43 +53,58 @@ class SiteCateDatabase extends BaseDatabase {
4953

5054
async listAll(): Promise<timer.site.Cate[]> {
5155
const items = await this.getItems()
52-
return Object.entries(items).map(([id, { n = '' } = {}]) => {
56+
return Object.entries(items).map(([id, { n = '', a } = {}]) => {
5357
return {
5458
id: parseInt(id),
5559
name: n,
60+
autoRules: a ?? [],
5661
} satisfies timer.site.Cate
5762
})
5863
}
5964

60-
async add(name: string): Promise<timer.site.Cate> {
65+
async add(name: string, autoRules: string[]): Promise<timer.site.Cate> {
6166
const items = await this.getItems()
6267
const existId = Object.entries(items).find(([_, v]) => v.n === name)?.[0]
6368
if (existId) {
6469
// Exist already
65-
return { id: parseInt(existId), name }
70+
return { id: parseInt(existId), name, autoRules }
6671
}
6772

6873
const id = (Object.keys(items || {}).map(k => parseInt(k)).sort().reverse()?.[0] ?? 0) + 1
69-
items[id] = { n: name || items[id]?.n }
74+
items[id] = { n: name || items[id]?.n, a: autoRules }
7075

7176
await this.saveItems(items)
72-
return { name, id }
77+
return { name, id, autoRules }
7378
}
7479

75-
async update(id: number, name: string): Promise<void> {
76-
if (!name) return
77-
80+
private async updateWithReplacer(id: number, replacer: (exist: Item) => Item): Promise<void> {
7881
const items = await this.getItems()
79-
const existId = Object.entries(items).find(([_, v]) => v.n === name)?.[0]
82+
const exist = items[id]
83+
if (!exist) return
8084

81-
if (existId) {
85+
const replaced = replacer(exist)
86+
87+
if (Object.entries(items).some(([vid, v]) => v.n === replaced.n && parseInt(vid) !== id)) {
88+
// Name exist already
8289
return
8390
}
8491

85-
items[id] = { ...items[id] || {}, n: name }
92+
items[id] = replaced
8693
await this.saveItems(items)
8794
}
8895

96+
async updateName(id: number, name: string): Promise<void> {
97+
await this.updateWithReplacer(id, exist => ({ ...exist, n: name }))
98+
}
99+
100+
async update(cate: timer.site.Cate): Promise<void> {
101+
await this.updateWithReplacer(cate.id, exist => ({
102+
...exist,
103+
n: cate.name,
104+
a: cate.autoRules,
105+
}))
106+
}
107+
89108
async importData(data: any): Promise<void> {
90109
let toImport = data[KEY] as Items
91110
// Not import

src/i18n/message/app/site-manage-resource.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@
113113
"relatedMsg": "This category has been associated with {siteCount} sites and cannot be deleted",
114114
"removeConfirm": "Confirm to delete category: {category}?",
115115
"batchChange": "Change categories",
116-
"batchDisassociate": "Disassociate categories"
116+
"batchDisassociate": "Disassociate categories",
117+
"auto": "Auto-categorization"
117118
},
118119
"form": {
119120
"emptyAlias": "Please enter site name",

src/i18n/message/app/site-manage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export type SiteManageMessage = {
2323
batchChange: string
2424
batchDisassociate: string
2525
removeConfirm: string
26+
auto: string
2627
}
2728
form: {
2829
emptyAlias: string

src/pages/app/components/Report/ReportTable/index.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,13 @@ const _default = defineComponent((_, ctx) => {
108108
() => filter.siteMerge,
109109
], () => table.value?.doLayout?.())
110110

111+
const handleCateChange = (key: timer.site.SiteKey, newCateId: number | undefined) => {
112+
data.value?.list
113+
?.filter(isSite)
114+
?.filter(item => siteEqual(item.siteKey, key))
115+
?.forEach(i => i.cateId = newCateId)
116+
}
117+
111118
return () => (
112119
<ContentCard>
113120
<Flex gap={23} width="100%" height="100%" column>
@@ -138,7 +145,7 @@ const _default = defineComponent((_, ctx) => {
138145
/>
139146
</>}
140147
{visible.value.group && <GroupColumn />}
141-
{visible.value.cate && <CateColumn onChange={refresh} />}
148+
{visible.value.cate && <CateColumn onChange={handleCateChange} />}
142149
<TimeColumn dimension="focus" />
143150
{runColVisible.value && <TimeColumn dimension="run" />}
144151
<VisitColumn />

src/pages/app/components/SiteManage/SiteManageTable/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Flex from "@pages/components/Flex"
1010
import { removeIconUrl, saveSiteRunState } from '@service/site-service'
1111
import { ElSwitch, ElTable, ElTableColumn, type RenderRowData } from "element-plus"
1212
import { defineComponent } from "vue"
13-
import CategoryEditable from "../../common/category/CategoryEditable"
13+
import Category from "../../common/category/CategoryEditable"
1414
import { useSiteManageTable } from '../useSiteManage'
1515
import AliasColumn from "./column/AliasColumn"
1616
import OperationColumn from "./column/OperationColumn"
@@ -70,7 +70,7 @@ const _default = defineComponent<{}>(() => {
7070
minWidth={140}
7171
align="center"
7272
v-slots={({ row }: RenderRowData<timer.site.SiteInfo>) => (
73-
<CategoryEditable siteKey={row} modelValue={row?.cate} onChange={val => row.cate = val} />
73+
<Category siteKey={row} modelValue={row?.cate} onChange={val => row.cate = val} />
7474
)}
7575
/>
7676
<ElTableColumn
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
2+
import { t } from '@app/locale'
3+
import { ElButton, ElDialog, ElFormItem, ElInput } from 'element-plus'
4+
import { createVNode, defineComponent, reactive, ref, render } from 'vue'
5+
6+
type Props = {
7+
cate?: timer.site.Cate
8+
onClose?: NoArgCallback
9+
onSave?: (cate: timer.site.Cate) => void
10+
}
11+
12+
const Component = defineComponent<Props>(props => {
13+
const visible = ref(true)
14+
const formData = reactive<Partial<timer.site.Cate>>({ ...props.cate })
15+
16+
const handleConfirm = () => {
17+
18+
}
19+
20+
return () => (
21+
<ElDialog
22+
modelValue={visible.value}
23+
title={props.cate ? t(msg => msg.button.modify) : t(msg => msg.button.create)}
24+
width={400}
25+
destroyOnClose
26+
beforeClose={props.onClose}
27+
closeOnClickModal={false}
28+
v-slots={{
29+
footer: () => <>
30+
<ElButton onClick={props.onClose}>{t(msg => msg.button.cancel)}</ElButton>
31+
<ElButton type="primary" onClick={handleConfirm}>
32+
{t(msg => msg.button.confirm)}
33+
</ElButton>
34+
</>
35+
}}
36+
>
37+
<ElFormItem label={t(msg => msg.siteManage.cate.name)} prop="name">
38+
<ElInput v-model={formData.name} />
39+
</ElFormItem>
40+
<ElFormItem label={t(msg => msg.siteManage.cate.name)} prop="name">
41+
<ElInput v-model={formData.name} />
42+
</ElFormItem>
43+
</ElDialog>
44+
)
45+
}, { props: ['cate', 'onClose', 'onSave'] })
46+
47+
interface DialogOptions {
48+
cate?: timer.site.Cate
49+
}
50+
51+
function open(options: DialogOptions = {}): Promise<timer.site.Cate> {
52+
const { cate } = options
53+
54+
return new Promise<timer.site.Cate>((resolve, reject) => {
55+
const container = document.createElement('div')
56+
document.body.appendChild(container)
57+
58+
const cleanup = () => {
59+
render(null, container)
60+
document.body.removeChild(container)
61+
}
62+
63+
const vnode = createVNode(Component, {
64+
cate,
65+
onClose: async (data: timer.site.Cate) => {
66+
try {
67+
resolve(data)
68+
} catch (error) {
69+
reject(error)
70+
} finally {
71+
cleanup()
72+
}
73+
},
74+
onCancel: () => {
75+
reject()
76+
cleanup()
77+
}
78+
})
79+
80+
render(vnode, container)
81+
})
82+
}
83+
84+
85+
86+
type CategoryDialogDef = typeof Component & {
87+
open: typeof open
88+
}
89+
90+
const CategoryDialog = Component as CategoryDialogDef
91+
CategoryDialog.open = open
92+
93+
export default CategoryDialog

src/pages/app/components/common/category/CategorySelect/OptionItem.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import cateService from "@service/cate-service"
77
import { selectAllSites } from '@service/site-service'
88
import { stopPropagationAfter } from "@util/document"
99
import { ElButton, ElInput, ElMessage, ElMessageBox } from "element-plus"
10-
import { defineComponent, nextTick, ref } from "vue"
10+
import { defineComponent, ref } from "vue"
11+
import CategoryDialog from '../CategoryDialog'
1112

1213
const OptionItem = defineComponent<{ value: timer.site.Cate }>(props => {
1314
const cate = useCategory()
@@ -51,10 +52,10 @@ const OptionItem = defineComponent<{ value: timer.site.Cate }>(props => {
5152
}).then(removeCate).catch(() => { })
5253
}
5354

54-
const onEditClick = () => {
55-
setEditingName(props.value.name)
56-
openEditing()
57-
nextTick(() => inputRef.value?.focus?.())
55+
const onEditClick = async () => {
56+
const edited = await CategoryDialog.open({ cate: props.value })
57+
await cateService.save(edited)
58+
await cate.refresh()
5859
}
5960

6061
return () => (

src/pages/app/components/common/category/CategorySelect/index.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,16 @@ const CategorySelect = defineComponent<Props>((props, ctx) => {
3131
ref={selectRef}
3232
size={props.size}
3333
modelValue={props.modelValue}
34-
onChange={val => ctx.emit('change', val)}
35-
onVisible-change={visible => ctx.emit('visibleChange', visible)}
34+
onChange={props.onChange}
35+
onVisible-change={props.onVisibleChange}
3636
style={{ width: props.width || '100%' }}
3737
clearable={props.clearable}
38-
onClear={() => ctx.emit('change', undefined)}
38+
onClear={() => props.onChange?.(undefined)}
3939
emptyValues={[CATE_NOT_SET_ID, undefined]}
4040
v-slots={{ footer: () => <SelectFooter /> }}
4141
>
4242
{cate.all.map(c => (
43-
<ElOption value={c?.id} label={c?.name}>
43+
<ElOption value={c.id} label={c.name}>
4444
<OptionItem value={c} />
4545
</ElOption>
4646
))}

src/pages/app/components/common/filter/CategoryFilter.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const CategoryFilter = defineComponent<Props>(props => {
1313
const cate = useCategory()
1414

1515
const displayCategories = computed(() => [
16-
{ id: CATE_NOT_SET_ID, name: t(msg => msg.shared.cate.notSet) } satisfies timer.site.Cate,
16+
{ id: CATE_NOT_SET_ID, name: t(msg => msg.shared.cate.notSet), autoRules: [] } satisfies timer.site.Cate,
1717
...cate.all,
1818
])
1919

src/service/cate-service.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
import siteCateDatabase from "@db/site-cate-database"
22

33
class CateService {
4-
54
listAll(): Promise<timer.site.Cate[]> {
65
return siteCateDatabase.listAll()
76
}
87

98
add(name: string): Promise<timer.site.Cate> {
10-
return siteCateDatabase.add(name)
9+
return siteCateDatabase.add(name, [])
1110
}
1211

1312
saveName(id: number, name: string): Promise<void> {
14-
return siteCateDatabase.update(id, name)
13+
return siteCateDatabase.updateName(id, name)
14+
}
15+
16+
save(edited: timer.site.Cate) {
17+
return siteCateDatabase.update(edited)
1518
}
1619

1720
remove(id: number): Promise<void> {

0 commit comments

Comments
 (0)