@esershnr/artalk-sidebar 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +16 -0
  2. package/auto-imports.d.ts +76 -0
  3. package/components.d.ts +31 -0
  4. package/env.d.ts +2 -0
  5. package/index.html +15 -0
  6. package/package.json +32 -0
  7. package/public/favicon.png +0 -0
  8. package/public/robots.txt +2 -0
  9. package/src/App.vue +89 -0
  10. package/src/artalk.ts +82 -0
  11. package/src/assets/favicon.png +0 -0
  12. package/src/assets/icon-darkmode-off.svg +1 -0
  13. package/src/assets/icon-darkmode-on.svg +1 -0
  14. package/src/assets/icon-eye-off.svg +1 -0
  15. package/src/assets/icon-eye-on.svg +1 -0
  16. package/src/assets/nav-icon-comments.svg +1 -0
  17. package/src/assets/nav-icon-pages.svg +1 -0
  18. package/src/assets/nav-icon-search.svg +1 -0
  19. package/src/assets/nav-icon-settings.svg +1 -0
  20. package/src/assets/nav-icon-sites.svg +1 -0
  21. package/src/assets/nav-icon-transfer.svg +1 -0
  22. package/src/assets/nav-icon-users.svg +1 -0
  23. package/src/components/AppHeader.vue +235 -0
  24. package/src/components/AppNavigation.vue +11 -0
  25. package/src/components/AppNavigationDesktop.vue +176 -0
  26. package/src/components/AppNavigationMenu.ts +152 -0
  27. package/src/components/AppNavigationMobile.vue +187 -0
  28. package/src/components/AppNavigationSearch.vue +137 -0
  29. package/src/components/FileUploader.vue +149 -0
  30. package/src/components/ItemTextEditor.vue +130 -0
  31. package/src/components/LoadingLayer.vue +37 -0
  32. package/src/components/LogTerminal.vue +89 -0
  33. package/src/components/PageEditor.vue +171 -0
  34. package/src/components/Pagination.vue +253 -0
  35. package/src/components/PreferenceArr.vue +105 -0
  36. package/src/components/PreferenceGrp.vue +153 -0
  37. package/src/components/PreferenceItem.vue +159 -0
  38. package/src/components/SiteCreate.vue +96 -0
  39. package/src/components/SiteEditor.vue +138 -0
  40. package/src/components/SiteSwitcher.vue +184 -0
  41. package/src/components/UserEditor.vue +229 -0
  42. package/src/global.ts +62 -0
  43. package/src/hooks/MobileWidth.ts +27 -0
  44. package/src/i18n/fr.ts +103 -0
  45. package/src/i18n/ja.ts +100 -0
  46. package/src/i18n/ko.ts +99 -0
  47. package/src/i18n/ru.ts +102 -0
  48. package/src/i18n/tr.ts +102 -0
  49. package/src/i18n/zh-CN.ts +97 -0
  50. package/src/i18n/zh-TW.ts +97 -0
  51. package/src/i18n-en.ts +99 -0
  52. package/src/i18n.ts +37 -0
  53. package/src/lib/promise-polyfill.ts +9 -0
  54. package/src/lib/settings-option.ts +186 -0
  55. package/src/lib/settings-sensitive.ts +44 -0
  56. package/src/lib/settings.ts +94 -0
  57. package/src/main.ts +65 -0
  58. package/src/pages/comments.vue +110 -0
  59. package/src/pages/index.vue +33 -0
  60. package/src/pages/login.vue +245 -0
  61. package/src/pages/pages.vue +309 -0
  62. package/src/pages/settings.vue +181 -0
  63. package/src/pages/sites.vue +353 -0
  64. package/src/pages/transfer.vue +204 -0
  65. package/src/pages/users.vue +271 -0
  66. package/src/stores/nav.ts +114 -0
  67. package/src/stores/user.ts +48 -0
  68. package/src/style/_extends.scss +100 -0
  69. package/src/style/_variables.scss +18 -0
  70. package/src/style.scss +245 -0
  71. package/src/vue-i18n.d.ts +7 -0
  72. package/tsconfig.json +40 -0
  73. package/tsconfig.node.json +11 -0
  74. package/typed-router.d.ts +30 -0
  75. package/vite.config.ts +71 -0
@@ -0,0 +1,110 @@
1
+ <script setup lang="ts">
2
+ import { storeToRefs } from 'pinia'
3
+ import { artalk } from '../global'
4
+ import { useNavStore } from '../stores/nav'
5
+ import { useUserStore } from '../stores/user'
6
+
7
+ const wrapEl = ref<HTMLElement>()
8
+ const listEl = ref<HTMLElement>()
9
+ const user = useUserStore()
10
+ const nav = useNavStore()
11
+ const { curtTab } = storeToRefs(nav)
12
+ const { site: curtSite } = storeToRefs(user)
13
+
14
+ const search = ref('')
15
+
16
+ onMounted(() => {
17
+ // 初始化导航条
18
+ if (user.is_admin) {
19
+ nav.updateTabs(
20
+ {
21
+ all: 'all',
22
+ pending: 'pending',
23
+ personal_all: 'personal',
24
+ },
25
+ 'all',
26
+ )
27
+ } else {
28
+ nav.updateTabs(
29
+ {
30
+ all: 'all',
31
+ mentions: 'mentions',
32
+ mine: 'mine',
33
+ pending: 'pending',
34
+ },
35
+ 'all',
36
+ )
37
+ }
38
+
39
+ watch(curtTab, (curtTab) => {
40
+ artalk!.ctx.fetch({
41
+ offset: 0,
42
+ })
43
+ })
44
+
45
+ watch(curtSite, (value) => {
46
+ artalk!.ctx.reload()
47
+ })
48
+
49
+ artalk!.ctx.on('comment-rendered', (comment) => {
50
+ const pageURL = comment.getData().page_url
51
+ comment.getRender().setOpenURL(`${pageURL}#atk-comment-${comment.getID()}`)
52
+ })
53
+
54
+ artalk!.ctx.updateConf({
55
+ listFetchParamsModifier: (params) => {
56
+ params.site_name = curtSite.value
57
+
58
+ let scope = user.is_admin ? 'site' : 'user'
59
+ let type = curtTab.value
60
+
61
+ if (curtTab.value === 'personal_all') {
62
+ scope = 'user'
63
+ type = 'all'
64
+ }
65
+
66
+ params.scope = scope
67
+ params.type = type
68
+
69
+ if (search.value) params.search = search.value
70
+ },
71
+ scrollRelativeTo: () => wrapEl.value!,
72
+ })
73
+
74
+ artalk!.reload()
75
+
76
+ const $el = artalk!.ctx.inject('list').getEl()
77
+
78
+ $el.querySelector<HTMLElement>('.atk-list-header')!.style.display = 'none'
79
+ $el.querySelector<HTMLElement>('.atk-list-footer')!.style.display = 'none'
80
+
81
+ listEl.value?.append($el)
82
+
83
+ // Comments search
84
+ nav.enableSearch(
85
+ (value: string) => {
86
+ search.value = value
87
+ artalk!.reload()
88
+ },
89
+ () => {
90
+ if (search.value === '') return
91
+ search.value = ''
92
+ artalk!.reload()
93
+ },
94
+ )
95
+ })
96
+ </script>
97
+
98
+ <template>
99
+ <div ref="wrapEl" class="comments-wrap">
100
+ <div ref="listEl" />
101
+ </div>
102
+ </template>
103
+
104
+ <style scoped lang="scss">
105
+ .comments-wrap {
106
+ :deep(.atk-comment-wrap) {
107
+ border-bottom: 1px solid var(--at-color-border);
108
+ }
109
+ }
110
+ </style>
@@ -0,0 +1,33 @@
1
+ <route lang="json">
2
+ {
3
+ "meta": {
4
+ "title": "Artalk Sidebar"
5
+ }
6
+ }
7
+ </route>
8
+
9
+ <script setup lang="ts">
10
+ import { bootParams } from '../global'
11
+
12
+ const router = useRouter()
13
+
14
+ onMounted(() => {
15
+ if (bootParams.view) {
16
+ const splitted = bootParams.view.split('|')
17
+ if (splitted[0]) bootParams.view = splitted[0]
18
+ if (splitted[1]) bootParams.viewParams = JSON.parse(splitted[1])
19
+ }
20
+
21
+ const LinkMap: { [key: string]: string } = {
22
+ comments: '/comments',
23
+ pages: '/pages',
24
+ sites: '/sites',
25
+ settings: '/settings',
26
+ }
27
+ router.replace(LinkMap[bootParams.view] || '/comments')
28
+ })
29
+ </script>
30
+
31
+ <template>
32
+ <div></div>
33
+ </template>
@@ -0,0 +1,245 @@
1
+ <route lang="json">
2
+ {
3
+ "meta": {
4
+ "title": "Login - Artalk Sidebar"
5
+ }
6
+ }
7
+ </route>
8
+
9
+ <script setup lang="ts">
10
+ import type { ArtalkType } from '@esershnr/artalk'
11
+ import { getArtalk } from '../global'
12
+ import { useUserStore } from '../stores/user'
13
+
14
+ const router = useRouter()
15
+ const { t } = useI18n()
16
+
17
+ let userForm = ref({
18
+ email: '',
19
+ password: '',
20
+ })
21
+ let version = ref('')
22
+ let buildHash = ref('')
23
+ let loginErr = ref('')
24
+ let userSelector = ref<string[] | null>(null)
25
+
26
+ onMounted(() => {
27
+ getArtalk()!
28
+ .ctx.getApi()
29
+ .version.getVersion()
30
+ .then((res) => {
31
+ version.value = res.data.version
32
+ buildHash.value = res.data.commit_hash
33
+ })
34
+ })
35
+
36
+ function onFocus() {
37
+ loginErr.value = ''
38
+ }
39
+
40
+ function login(username?: string) {
41
+ loginErr.value = ''
42
+
43
+ const artalk = getArtalk()
44
+ if (!artalk) throw new Error('Artalk instance not initialized')
45
+
46
+ artalk.ctx
47
+ .getApi()
48
+ .user.login({
49
+ name: username || '',
50
+ email: userForm.value.email,
51
+ password: userForm.value.password,
52
+ })
53
+ .then((res) => {
54
+ artalk.ctx.getUser().update({
55
+ ...res.data.user,
56
+ token: res.data.token,
57
+ })
58
+ useUserStore().sync()
59
+ router.replace('/')
60
+ })
61
+ .catch((e: ArtalkType.FetchError) => {
62
+ if (e.data?.need_name_select) {
63
+ userSelector.value = e.data?.need_name_select
64
+ } else {
65
+ loginErr.value = e.message || t('loginFailure')
66
+ }
67
+ })
68
+ }
69
+
70
+ function selectUser(username: string) {
71
+ userSelector.value = null
72
+ login(username)
73
+ }
74
+
75
+ const versionInfo = computed(() => {
76
+ return `v${version.value + (buildHash.value ? ' / ' + buildHash.value : '')}`
77
+ })
78
+ </script>
79
+
80
+ <template>
81
+ <div class="login-dialog">
82
+ <a href="https://artalk.js.org/" target="_blank">
83
+ <img class="logo" src="../assets/favicon.png" alt="logo" draggable="false" />
84
+ </a>
85
+ <form class="login-form" @submit.prevent="login()">
86
+ <input v-model="userForm.email" type="text" :placeholder="t('email')" @focus="onFocus" />
87
+ <input
88
+ v-model="userForm.password"
89
+ type="password"
90
+ :placeholder="t('password')"
91
+ @focus="onFocus"
92
+ />
93
+ <div v-if="!!loginErr" class="err-msg atk-fade-in">{{ loginErr }}</div>
94
+ <button type="submit">{{ t('login') }}</button>
95
+ </form>
96
+ <div class="copyright">
97
+ Powered by
98
+ <a href="https://artalk.js.org" target="_blank">Artalk</a>
99
+ ({{ versionInfo }})
100
+ </div>
101
+
102
+ <div v-if="userSelector" class="layer">
103
+ <div class="user-selector atk-fade-in">
104
+ <div class="text">{{ t('loginSelectHint') }}</div>
105
+ <div class="user-list">
106
+ <div v-for="(u, i) in userSelector" :key="i" class="item" @click="selectUser(u)">
107
+ {{ u }}
108
+ </div>
109
+ </div>
110
+ </div>
111
+ </div>
112
+ </div>
113
+ </template>
114
+
115
+ <style lang="scss" scoped>
116
+ .login-dialog {
117
+ z-index: 11;
118
+ position: fixed;
119
+ display: flex;
120
+ flex-direction: column;
121
+ place-items: center;
122
+ justify-content: center;
123
+ top: 0;
124
+ left: 0;
125
+ background: var(--at-color-bg);
126
+ width: 100%;
127
+ height: 100%;
128
+
129
+ .logo {
130
+ user-select: none;
131
+ display: inline-block;
132
+ width: 50px;
133
+ height: 50px;
134
+ margin-bottom: 50px;
135
+ margin-top: -50px;
136
+ border-radius: 2px;
137
+ }
138
+
139
+ .copyright {
140
+ font-size: 13px;
141
+ color: var(--at-color-meta);
142
+ position: fixed;
143
+ height: 30px;
144
+ bottom: 0;
145
+ width: 100%;
146
+ text-align: center;
147
+ }
148
+ }
149
+
150
+ .login-form {
151
+ display: flex;
152
+ flex-direction: column;
153
+ width: 280px;
154
+
155
+ input {
156
+ display: block;
157
+ border: 0;
158
+ background: transparent;
159
+ border-bottom: 1px solid var(--at-color-border);
160
+ padding: 0 20px;
161
+ line-height: 40px;
162
+ margin-bottom: 10px;
163
+
164
+ &:focus {
165
+ outline: none;
166
+ }
167
+ }
168
+
169
+ button {
170
+ color: #fff;
171
+ background: #6e8392;
172
+ border-radius: 2px;
173
+ cursor: pointer;
174
+ border: 0;
175
+ line-height: 35px;
176
+ padding: 0 20px;
177
+ margin-top: 20px;
178
+ display: block;
179
+
180
+ &:hover {
181
+ opacity: 0.95;
182
+ }
183
+ }
184
+
185
+ .err-msg {
186
+ display: flex;
187
+ flex-direction: row;
188
+ align-items: center;
189
+ margin: 0 10px;
190
+ margin-top: 5px;
191
+ padding: 0 10px;
192
+ color: var(--at-color-red);
193
+
194
+ &::before {
195
+ content: '';
196
+ display: inline-block;
197
+ vertical-align: bottom;
198
+ height: 19px;
199
+ width: 19px;
200
+ margin-right: 6px;
201
+ background-size: contain;
202
+ background-position: center;
203
+ background-repeat: no-repeat;
204
+ background-image: url("data:image/svg+xml,%3Csvg width='19' height='19' viewBox='0 0 19 19' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M9.5 15.8332C12.9978 15.8332 15.8333 12.9976 15.8333 9.49984C15.8333 6.00203 12.9978 3.1665 9.50001 3.1665C6.0022 3.1665 3.16667 6.00203 3.16667 9.49984C3.16667 12.9976 6.0022 15.8332 9.5 15.8332ZM17.4167 9.49984C17.4167 13.8721 13.8723 17.4165 9.5 17.4165C5.12775 17.4165 1.58334 13.8721 1.58334 9.49984C1.58334 5.12758 5.12775 1.58317 9.50001 1.58317C13.8723 1.58317 17.4167 5.12758 17.4167 9.49984ZM8.70834 13.4581L8.70834 11.8748L10.2917 11.8748L10.2917 13.4581L8.70834 13.4581ZM10.2917 5.54148L10.2917 11.0831L8.70834 11.0831L8.70834 5.54148L10.2917 5.54148Z' fill='%23F53F3F'/%3E%3C/svg%3E");
205
+ }
206
+ }
207
+ }
208
+
209
+ .layer {
210
+ position: fixed;
211
+ width: 100%;
212
+ height: 100%;
213
+ background: var(--at-color-bg-transl);
214
+ }
215
+
216
+ .user-selector {
217
+ z-index: 999;
218
+ position: fixed;
219
+ left: 50%;
220
+ top: 43.5%;
221
+ transform: translate(-50%, -50%);
222
+ background: var(--at-color-bg);
223
+ border: 1px solid var(--at-color-border);
224
+ padding-bottom: 10px;
225
+ width: 280px;
226
+
227
+ & > .text {
228
+ font-size: 15px;
229
+ padding: 15px 20px;
230
+ }
231
+
232
+ .user-list {
233
+ & > .item {
234
+ cursor: pointer;
235
+ padding: 10px 20px;
236
+ transition: border 0.3s ease;
237
+ border-left: 2px solid transparent;
238
+
239
+ &:hover {
240
+ border-left: 2px solid #6e8392;
241
+ }
242
+ }
243
+ }
244
+ }
245
+ </style>
@@ -0,0 +1,309 @@
1
+ <script setup lang="ts">
2
+ import { storeToRefs } from 'pinia'
3
+ import type { ArtalkType } from '@esershnr/artalk'
4
+ import { artalk } from '../global'
5
+ import { useNavStore } from '../stores/nav'
6
+ import { useUserStore } from '../stores/user'
7
+ import Pagination from '../components/Pagination.vue'
8
+
9
+ const nav = useNavStore()
10
+ const { site: curtSite } = storeToRefs(useUserStore())
11
+ const pages = ref<ArtalkType.PageData[]>([])
12
+ const curtEditPageID = ref<number | null>(null)
13
+ const { t } = useI18n()
14
+
15
+ const pageSize = ref(20)
16
+ const pageTotal = ref(0)
17
+ const search = ref('')
18
+ const pagination = ref<InstanceType<typeof Pagination>>()
19
+ const showActBarBorder = ref(false)
20
+ const refreshBtn = ref({
21
+ isRun: false,
22
+ statusText: '',
23
+ })
24
+
25
+ onMounted(() => {
26
+ nav.updateTabs(
27
+ {
28
+ all: 'all',
29
+ },
30
+ 'all',
31
+ )
32
+
33
+ // Users search
34
+ nav.enableSearch(
35
+ (value: string) => {
36
+ search.value = value
37
+ fetchPages(0)
38
+ },
39
+ () => {
40
+ if (search.value === '') return
41
+ search.value = ''
42
+ fetchPages(0)
43
+ },
44
+ )
45
+
46
+ fetchPages(0)
47
+
48
+ // Refresh task status recovery
49
+ getRefreshTaskStatus().then((d) => {
50
+ if (d.is_progress === true) {
51
+ refreshBtn.value.isRun = true
52
+ refreshBtn.value.statusText = d.msg
53
+ startRefreshTaskWatchdog()
54
+ }
55
+ })
56
+ })
57
+
58
+ watch(curtSite, () => {
59
+ pagination.value?.reset()
60
+ fetchPages(0)
61
+ })
62
+
63
+ onMounted(() => nav.scrollableArea?.addEventListener('scroll', scrollHandler))
64
+ onUnmounted(() => nav.scrollableArea?.removeEventListener('scroll', scrollHandler))
65
+
66
+ function scrollHandler() {
67
+ showActBarBorder.value = nav.scrollableArea!.scrollTop > 10
68
+ }
69
+
70
+ function editPage(page: ArtalkType.PageData) {
71
+ curtEditPageID.value = page.id
72
+ }
73
+
74
+ function fetchPages(offset: number) {
75
+ if (offset === 0) pagination.value?.reset()
76
+ nav.setPageLoading(true)
77
+ artalk?.ctx
78
+ .getApi()
79
+ .pages.getPages({
80
+ site_name: curtSite.value,
81
+ offset: offset,
82
+ limit: pageSize.value,
83
+ search: search.value,
84
+ })
85
+ .then((res) => {
86
+ pageTotal.value = res.data.count
87
+ pages.value = res.data.pages
88
+ nav.scrollPageToTop()
89
+ })
90
+ .finally(() => {
91
+ nav.setPageLoading(false)
92
+ })
93
+ }
94
+
95
+ function onChangePage(offset: number) {
96
+ fetchPages(offset)
97
+ }
98
+
99
+ function onPageItemUpdate(page: ArtalkType.PageData) {
100
+ const index = pages.value.findIndex((p) => p.id === page.id)
101
+ if (index != -1) {
102
+ const orgPage = pages.value[index]
103
+ Object.keys(page).forEach((key) => {
104
+ ;(orgPage as any)[key] = (page as any)[key]
105
+ })
106
+ }
107
+ }
108
+
109
+ function onPageItemRemove(id: number) {
110
+ const index = pages.value.findIndex((p) => p.id === id)
111
+ pages.value.splice(index, 1)
112
+ }
113
+
114
+ async function getRefreshTaskStatus() {
115
+ return (await artalk!.ctx.getApi().pages.getPageFetchStatus()).data
116
+ }
117
+
118
+ function startRefreshTaskWatchdog() {
119
+ // TODO: Not perfect polling update status
120
+ const timerID = window.setInterval(async () => {
121
+ const d = await getRefreshTaskStatus()
122
+
123
+ if (d.is_progress === false) {
124
+ clearInterval(timerID)
125
+ setRefreshTaskDone()
126
+ return
127
+ }
128
+
129
+ refreshBtn.value.statusText = d.msg
130
+ }, 1000)
131
+ }
132
+
133
+ function setRefreshTaskDone() {
134
+ refreshBtn.value.statusText = t('updateComplete')
135
+ window.setTimeout(() => {
136
+ refreshBtn.value.isRun = false
137
+ }, 1500)
138
+ }
139
+
140
+ async function refreshAllPages() {
141
+ if (refreshBtn.value.isRun) return
142
+ refreshBtn.value.isRun = true
143
+ refreshBtn.value.statusText = t('updateReady')
144
+
145
+ try {
146
+ await artalk!.ctx.getApi().pages.fetchAllPages({
147
+ site_name: curtSite.value,
148
+ })
149
+ } catch (err: any) {
150
+ alert(err.msg)
151
+ setRefreshTaskDone()
152
+ return
153
+ }
154
+
155
+ startRefreshTaskWatchdog()
156
+ }
157
+
158
+ function cacheFlush() {
159
+ artalk!.ctx
160
+ .getApi()
161
+ .cache.flushCache()
162
+ .then((res) => alert(res.data.msg))
163
+ .catch(() => alert(t('opFailed')))
164
+ }
165
+
166
+ function cacheWarm() {
167
+ artalk!.ctx
168
+ .getApi()
169
+ .cache.warmUpCache()
170
+ .then((res) => alert(res.data.msg))
171
+ .catch(() => alert(t('opFailed')))
172
+ }
173
+
174
+ function openPage(url: string) {
175
+ window.open(url)
176
+ }
177
+ </script>
178
+
179
+ <template>
180
+ <div class="atk-page-list-wrap">
181
+ <div class="atk-header-action-bar" :class="{ bordered: showActBarBorder }">
182
+ <span class="atk-update-all-title-btn" @click="refreshAllPages()">
183
+ <i class="atk-icon atk-icon-sync" :class="{ 'atk-rotate': refreshBtn.isRun }"></i>
184
+ <span class="atk-text">
185
+ {{ refreshBtn.isRun ? refreshBtn.statusText : t('updateTitle') }}
186
+ </span>
187
+ </span>
188
+ <span class="atk-cache-flush-all-btn" @click="cacheFlush()">
189
+ <span class="atk-text">{{ t('cacheClear') }}</span>
190
+ </span>
191
+ <span class="atk-cache-warm-up-btn" @click="cacheWarm()">
192
+ <span class="atk-text">{{ t('cacheWarm') }}</span>
193
+ </span>
194
+ </div>
195
+ <div class="atk-page-list">
196
+ <div v-for="page in pages" :key="page.id" class="atk-page-item">
197
+ <div class="atk-page-main">
198
+ <div class="atk-title" @click="openPage(page.url)">
199
+ {{ page.title }}
200
+ </div>
201
+ <div class="atk-sub" @click="openPage(page.url)">{{ page.url }}</div>
202
+ </div>
203
+ <div class="atk-page-actions">
204
+ <div class="atk-item atk-edit-btn" @click="editPage(page)">
205
+ <i class="atk-icon atk-icon-edit"></i>
206
+ </div>
207
+ </div>
208
+ <PageEditor
209
+ v-if="curtEditPageID === page.id"
210
+ :page="page"
211
+ @close="curtEditPageID = null"
212
+ @update="onPageItemUpdate"
213
+ @remove="onPageItemRemove"
214
+ />
215
+ </div>
216
+ </div>
217
+ <Pagination
218
+ ref="pagination"
219
+ :page-size="pageSize"
220
+ :total="pageTotal"
221
+ :disabled="nav.isPageLoading"
222
+ @change="onChangePage"
223
+ />
224
+ </div>
225
+ </template>
226
+
227
+ <style scoped lang="scss">
228
+ .atk-page-list-wrap {
229
+ .atk-header-action-bar {
230
+ position: sticky;
231
+ top: 0;
232
+ display: flex;
233
+ align-items: center;
234
+ overflow: hidden;
235
+ padding: 10px 15px 0 15px;
236
+ background: var(--at-color-bg);
237
+ z-index: 10;
238
+ border-bottom: 1px solid transparent;
239
+ transition: 0.3s ease-out padding;
240
+
241
+ &.bordered {
242
+ padding-bottom: 10px;
243
+ border-color: var(--at-color-border);
244
+ }
245
+
246
+ & > span {
247
+ display: inline-flex;
248
+ align-items: center;
249
+ flex-direction: row;
250
+ padding: 2px 10px;
251
+ cursor: pointer;
252
+ font-size: 13px;
253
+
254
+ i {
255
+ display: inline-block;
256
+ width: 14px;
257
+ height: 14px;
258
+ margin-right: 5px;
259
+
260
+ &::after {
261
+ background-color: var(--at-color-meta);
262
+ }
263
+ }
264
+
265
+ &:hover {
266
+ background: var(--at-color-bg-grey);
267
+ }
268
+ }
269
+ }
270
+ }
271
+
272
+ .atk-page-list {
273
+ .atk-page-item {
274
+ display: flex;
275
+ flex-direction: row;
276
+ position: relative;
277
+ min-height: 120px;
278
+ align-items: center;
279
+
280
+ &:not(:last-child) {
281
+ border-bottom: 1px solid var(--at-color-border);
282
+ }
283
+ }
284
+
285
+ .atk-page-main {
286
+ display: flex;
287
+ flex-direction: column;
288
+ flex: auto;
289
+ padding: 20px 30px;
290
+
291
+ .atk-title {
292
+ color: var(--at-color-font);
293
+ font-size: 21px;
294
+ margin-bottom: 10px;
295
+ cursor: pointer;
296
+ }
297
+
298
+ .atk-sub {
299
+ color: var(--at-color-sub);
300
+ font-size: 14px;
301
+ cursor: pointer;
302
+ }
303
+ }
304
+
305
+ :deep(.atk-page-actions) {
306
+ @extend .atk-list-btn-actions;
307
+ }
308
+ }
309
+ </style>