artalk 2.2.3 → 2.2.6

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.
package/index.html CHANGED
@@ -32,6 +32,13 @@
32
32
  <!--<link rel="stylesheet"
33
33
  href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/styles/arta.min.css">
34
34
  <script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/highlight.min.js"></script>-->
35
+
36
+ <!-- For mobile testing -->
37
+ <script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
38
+ <script>
39
+ // VConsole will be exported to `window.VConsole` by default.
40
+ var vConsole = new window.VConsole();
41
+ </script>
35
42
  </head>
36
43
  <body>
37
44
  <div class="card billboard atk-fade-in">
@@ -57,7 +64,7 @@
57
64
  el: '#comments',
58
65
  pageKey: 'https://artalk.js.org/',
59
66
  pageTitle: 'Artalk Home',
60
- server: (import.meta.env.MODE === 'development') ? 'http://localhost:23366/api' : 'https://artalk.qwqaq.com/api',
67
+ server: (import.meta.env.MODE === 'development') ? 'http://localhost:23366' : 'https://artalk.qwqaq.com',
61
68
  site: 'ArtalkDemo',
62
69
  });
63
70
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "artalk",
3
- "version": "2.2.3",
4
- "backendMinVersion": "2.1.1",
3
+ "version": "2.2.6",
4
+ "backendMinVersion": "2.1.2",
5
5
  "license": "LGPL-3.0",
6
6
  "description": "A Selfhosted Comment System",
7
7
  "keywords": [
package/src/api/index.ts CHANGED
@@ -9,7 +9,7 @@ export default class Api {
9
9
 
10
10
  constructor (ctx: Context) {
11
11
  this.ctx = ctx
12
- this.baseURL = ctx.conf.server
12
+ this.baseURL = `${ctx.conf.server}/api`
13
13
  }
14
14
 
15
15
  // ============================
@@ -45,7 +45,7 @@ export default class Api {
45
45
  content: comment.content,
46
46
  rid: comment.rid,
47
47
  page_key: comment.page_key,
48
- ua: await Utils.getCorrectUserAgent(), // 需要后端支持,获取修正后的 UA (针对 Win11)
48
+ ua: await Utils.getCorrectUserAgent(), // 需要后端支持,获取修正后的 UA
49
49
  }
50
50
 
51
51
  if (comment.page_title) params.page_title = comment.page_title
@@ -119,8 +119,11 @@ export default class Api {
119
119
 
120
120
  /** 用户 · 登录状态 */
121
121
  public async loginStatus() {
122
- const data = await POST<any>(this.ctx, `${this.baseURL}/login-status`)
123
- return (data || { is_login: false }) as { is_login: boolean }
122
+ const data = await POST<any>(this.ctx, `${this.baseURL}/login-status`, {
123
+ name: this.ctx.user.data.nick,
124
+ email: this.ctx.user.data.email
125
+ })
126
+ return (data || { is_login: false, is_admin: false }) as { is_login: boolean, is_admin: boolean }
124
127
  }
125
128
 
126
129
  // ============================
@@ -164,13 +167,14 @@ export default class Api {
164
167
  }
165
168
 
166
169
  /** 页面 · 数据更新 */
167
- public async pageFetch(id: number) {
168
- const params: any = {
169
- id,
170
- }
170
+ public async pageFetch(id?: number, siteName?: string, getStatus?: boolean) {
171
+ const params: any = {}
172
+ if (id) params.id = id
173
+ if (siteName) params.site_name = siteName
174
+ if (getStatus) params.get_status = getStatus
171
175
 
172
176
  const d = await POST<any>(this.ctx, `${this.baseURL}/admin/page-fetch`, params)
173
- return (d.page as PageData)
177
+ return (d as any)
174
178
  }
175
179
 
176
180
  // ============================
package/src/artalk.ts CHANGED
@@ -9,7 +9,7 @@ import Editor from './components/editor'
9
9
  import List from './components/list'
10
10
  import SidebarLayer from './components/sidebar-layer'
11
11
 
12
- import { GetLayerWrap } from './components/layer'
12
+ import Layer, { GetLayerWrap } from './components/layer'
13
13
  import { EventPayloadMap, Handler } from '~/types/event'
14
14
  import Api from './api'
15
15
  import * as Utils from './lib/utils'
@@ -33,21 +33,31 @@ export default class Artalk {
33
33
  constructor (customConf: ArtalkConfig) {
34
34
  // 配置
35
35
  this.conf = { ...Artalk.defaults, ...customConf }
36
- this.conf.server = this.conf.server.replace(/\/$/, '')
36
+ this.conf.server = this.conf.server.replace(/\/$/, '').replace(/\/api\/?$/, '')
37
37
 
38
38
  // 默认 pageKey
39
39
  if (!this.conf.pageKey) {
40
- this.conf.pageKey = `${window.location.protocol}//${window.location.host}${window.location.pathname}`
40
+ // @link http://bl.ocks.org/abernier/3070589
41
+ this.conf.pageKey = `${window.location.pathname}`
42
+ }
43
+
44
+ // 默认 pageTitle
45
+ if (!this.conf.pageTitle) {
46
+ this.conf.pageTitle = `${document.title}`
41
47
  }
42
48
 
43
49
  // 装载元素
44
- try {
45
- const $root = document.querySelector<HTMLElement>(this.conf.el)
46
- if (!$root) throw Error(`Sorry, target element "${this.conf.el}" was not found.`)
47
- this.$root = $root
48
- } catch (e) {
49
- console.error(e)
50
- throw new Error('Please check your Artalk `el` config.')
50
+ if (!!this.conf.el && this.conf.el instanceof HTMLElement) {
51
+ this.$root = this.conf.el
52
+ } else {
53
+ try {
54
+ const $root = document.querySelector<HTMLElement>(this.conf.el)
55
+ if (!$root) throw Error(`Sorry, target element "${this.conf.el}" was not found.`)
56
+ this.$root = $root
57
+ } catch (e) {
58
+ console.error(e)
59
+ throw new Error('Please check your Artalk `el` config.')
60
+ }
51
61
  }
52
62
 
53
63
  // Context 初始化
@@ -56,6 +66,7 @@ export default class Artalk {
56
66
  // 界面初始化
57
67
  this.$root.classList.add('artalk')
58
68
  this.$root.innerHTML = ''
69
+ this.initLayer()
59
70
  this.initDarkMode()
60
71
 
61
72
  // 组件初始化
@@ -133,6 +144,13 @@ export default class Artalk {
133
144
  this.list.fetchComments(0)
134
145
  }
135
146
 
147
+ /** Layer 初始化 */
148
+ initLayer() {
149
+ // 记录页面原始 Styles
150
+ Layer.BodyOrgOverflow = document.body.style.overflow
151
+ Layer.BodyOrgPaddingRight = document.body.style.paddingRight
152
+ }
153
+
136
154
  /** 暗黑模式 · 初始化 */
137
155
  public initDarkMode() {
138
156
  const darkModeClassName = 'atk-dark-mode'
@@ -163,14 +181,15 @@ export default class Artalk {
163
181
 
164
182
  /** PV */
165
183
  public async initPV() {
184
+ const pvNum = await new Api(this.ctx).pv()
185
+ if (Number.isNaN(Number(pvNum)))
186
+ return
187
+
166
188
  if (!this.conf.pvEl || !document.querySelector(this.conf.pvEl))
167
189
  return
168
190
 
169
191
  const $pv = document.querySelector<HTMLElement>(this.conf.pvEl)!
170
192
  // $pv.innerText = '-' // 默认占位符
171
- const pvNum = await new Api(this.ctx).pv()
172
- if (Number.isNaN(Number(pvNum)))
173
- return
174
193
 
175
194
  $pv.innerText = String(pvNum)
176
195
  }
@@ -20,22 +20,33 @@ export default class EmoticonsPlug extends EditorPlug {
20
20
  public $grpWrap!: HTMLElement
21
21
  public $grpSwitcher!: HTMLElement
22
22
 
23
+ public loadingTask: Promise<void>|null = null
24
+
23
25
  constructor (public editor: Editor) {
24
26
  super(editor)
25
27
 
26
28
  this.$el = Utils.createElement(`<div class="atk-editor-plug-emoticons"></div>`)
27
-
28
- this.init()
29
29
  }
30
30
 
31
- async init() {
32
- // 数据处理
33
- Ui.showLoading(this.$el)
34
- this.emoticons = await this.handleData(this.ctx.conf.emoticons)
35
- Ui.hideLoading(this.$el)
31
+ isListLoaded = false
32
+ isImgLoaded = false
33
+
34
+ async loadEmoticonsData() {
35
+ if (this.isListLoaded) return
36
+ if (this.loadingTask !== null) {
37
+ await this.loadingTask
38
+ return
39
+ }
36
40
 
37
- // 初始化元素
38
- this.initEmoticonsList()
41
+ // 数据处理
42
+ this.loadingTask = (async () => {
43
+ Ui.showLoading(this.$el)
44
+ this.emoticons = await this.handleData(this.ctx.conf.emoticons)
45
+ Ui.hideLoading(this.$el)
46
+ this.loadingTask = null
47
+ this.isListLoaded = true
48
+ })()
49
+ await this.loadingTask
39
50
  }
40
51
 
41
52
  async handleData(data: any): Promise<any[]> {
@@ -85,7 +96,7 @@ export default class EmoticonsPlug extends EditorPlug {
85
96
  // 剔除非法数据
86
97
  data = data.filter((item: any) => (typeof item === 'object' && !Array.isArray(item) && !!item && !!item.name))
87
98
 
88
- console.log(data)
99
+ // console.log(data)
89
100
 
90
101
  this.solveNullKey(data)
91
102
  this.solveSameKey(data)
@@ -243,10 +254,20 @@ export default class EmoticonsPlug extends EditorPlug {
243
254
  }
244
255
 
245
256
  onShow () {
246
- // 延迟执行,防止无法读取高度
247
- setTimeout(() => {
248
- this.changeListHeight()
249
- }, 30)
257
+ ;(async () => {
258
+ await this.loadEmoticonsData()
259
+
260
+ // 初始化元素
261
+ if (!this.isImgLoaded) {
262
+ this.initEmoticonsList()
263
+ this.isImgLoaded = true
264
+ }
265
+
266
+ // 延迟执行,防止无法读取高度
267
+ setTimeout(() => {
268
+ this.changeListHeight()
269
+ }, 30)
270
+ })()
250
271
  }
251
272
 
252
273
  onHide () {
@@ -22,6 +22,7 @@ export default class Editor extends Component {
22
22
  public $bottom: HTMLElement
23
23
  public $plugBtnWrap: HTMLElement
24
24
  public $imgUploadBtn?: HTMLElement
25
+ public $imgUploadInput?: HTMLInputElement
25
26
  public $submitBtn: HTMLButtonElement
26
27
  public $notifyWrap: HTMLElement
27
28
 
@@ -204,6 +205,17 @@ export default class Editor extends Component {
204
205
  // 切换按钮
205
206
  const btnElem = Utils.createElement(`<span class="atk-plug-btn" data-plug-name="${PlugObj.Name}">${PlugObj.BtnHTML}</span>`)
206
207
  this.$plugBtnWrap.appendChild(btnElem)
208
+
209
+ // 表情包插件预加载
210
+ if (PlugObj.Name === 'emoticons') {
211
+ const emoPlug = new PlugObj(this) as EmoticonsPlug
212
+ this.plugList[PlugObj.Name] = emoPlug
213
+
214
+ window.setTimeout(() => {
215
+ emoPlug.loadEmoticonsData()
216
+ }, 1000) // 延迟 1s 加载
217
+ }
218
+
207
219
  btnElem.addEventListener('click', () => {
208
220
  let plug = this.plugList[PlugObj.Name]
209
221
  if (!plug) {
@@ -265,12 +277,16 @@ export default class Editor extends Component {
265
277
  this.$imgUploadBtn = Utils.createElement(`<span class="atk-plug-btn">图片</span>`)
266
278
  this.$plugBtnWrap.querySelector('[data-plug-name="preview"]')!.before(this.$imgUploadBtn) // 显示在预览图标之前
267
279
 
280
+ this.$imgUploadInput = document.createElement('input')
281
+ this.$imgUploadInput.type = 'file'
282
+ this.$imgUploadInput.style.display = 'none'
283
+ this.$imgUploadInput.accept = this.allowImgExts.map(o => `.${o}`).join(',')
284
+ this.$imgUploadBtn.after(this.$imgUploadInput)
285
+
268
286
  // 按钮点击
269
287
  this.$imgUploadBtn.onclick = () => {
270
288
  // 选择图片
271
- const $input = document.createElement('input')
272
- $input.type = 'file'
273
- $input.accept = this.allowImgExts.map(o => `.${o}`).join(',')
289
+ const $input = this.$imgUploadInput!
274
290
  $input.onchange = () => {
275
291
  (async () => { // 解决阻塞 UI 问题
276
292
  if (!$input.files || $input.files.length === 0) return
@@ -393,7 +409,11 @@ export default class Editor extends Component {
393
409
  if (!!this.plugList && !!this.plugList.preview) {
394
410
  this.plugList.preview.updateContent()
395
411
  }
396
- this.adjustTextareaHeight()
412
+
413
+ // 延迟执行防止无效
414
+ window.setTimeout(() => {
415
+ this.adjustTextareaHeight()
416
+ }, 80)
397
417
  }
398
418
 
399
419
  clearEditor () {
@@ -501,7 +521,7 @@ export default class Editor extends Component {
501
521
 
502
522
  // 回复不同页面的评论
503
523
  if (this.replyComment !== null && this.replyComment.page_key !== this.ctx.conf.pageKey) {
504
- window.open(`${this.replyComment.page_key}#atk-comment-${nComment.id}`)
524
+ window.open(`${this.replyComment.page_url}#atk-comment-${nComment.id}`)
505
525
  }
506
526
 
507
527
  this.ctx.trigger('list-insert', nComment)
@@ -563,5 +583,9 @@ export default class Editor extends Component {
563
583
  // 取消回复
564
584
  if (this.replyComment !== null) this.cancelReply()
565
585
  }
586
+
587
+ initRemoteEmoticons () {
588
+
589
+ }
566
590
  }
567
591
 
@@ -10,8 +10,8 @@ export default class Layer extends Component {
10
10
 
11
11
  private maskClickHideEnable: boolean = true
12
12
 
13
- private bodyStyleOrgOverflow = ''
14
- private bodyStyleOrgPaddingRight = ''
13
+ public static BodyOrgOverflow: string
14
+ public static BodyOrgPaddingRight: string
15
15
 
16
16
  public afterHide?: Function
17
17
 
@@ -73,10 +73,11 @@ export default class Layer extends Component {
73
73
  this.$wrap.classList.add('atk-fade-out')
74
74
  this.$el.style.display = 'none'
75
75
 
76
+ // body style 禁止滚动解除
77
+ this.pageBodyScrollBarShow()
78
+
76
79
  this.newActionTimer(() => {
77
80
  this.$wrap.style.display = 'none'
78
- // body style 禁止滚动解除
79
- this.pageBodyScrollBarShow()
80
81
  this.checkCleanLayer()
81
82
  }, 450)
82
83
  this.newActionTimer(() => {
@@ -91,8 +92,6 @@ export default class Layer extends Component {
91
92
 
92
93
  // 页面滚动条隐藏
93
94
  pageBodyScrollBarHide() {
94
- this.bodyStyleOrgOverflow = document.body.style.overflow
95
- this.bodyStyleOrgPaddingRight = document.body.style.paddingRight
96
95
  document.body.style.overflow = 'hidden'
97
96
 
98
97
  const bpr = parseInt(window.getComputedStyle(document.body, null).getPropertyValue('padding-right'), 10)
@@ -101,8 +100,8 @@ export default class Layer extends Component {
101
100
 
102
101
  // 页面滚动条显示
103
102
  pageBodyScrollBarShow() {
104
- document.body.style.overflow = this.bodyStyleOrgOverflow
105
- document.body.style.paddingRight = this.bodyStyleOrgPaddingRight
103
+ document.body.style.overflow = Layer.BodyOrgOverflow
104
+ document.body.style.paddingRight = Layer.BodyOrgPaddingRight
106
105
  }
107
106
 
108
107
  // Timers
@@ -128,7 +127,6 @@ export default class Layer extends Component {
128
127
 
129
128
  /** 销毁 - 无动画 */
130
129
  disposeNow () {
131
- document.body.style.overflow = ''
132
130
  this.$el.remove()
133
131
  this.pageBodyScrollBarShow()
134
132
  // this.$el dispose
@@ -91,7 +91,7 @@ export default class ListLite extends Component {
91
91
  try {
92
92
  listData = await new Api(this.ctx).get(offset, this.pageSize, this.flatMode, this.paramsEditor)
93
93
  } catch (e: any) {
94
- this.onError(e.msg || String(e), offset)
94
+ this.onError(e.msg || String(e), offset, e.data)
95
95
  throw e
96
96
  } finally {
97
97
  hideLoading()
@@ -219,7 +219,7 @@ export default class ListLite extends Component {
219
219
  }
220
220
 
221
221
  /** 错误处理 */
222
- protected onError(msg: any, offset: number) {
222
+ protected onError(msg: any, offset: number, errData?: any) {
223
223
  msg = String(msg)
224
224
  console.error(msg)
225
225
 
@@ -237,9 +237,24 @@ export default class ListLite extends Component {
237
237
  $err.appendChild($retryBtn)
238
238
 
239
239
  const adminBtn = Utils.createElement('<span atk-only-admin-show> | <span style="cursor:pointer;">打开控制台</span></span>')
240
- adminBtn.onclick = () => (this.ctx.trigger('sidebar-show'))
241
- if (!this.ctx.user.data.isAdmin) adminBtn.classList.add('atk-hide')
242
240
  $err.appendChild(adminBtn)
241
+ if (!this.ctx.user.data.isAdmin) adminBtn.classList.add('atk-hide')
242
+
243
+ let sidebarView = ''
244
+
245
+ // 找不到站点错误,打开侧边栏并填入创建站点表单
246
+ if (errData?.err_no_site) {
247
+ const viewLoadParam = {
248
+ create_name: this.ctx.conf.site,
249
+ create_urls: `${window.location.protocol}//${window.location.host}`
250
+ }
251
+ // TODO 真的是飞鸽传书啊
252
+ sidebarView = `sites|${JSON.stringify(viewLoadParam)}`
253
+ }
254
+
255
+ adminBtn.onclick = () => (this.ctx.trigger('sidebar-show', {
256
+ view: sidebarView
257
+ }))
243
258
 
244
259
  Ui.setError(this.$el, $err)
245
260
  }
@@ -7,6 +7,7 @@ import SidebarHTML from './html/sidebar-layer.html?raw'
7
7
  import * as Ui from '@/lib/ui'
8
8
  import Layer from './layer'
9
9
  import Api from '../api'
10
+ import { SidebarShowPayload } from '~/types/event'
10
11
 
11
12
  export default class SidebarLayer extends Component {
12
13
  public layer?: Layer
@@ -28,7 +29,7 @@ export default class SidebarLayer extends Component {
28
29
  }
29
30
 
30
31
  // event
31
- this.ctx.on('sidebar-show', () => (this.show()))
32
+ this.ctx.on('sidebar-show', (conf) => (this.show(conf || {})))
32
33
  this.ctx.on('sidebar-hide', () => (this.hide()))
33
34
  this.ctx.on('user-changed', () => { this.firstShow = true })
34
35
  }
@@ -36,7 +37,7 @@ export default class SidebarLayer extends Component {
36
37
  private firstShow = true
37
38
 
38
39
  /** 显示 */
39
- public async show() {
40
+ public async show(conf: SidebarShowPayload) {
40
41
  this.$el.style.transform = '' // 动画清除,防止二次打开失效
41
42
 
42
43
  // 获取 Layer
@@ -59,54 +60,60 @@ export default class SidebarLayer extends Component {
59
60
  this.$el.style.transform = 'translate(0, 0)'
60
61
  }, 20)
61
62
 
62
- // 第一次加载
63
- if (this.firstShow) {
64
- // 管理员身份验证
65
- if (this.ctx.user.data.isAdmin) {
66
- const resp = await new Api(this.ctx).loginStatus()
67
- if (!resp.is_login) {
68
- await (new Promise<any>((resolve, reject) => {
69
- this.ctx.trigger('checker-admin', {
70
- onSuccess: () => {
71
- resolve(null)
72
- setTimeout(() => {
73
- this.show()
74
- }, 500)
75
- },
76
- onCancel: () => {
77
- this.layer?.hide()
78
- }
79
- })
80
- }))
81
- }
63
+ // 管理员身份验证 (若身份失效,弹出验证窗口)
64
+ ;(async () => {
65
+ const resp = await new Api(this.ctx).loginStatus()
66
+ if (resp.is_admin && !resp.is_login) {
67
+ this.layer?.hide()
68
+ this.firstShow = true
69
+
70
+ this.ctx.trigger('checker-admin', {
71
+ onSuccess: () => {
72
+ setTimeout(() => {
73
+ this.show(conf)
74
+ }, 500)
75
+ },
76
+ onCancel: () => {}
77
+ })
82
78
  }
79
+ })()
80
+
81
+ // 清空 unread
82
+ this.ctx.trigger('unread-update', { notifies: [] })
83
83
 
84
+ // 第一次加载
85
+ if (this.firstShow) {
84
86
  this.$iframeWrap.innerHTML = ''
85
87
  this.$iframe = Utils.createElement<HTMLIFrameElement>('<iframe></iframe>')
86
88
 
89
+ // 准备 Iframe 参数
87
90
  const baseURL = (import.meta.env.MODE === 'development') ? 'http://localhost:23367/'
88
- : Utils.getURLBasedOnApi(this.ctx, 'sidebar/')
89
- const userData = encodeURIComponent(JSON.stringify(this.ctx.user.data))
90
-
91
- const location = window.location
92
- this.iframeLoad(`${baseURL}`
93
- + `?pageKey=${encodeURIComponent(this.conf.pageKey)}`
94
- + `&site=${encodeURIComponent(this.conf.site || '')}`
95
- + `&user=${userData}`
96
- + `&time=${+new Date()}`
97
- + `&referer=${encodeURIComponent(`${location.protocol}//${location.host}${location.pathname}`)}`
98
- + `${((this.conf.darkMode) ? `&darkMode=1` : ``)}`)
91
+ : Utils.getURLBasedOnApi(this.ctx, '/sidebar/')
92
+
93
+ const query: any = {
94
+ pageKey: this.conf.pageKey,
95
+ site: this.conf.site || '',
96
+ user: JSON.stringify(this.ctx.user.data),
97
+ time: +new Date()
98
+ }
99
+
100
+ if (conf.view) query.view = conf.view
101
+ if (this.conf.darkMode) query.darkMode = '1'
102
+
103
+ const urlParams = new URLSearchParams(query);
104
+ this.iframeLoad(`${baseURL}?${urlParams.toString()}`)
99
105
 
100
106
  this.$iframeWrap.append(this.$iframe)
101
107
  this.firstShow = false
102
108
  } else {
103
109
  // 暗黑模式
104
- if (this.conf.darkMode && !this.$iframe!.src.match(/darkMode=1$/)) {
110
+ const isIframeSrcDarkMode = this.$iframe!.src.includes('darkMode=1')
111
+
112
+ if (this.conf.darkMode && !isIframeSrcDarkMode)
105
113
  this.iframeLoad(`${this.$iframe!.src}&darkMode=1`)
106
- }
107
- if (!this.conf.darkMode && this.$iframe!.src.match(/darkMode=1$/)) {
108
- this.iframeLoad(this.$iframe!.src.replace(/&darkMode=1$/, ''))
109
- }
114
+
115
+ if (!this.conf.darkMode && isIframeSrcDarkMode)
116
+ this.iframeLoad(this.$iframe!.src.replace('&darkMode=1', ''))
110
117
  }
111
118
  }
112
119
 
package/src/context.ts CHANGED
@@ -42,4 +42,5 @@ export default class Context {
42
42
  }
43
43
 
44
44
  public markedInstance!: typeof libMarked
45
+ public markedReplacers: ((raw: string) => string)[] = []
45
46
  }
@@ -15,7 +15,7 @@ const CaptchaChecker: Checker = {
15
15
  const $iframe = Utils.createElement<HTMLIFrameElement>(`<iframe class="atk-fade-in"></iframe>`)
16
16
  $iframe.style.display = 'none'
17
17
  Ui.showLoading($iframeWrap, { transparentBg: true })
18
- $iframe.src = `${that.ctx.conf.server}/captcha/get`
18
+ $iframe.src = `${that.ctx.conf.server}/api/captcha/get?t=${+new Date()}`
19
19
  $iframe.onload = () => {
20
20
  $iframe.style.display = ''
21
21
  Ui.hideLoading($iframeWrap)