Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
a04ae28a
Commit
a04ae28a
authored
Apr 13, 2026
by
陈曦
Browse files
merge v0.1.111
parents
68f67198
ad64190b
Changes
302
Hide whitespace changes
Inline
Side-by-side
Too many changes to show.
To preserve performance only
302 of 302+
files are displayed.
Plain diff
Email patch
frontend/src/views/admin/AnnouncementsView.vue
View file @
a04ae28a
...
@@ -39,7 +39,15 @@
...
@@ -39,7 +39,15 @@
</
template
>
</
template
>
<
template
#table
>
<
template
#table
>
<DataTable
:columns=
"columns"
:data=
"announcements"
:loading=
"loading"
>
<DataTable
:columns=
"columns"
:data=
"announcements"
:loading=
"loading"
:server-side-sort=
"true"
default-sort-key=
"created_at"
default-sort-order=
"desc"
@
sort=
"handleSort"
>
<template
#cell-title
="
{ value, row }">
<template
#cell-title
="
{ value, row }">
<div
class=
"min-w-0"
>
<div
class=
"min-w-0"
>
<div
class=
"flex items-center gap-2"
>
<div
class=
"flex items-center gap-2"
>
...
@@ -68,7 +76,7 @@
...
@@ -68,7 +76,7 @@
</span>
</span>
</
template
>
</
template
>
<
template
#cell-notify
M
ode=
"{ row }"
>
<
template
#cell-notify
_m
ode=
"{ row }"
>
<span
<span
:class=
"[
:class=
"[
'badge',
'badge',
...
@@ -100,7 +108,7 @@
...
@@ -100,7 +108,7 @@
</div>
</div>
</
template
>
</
template
>
<
template
#cell-created
A
t=
"{ value }"
>
<
template
#cell-created
_a
t=
"{ value }"
>
<span
class=
"text-sm text-gray-500 dark:text-dark-400"
>
{{
formatDateTime
(
value
)
}}
</span>
<span
class=
"text-sm text-gray-500 dark:text-dark-400"
>
{{
formatDateTime
(
value
)
}}
</span>
</
template
>
</
template
>
...
@@ -236,7 +244,7 @@
...
@@ -236,7 +244,7 @@
</template>
</template>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
computed
,
onMounted
,
reactive
,
ref
}
from
'
vue
'
import
{
computed
,
onMounted
,
onUnmounted
,
reactive
,
ref
}
from
'
vue
'
import
{
useI18n
}
from
'
vue-i18n
'
import
{
useI18n
}
from
'
vue-i18n
'
import
{
useAppStore
}
from
'
@/stores/app
'
import
{
useAppStore
}
from
'
@/stores/app
'
import
{
getPersistedPageSize
}
from
'
@/composables/usePersistedPageSize
'
import
{
getPersistedPageSize
}
from
'
@/composables/usePersistedPageSize
'
...
@@ -276,6 +284,11 @@ const pagination = reactive({
...
@@ -276,6 +284,11 @@ const pagination = reactive({
pages
:
0
pages
:
0
})
})
const
sortState
=
reactive
({
sort_by
:
'
created_at
'
,
sort_order
:
'
desc
'
as
'
asc
'
|
'
desc
'
})
const
statusFilterOptions
=
computed
(()
=>
[
const
statusFilterOptions
=
computed
(()
=>
[
{
value
:
''
,
label
:
t
(
'
admin.announcements.allStatus
'
)
},
{
value
:
''
,
label
:
t
(
'
admin.announcements.allStatus
'
)
},
{
value
:
'
draft
'
,
label
:
t
(
'
admin.announcements.statusLabels.draft
'
)
},
{
value
:
'
draft
'
,
label
:
t
(
'
admin.announcements.statusLabels.draft
'
)
},
...
@@ -295,12 +308,12 @@ const notifyModeOptions = computed(() => [
...
@@ -295,12 +308,12 @@ const notifyModeOptions = computed(() => [
])
])
const
columns
=
computed
<
Column
[]
>
(()
=>
[
const
columns
=
computed
<
Column
[]
>
(()
=>
[
{
key
:
'
title
'
,
label
:
t
(
'
admin.announcements.columns.title
'
)
},
{
key
:
'
title
'
,
label
:
t
(
'
admin.announcements.columns.title
'
)
,
sortable
:
true
},
{
key
:
'
status
'
,
label
:
t
(
'
admin.announcements.columns.status
'
)
},
{
key
:
'
status
'
,
label
:
t
(
'
admin.announcements.columns.status
'
)
,
sortable
:
true
},
{
key
:
'
notify
M
ode
'
,
label
:
t
(
'
admin.announcements.columns.notifyMode
'
)
},
{
key
:
'
notify
_m
ode
'
,
label
:
t
(
'
admin.announcements.columns.notifyMode
'
)
,
sortable
:
true
},
{
key
:
'
targeting
'
,
label
:
t
(
'
admin.announcements.columns.targeting
'
)
},
{
key
:
'
targeting
'
,
label
:
t
(
'
admin.announcements.columns.targeting
'
)
},
{
key
:
'
timeRange
'
,
label
:
t
(
'
admin.announcements.columns.timeRange
'
)
},
{
key
:
'
timeRange
'
,
label
:
t
(
'
admin.announcements.columns.timeRange
'
)
},
{
key
:
'
created
A
t
'
,
label
:
t
(
'
admin.announcements.columns.createdAt
'
)
},
{
key
:
'
created
_a
t
'
,
label
:
t
(
'
admin.announcements.columns.createdAt
'
)
,
sortable
:
true
},
{
key
:
'
actions
'
,
label
:
t
(
'
admin.announcements.columns.actions
'
)
}
{
key
:
'
actions
'
,
label
:
t
(
'
admin.announcements.columns.actions
'
)
}
])
])
...
@@ -321,15 +334,21 @@ const targetingSummary = (targeting: AnnouncementTargeting) => {
...
@@ -321,15 +334,21 @@ const targetingSummary = (targeting: AnnouncementTargeting) => {
let
currentController
:
AbortController
|
null
=
null
let
currentController
:
AbortController
|
null
=
null
async
function
loadAnnouncements
()
{
async
function
loadAnnouncements
()
{
if
(
currentController
)
currentController
.
abort
()
currentController
?.
abort
()
currentController
=
new
AbortController
()
const
requestController
=
new
AbortController
()
currentController
=
requestController
const
{
signal
}
=
requestController
try
{
try
{
loading
.
value
=
true
loading
.
value
=
true
const
res
=
await
adminAPI
.
announcements
.
list
(
pagination
.
page
,
pagination
.
page_size
,
{
const
res
=
await
adminAPI
.
announcements
.
list
(
pagination
.
page
,
pagination
.
page_size
,
{
status
:
filters
.
status
||
undefined
,
status
:
filters
.
status
||
undefined
,
search
:
searchQuery
.
value
||
undefined
search
:
searchQuery
.
value
||
undefined
,
})
sort_by
:
sortState
.
sort_by
,
sort_order
:
sortState
.
sort_order
},
{
signal
})
if
(
signal
.
aborted
||
currentController
!==
requestController
)
return
announcements
.
value
=
res
.
items
announcements
.
value
=
res
.
items
pagination
.
total
=
res
.
total
pagination
.
total
=
res
.
total
...
@@ -337,11 +356,21 @@ async function loadAnnouncements() {
...
@@ -337,11 +356,21 @@ async function loadAnnouncements() {
pagination
.
page
=
res
.
page
pagination
.
page
=
res
.
page
pagination
.
page_size
=
res
.
page_size
pagination
.
page_size
=
res
.
page_size
}
catch
(
error
:
any
)
{
}
catch
(
error
:
any
)
{
if
(
currentController
.
signal
.
aborted
||
error
?.
name
===
'
AbortError
'
)
return
if
(
signal
.
aborted
||
currentController
!==
requestController
||
error
?.
name
===
'
AbortError
'
||
error
?.
code
===
'
ERR_CANCELED
'
)
{
return
}
console
.
error
(
'
Error loading announcements:
'
,
error
)
console
.
error
(
'
Error loading announcements:
'
,
error
)
appStore
.
showError
(
error
.
response
?.
data
?.
detail
||
t
(
'
admin.announcements.failedToLoad
'
))
appStore
.
showError
(
error
.
response
?.
data
?.
detail
||
t
(
'
admin.announcements.failedToLoad
'
))
}
finally
{
}
finally
{
loading
.
value
=
false
if
(
currentController
===
requestController
)
{
loading
.
value
=
false
currentController
=
null
}
}
}
}
}
...
@@ -361,6 +390,13 @@ function handleStatusChange() {
...
@@ -361,6 +390,13 @@ function handleStatusChange() {
loadAnnouncements
()
loadAnnouncements
()
}
}
function
handleSort
(
key
:
string
,
order
:
'
asc
'
|
'
desc
'
)
{
sortState
.
sort_by
=
key
sortState
.
sort_order
=
order
pagination
.
page
=
1
loadAnnouncements
()
}
let
searchDebounceTimer
:
number
|
null
=
null
let
searchDebounceTimer
:
number
|
null
=
null
function
handleSearch
()
{
function
handleSearch
()
{
if
(
searchDebounceTimer
)
window
.
clearTimeout
(
searchDebounceTimer
)
if
(
searchDebounceTimer
)
window
.
clearTimeout
(
searchDebounceTimer
)
...
@@ -562,4 +598,9 @@ onMounted(async () => {
...
@@ -562,4 +598,9 @@ onMounted(async () => {
await
loadSubscriptionGroups
()
await
loadSubscriptionGroups
()
await
loadAnnouncements
()
await
loadAnnouncements
()
})
})
onUnmounted
(()
=>
{
if
(
searchDebounceTimer
)
window
.
clearTimeout
(
searchDebounceTimer
)
currentController
?.
abort
()
})
</
script
>
</
script
>
frontend/src/views/admin/ChannelsView.vue
View file @
a04ae28a
...
@@ -48,7 +48,15 @@
...
@@ -48,7 +48,15 @@
</
template
>
</
template
>
<
template
#table
>
<
template
#table
>
<DataTable
:columns=
"columns"
:data=
"channels"
:loading=
"loading"
>
<DataTable
:columns=
"columns"
:data=
"channels"
:loading=
"loading"
:server-side-sort=
"true"
default-sort-key=
"created_at"
default-sort-order=
"desc"
@
sort=
"handleSort"
>
<template
#cell-name
="
{ value }">
<template
#cell-name
="
{ value }">
<span
class=
"font-medium text-gray-900 dark:text-white"
>
{{
value
}}
</span>
<span
class=
"font-medium text-gray-900 dark:text-white"
>
{{
value
}}
</span>
</
template
>
</
template
>
...
@@ -486,6 +494,10 @@ const pagination = reactive({
...
@@ -486,6 +494,10 @@ const pagination = reactive({
page_size
:
getPersistedPageSize
(),
page_size
:
getPersistedPageSize
(),
total
:
0
total
:
0
})
})
const
sortState
=
reactive
({
sort_by
:
'
created_at
'
,
sort_order
:
'
desc
'
as
'
asc
'
|
'
desc
'
})
// Dialog state
// Dialog state
const
showDialog
=
ref
(
false
)
const
showDialog
=
ref
(
false
)
...
@@ -766,7 +778,9 @@ async function loadChannels() {
...
@@ -766,7 +778,9 @@ async function loadChannels() {
try
{
try
{
const
response
=
await
adminAPI
.
channels
.
list
(
pagination
.
page
,
pagination
.
page_size
,
{
const
response
=
await
adminAPI
.
channels
.
list
(
pagination
.
page
,
pagination
.
page_size
,
{
status
:
filters
.
status
||
undefined
,
status
:
filters
.
status
||
undefined
,
search
:
searchQuery
.
value
||
undefined
search
:
searchQuery
.
value
||
undefined
,
sort_by
:
sortState
.
sort_by
,
sort_order
:
sortState
.
sort_order
},
{
signal
:
ctrl
.
signal
})
},
{
signal
:
ctrl
.
signal
})
if
(
ctrl
.
signal
.
aborted
||
abortController
!==
ctrl
)
return
if
(
ctrl
.
signal
.
aborted
||
abortController
!==
ctrl
)
return
...
@@ -825,6 +839,13 @@ function handlePageSizeChange(pageSize: number) {
...
@@ -825,6 +839,13 @@ function handlePageSizeChange(pageSize: number) {
loadChannels
()
loadChannels
()
}
}
function
handleSort
(
key
:
string
,
order
:
'
asc
'
|
'
desc
'
)
{
sortState
.
sort_by
=
key
sortState
.
sort_order
=
order
pagination
.
page
=
1
loadChannels
()
}
// ── Dialog ──
// ── Dialog ──
function
resetForm
()
{
function
resetForm
()
{
form
.
name
=
''
form
.
name
=
''
...
...
Prev
1
…
12
13
14
15
16
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment