Compare commits
161
Commits
e506309434
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de0e5cd481 | ||
|
|
a120f61e13 | ||
|
|
0d51beeaad | ||
|
|
573040381d | ||
|
|
7a0445fc4a | ||
|
|
0bc72616e5 | ||
|
|
064f4e0955 | ||
|
|
4b5098a753 | ||
|
|
05f16d5fd7 | ||
|
|
af8dccdc45 | ||
|
|
dcb80cff6e | ||
|
|
2e2558e5ce | ||
|
|
817ac9c647 | ||
|
|
e94ecdd7b1 | ||
|
|
659b77719c | ||
|
|
7f556892ad | ||
|
|
cb76b95b01 | ||
|
|
319ec2f47f | ||
|
|
4b18f7ef2c | ||
|
|
1461c9fe27 | ||
|
|
5641c40958 | ||
|
|
0bee2c605e | ||
|
|
c472045ce4 | ||
|
|
6dc9265260 | ||
|
|
008e63524d | ||
|
|
cff3083a91 | ||
|
|
4d69aaee50 | ||
|
|
ee39024549 | ||
|
|
5810693e01 | ||
|
|
0722c4f00e | ||
|
|
4f2edebab6 | ||
|
|
ce6fbb4e39 | ||
|
|
cf39e72126 | ||
|
|
740bd48f42 | ||
|
|
18e579a8c6 | ||
|
|
cdab7a5e5f | ||
|
|
bd01199930 | ||
|
|
7ed739615e | ||
|
|
60e4d67772 | ||
|
|
7287cfeaec | ||
|
|
f03d72c880 | ||
|
|
6e90a96a15 | ||
|
|
b368678662 | ||
|
|
40b9a3f36b | ||
|
|
6fd80be243 | ||
|
|
855e85e7db | ||
|
|
7e7bf6db23 | ||
|
|
6f815678ec | ||
|
|
8ff74f56ee | ||
|
|
a3b708f9d4 | ||
|
|
9cb5e7bea3 | ||
|
|
16b57dcf1c | ||
|
|
1875470ac7 | ||
|
|
c955fd35f2 | ||
|
|
36b3ba6f45 | ||
|
|
039fa6d863 | ||
|
|
26ce9a5904 | ||
|
|
31e6ba00af | ||
|
|
89ae462610 | ||
|
|
afd08885fc | ||
|
|
536ac67f3c | ||
|
|
7b83893cf6 | ||
|
|
83c164a427 | ||
|
|
5fd8c6fc31 | ||
|
|
b733e41993 | ||
|
|
88669f6989 | ||
|
|
80373746f8 | ||
|
|
d2034f5031 | ||
|
|
ad2290cbb7 | ||
|
|
c870f08395 | ||
|
|
80fb4068d1 | ||
|
|
01cd734989 | ||
|
|
f44180d06c | ||
|
|
c7d18d426c | ||
|
|
0ec405f566 | ||
|
|
dd320c99c1 | ||
|
|
fc75d7b810 | ||
|
|
b3012b3133 | ||
|
|
ab6c2339a0 | ||
|
|
3eb9cc739c | ||
|
|
e9a1a75259 | ||
|
|
d69e1d33c3 | ||
|
|
8023e2e472 | ||
|
|
918ca371be | ||
|
|
9e3267fef4 | ||
|
|
6081e8dcc9 | ||
|
|
c801ff5047 | ||
|
|
8460132feb | ||
|
|
732f615a09 | ||
|
|
738abe5102 | ||
|
|
915311dcb6 | ||
|
|
c200427358 | ||
|
|
7b5dcf16a1 | ||
|
|
2db2414270 | ||
|
|
365b2eafe5 | ||
|
|
ea88499996 | ||
|
|
f4ff3e16d4 | ||
|
|
c67d5ae4bc | ||
|
|
00a087b1a4 | ||
|
|
a0f59826fe | ||
|
|
e7e281f10c | ||
|
|
93579cbc9f | ||
|
|
4ea02f25ac | ||
|
|
1a9a2762f3 | ||
|
|
b447c41f13 | ||
|
|
8e1fd0a30f | ||
|
|
cf6dc24193 | ||
|
|
c8fdd81d6a | ||
|
|
27868b6131 | ||
|
|
ef718042d2 | ||
|
|
194280af72 | ||
|
|
50fac36169 | ||
|
|
2595bb02db | ||
|
|
fe57022550 | ||
|
|
962ab5c290 | ||
|
|
6dbe1fe4c6 | ||
|
|
12f60897d9 | ||
|
|
b75bf197a3 | ||
|
|
613b8a57a3 | ||
|
|
af72364078 | ||
|
|
224f86ec08 | ||
|
|
fc9bf27560 | ||
|
|
d8d0b5f290 | ||
|
|
ce429e1caf | ||
|
|
248912f610 | ||
|
|
f646fa8573 | ||
|
|
0cf4639d95 | ||
|
|
0a52bf36f9 | ||
|
|
346146b4fa | ||
|
|
f00c3faeaa | ||
|
|
59e9bbf8a6 | ||
|
|
1af51ab172 | ||
|
|
7b55cfb190 | ||
|
|
476e2f961d | ||
|
|
979639342a | ||
|
|
356f7fe5bd | ||
|
|
6037ca184b | ||
|
|
8ef2f7e49f | ||
|
|
5091da6a60 | ||
|
|
d0a3972ca7 | ||
|
|
2ae2b2fd07 | ||
|
|
90c1f7a62b | ||
|
|
8fcb136d55 | ||
|
|
d6e3b377b4 | ||
|
|
2b3e325d63 | ||
|
|
4caf81c9e2 | ||
|
|
88661e1574 | ||
|
|
67ec3c8aeb | ||
|
|
59901b9e10 | ||
|
|
5a17b399a0 | ||
|
|
4c6b02a6b9 | ||
|
|
56bbb63afd | ||
|
|
00817766a6 | ||
|
|
16d67c8a87 | ||
|
|
45dc909e4c | ||
|
|
20fde1d652 | ||
|
|
38bbd917a9 | ||
|
|
f61142ee9a | ||
|
|
cf4554e7c8 | ||
|
|
271e7f0518 | ||
|
|
b5f37d6d46 |
No files matched your search
@@ -5,11 +5,4 @@ export const extensionsApiMethods = {
|
||||
|
||||
testIpGeo(ip) { return this.request('/extensions/ip-test', { method: 'POST', data: { ip } }) },
|
||||
testContentAudit(text) { return this.request('/extensions/audit-test', { method: 'POST', data: { text } }) },
|
||||
|
||||
/* AI 辅助 */
|
||||
getAiStatus() { return this.request('/ai/status') },
|
||||
updateAiSettings(data) { return this.request('/ai/settings', { method: 'POST', data }) },
|
||||
generateSeo(data) { return this.request('/ai/seo-generate', { method: 'POST', data }) },
|
||||
suggestSummary(data) { return this.request('/ai/summary-suggest', { method: 'POST', data }) },
|
||||
suggestTags(data) { return this.request('/ai/tags-suggest', { method: 'POST', data }) },
|
||||
}
|
||||
@@ -5,6 +5,7 @@ export const usersApiMethods = {
|
||||
|
||||
getAuthSettings() { return this.request('/settings/auth') },
|
||||
saveAuthSettings(data) { return this.request('/settings/auth', { method: 'POST', body: JSON.stringify(data) }) },
|
||||
testSms(phone) { return this.request('/sms/test', { method: 'POST', body: JSON.stringify({ phone }) }) },
|
||||
|
||||
getNotificationSettings() { return this.request('/settings/notifications') },
|
||||
saveNotificationSettings(data) { return this.request('/settings/notifications', { method: 'POST', body: JSON.stringify(data) }) },
|
||||
@@ -17,14 +18,6 @@ export const usersApiMethods = {
|
||||
deleteNotification(id) { return this.request(`/notifications/${id}`, { method: 'DELETE' }) },
|
||||
runNotificationCleanup() { return this.request('/notifications/cleanup', { method: 'POST', body: JSON.stringify({}) }) },
|
||||
|
||||
getIdentitySettings() { return this.request('/identity/settings') },
|
||||
saveIdentitySettings(data) {
|
||||
return this.request('/identity/settings', { method: 'POST', body: JSON.stringify(data) })
|
||||
},
|
||||
testIdentitySettings(data = {}) {
|
||||
return this.request('/identity/settings/test', { method: 'POST', body: JSON.stringify(data) })
|
||||
},
|
||||
|
||||
getCreditSettings() { return this.request('/assets/settings') },
|
||||
saveCreditSettings(data) { return this.request('/assets/settings', { method: 'POST', body: JSON.stringify(data) }) },
|
||||
listCreditUsers(params = {}) {
|
||||
|
||||
@@ -476,14 +476,43 @@
|
||||
|
||||
<!-- Fieldset (single nested object) -->
|
||||
<div v-else-if="schema.type === 'fieldset' || schema.type === 'accordion'" class="aksrt-field__fieldset">
|
||||
<AksField
|
||||
v-for="field in visibleNestedFields"
|
||||
:key="field.key"
|
||||
:schema="field"
|
||||
:model-value="fieldsetValue(field)"
|
||||
:form-data="formData"
|
||||
@update:model-value="updateFieldsetValue(field.key, $event)"
|
||||
/>
|
||||
<template v-if="fieldsetSections.length">
|
||||
<div
|
||||
v-for="(section, sIndex) in fieldsetSections"
|
||||
:key="section.title"
|
||||
class="aksrt-field__accordion-section"
|
||||
:class="{ 'is-collapsed': !isFieldsetSectionOpen(sIndex) }"
|
||||
>
|
||||
<div class="aksrt-field__accordion-section-head" @click="toggleFieldsetSection(sIndex)">
|
||||
<span class="aksrt-field__accordion-section-title">{{ section.title }}</span>
|
||||
<FaIcon
|
||||
:icon="isFieldsetSectionOpen(sIndex) ? 'fa fas fa-chevron-down' : 'fa fas fa-chevron-right'"
|
||||
:size="12"
|
||||
class="aksrt-field__accordion-section-toggle"
|
||||
/>
|
||||
</div>
|
||||
<div v-show="isFieldsetSectionOpen(sIndex)" class="aksrt-field__accordion-section-body">
|
||||
<AksField
|
||||
v-for="field in sectionFields(section)"
|
||||
:key="field.key"
|
||||
:schema="field"
|
||||
:model-value="fieldsetValue(field)"
|
||||
:form-data="formData"
|
||||
@update:model-value="updateFieldsetValue(field.key, $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="!fieldsetSections.length">
|
||||
<AksField
|
||||
v-for="field in visibleNestedFields"
|
||||
:key="field.key"
|
||||
:schema="field"
|
||||
:model-value="fieldsetValue(field)"
|
||||
:form-data="formData"
|
||||
@update:model-value="updateFieldsetValue(field.key, $event)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-else-if="schema.type === 'link'" class="aksrt-field__link">
|
||||
@@ -507,38 +536,62 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Repeater (array of objects) — inline expand (default) -->
|
||||
<!-- Repeater (array of objects) — inline expand (default, draggable) -->
|
||||
<div v-else-if="(schema.type === 'repeater' || schema.type === 'group') && schema.ui !== 'list-modal'" class="aksrt-field__repeater">
|
||||
<div
|
||||
v-for="(item, index) in repeaterItems"
|
||||
:key="item._id || index"
|
||||
class="aksrt-field__repeater-item"
|
||||
<draggable
|
||||
v-model="repeaterDragList"
|
||||
:item-key="repeaterDragKey"
|
||||
handle=".aksrt-field__repeater-handle"
|
||||
ghost-class="aksrt-field__repeater-ghost"
|
||||
animation="180"
|
||||
>
|
||||
<div class="aksrt-field__repeater-header">
|
||||
<span class="aksrt-field__repeater-index">#{{ index + 1 }}</span>
|
||||
<n-button
|
||||
size="tiny"
|
||||
type="error"
|
||||
text
|
||||
:disabled="schema.disabled || reachedRepeaterMinimum"
|
||||
@click="removeRepeaterItem(index)"
|
||||
>
|
||||
<FaIcon icon="fa fas fa-trash-alt" :size="12" />
|
||||
</n-button>
|
||||
</div>
|
||||
<div class="aksrt-field__repeater-fields">
|
||||
<template v-for="field in (schema.fields || [])" :key="field.key">
|
||||
<AksField
|
||||
v-if="matchDependency(field.dependsOn, item)"
|
||||
:schema="field"
|
||||
:model-value="item[field.key]"
|
||||
:form-data="formData"
|
||||
:disabled="schema.disabled"
|
||||
@update:model-value="updateRepeaterItem(index, field.key, $event)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<template #item="{ element, index }">
|
||||
<div class="aksrt-field__repeater-item" :class="{ 'is-collapsible': repeaterCollapsible, 'is-collapsed': repeaterCollapsible && !isRepeaterItemOpen(index) }">
|
||||
<div
|
||||
class="aksrt-field__repeater-header"
|
||||
:class="{ 'is-clickable': repeaterCollapsible }"
|
||||
@click="repeaterCollapsible ? toggleRepeaterItem(index) : undefined"
|
||||
>
|
||||
<div class="aksrt-field__repeater-title">
|
||||
<i class="fa fas fa-grip-vertical aksrt-field__repeater-handle" aria-hidden="true" @click.stop></i>
|
||||
<span class="aksrt-field__repeater-index">#{{ index + 1 }}</span>
|
||||
<span v-if="repeaterCollapsible && repeaterItemSummary(element)" class="aksrt-field__repeater-summary">
|
||||
{{ repeaterItemSummary(element) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="aksrt-field__repeater-header-actions" @click.stop>
|
||||
<FaIcon
|
||||
v-if="repeaterCollapsible"
|
||||
:icon="isRepeaterItemOpen(index) ? 'fa fas fa-chevron-down' : 'fa fas fa-chevron-right'"
|
||||
:size="12"
|
||||
class="aksrt-field__repeater-toggle"
|
||||
/>
|
||||
<n-button
|
||||
size="tiny"
|
||||
type="error"
|
||||
text
|
||||
:disabled="schema.disabled || reachedRepeaterMinimum"
|
||||
@click="removeRepeaterItem(index)"
|
||||
>
|
||||
<FaIcon icon="fa fas fa-trash-alt" :size="12" />
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="!repeaterCollapsible || isRepeaterItemOpen(index)" class="aksrt-field__repeater-fields">
|
||||
<template v-for="field in (schema.fields || [])" :key="field.key">
|
||||
<AksField
|
||||
v-if="matchDependency(field.dependsOn, element)"
|
||||
:schema="field"
|
||||
:model-value="element[field.key]"
|
||||
:form-data="formData"
|
||||
:disabled="schema.disabled"
|
||||
@update:model-value="updateRepeaterItem(index, field.key, $event)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
<n-button
|
||||
size="small"
|
||||
dashed
|
||||
@@ -688,6 +741,8 @@ import FaIconPicker from './FaIconPicker.vue'
|
||||
import AksMediaField from './AksMediaField.vue'
|
||||
import AksGalleryField from './AksGalleryField.vue'
|
||||
import AksCodeEditor from './AksCodeEditor.vue'
|
||||
import { buildFieldsetSections } from './fieldset-sections.js'
|
||||
import draggable from 'vuedraggable'
|
||||
import api from '@/api'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -720,6 +775,85 @@ const repeaterItems = computed(() => Array.isArray(props.modelValue) ? props.mod
|
||||
const reachedRepeaterMinimum = computed(() => repeaterLength.value <= Number(props.schema.min || 0))
|
||||
const reachedRepeaterMaximum = computed(() => Number(props.schema.max || 0) > 0 && repeaterLength.value >= Number(props.schema.max))
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* 手风琴模式:schema.collapsible 开启后,每一项默认收起,仅显示标题行,
|
||||
* 点击标题行展开/收起。defaultCollapsed 为 false 时改为默认全部展开。
|
||||
* ------------------------------------------------------------------ */
|
||||
const repeaterCollapsible = computed(() => !!props.schema.collapsible)
|
||||
const repeaterDefaultOpen = computed(() => props.schema.defaultCollapsed === false)
|
||||
// 只记录「与默认状态相反」的下标,新增/删除项时其余项的开合状态不受影响。
|
||||
const toggledRepeaterItems = ref(new Set())
|
||||
|
||||
function isRepeaterItemOpen(index) {
|
||||
if (!repeaterCollapsible.value) return true
|
||||
return repeaterDefaultOpen.value !== toggledRepeaterItems.value.has(index)
|
||||
}
|
||||
|
||||
function toggleRepeaterItem(index) {
|
||||
const next = new Set(toggledRepeaterItems.value)
|
||||
if (next.has(index)) {
|
||||
next.delete(index)
|
||||
} else {
|
||||
next.add(index)
|
||||
}
|
||||
toggledRepeaterItems.value = next
|
||||
}
|
||||
|
||||
/** 收起态标题行上的一行摘要,取自 listPreview 配置。 */
|
||||
function repeaterItemSummary(item) {
|
||||
if (!repeaterCollapsible.value || !item) return ''
|
||||
const cfg = getRepeaterPreviewConfig()
|
||||
const titleKey = cfg.titleKey || 'title'
|
||||
const title = String(item[titleKey] ?? '').trim()
|
||||
const prefix = String(cfg.prefix || '')
|
||||
if (title === '') return prefix
|
||||
const desc = repeaterItemDuration(item)
|
||||
return desc ? `${prefix}${title} · ${desc}` : `${prefix}${title}`
|
||||
}
|
||||
|
||||
/** time/unit 形态的时长摘要:0 视为永久,其余按 day/month 补单位。 */
|
||||
function repeaterItemDuration(item) {
|
||||
const cfg = getRepeaterPreviewConfig()
|
||||
const descKey = cfg.descKey
|
||||
if (!descKey) return ''
|
||||
const raw = item[descKey]
|
||||
if (raw === '' || raw == null) return ''
|
||||
const amount = Number(raw)
|
||||
if (!Number.isFinite(amount)) return String(raw)
|
||||
if (amount <= 0) return '永久'
|
||||
const unitKey = cfg.descUnitKey
|
||||
if (!unitKey) return String(raw)
|
||||
const unit = String(item[unitKey] || '')
|
||||
if (unit === 'month') return `${amount} 个月`
|
||||
if (unit === 'day') return `${amount} 天`
|
||||
return String(raw)
|
||||
}
|
||||
|
||||
/** 拖拽视图:vuedraggable 以 modelValue 模式工作(内部复制后回传 update:modelValue)。 */
|
||||
const repeaterDragList = computed({
|
||||
get: () => (Array.isArray(props.modelValue) ? props.modelValue : []),
|
||||
set: (value) => emit('update:modelValue', Array.isArray(value) ? value : []),
|
||||
})
|
||||
|
||||
/**
|
||||
* 行 key:优先用行自身 _id;服务端历史数据没有 _id 时按对象引用派生稳定 key,
|
||||
* 避免每次输入都重算 key 导致子字段重挂载、输入焦点丢失。
|
||||
*/
|
||||
const dragRowKeys = new WeakMap()
|
||||
let dragRowKeySeed = 0
|
||||
function repeaterDragKey(item) {
|
||||
if (item && typeof item === 'object') {
|
||||
if (item._id) return item._id
|
||||
let key = dragRowKeys.get(item)
|
||||
if (!key) {
|
||||
key = `rpt_drag_${++dragRowKeySeed}`
|
||||
dragRowKeys.set(item, key)
|
||||
}
|
||||
return key
|
||||
}
|
||||
return String(item)
|
||||
}
|
||||
|
||||
function fieldsetValue(field) {
|
||||
const current = props.modelValue && typeof props.modelValue === 'object' && !Array.isArray(props.modelValue)
|
||||
? props.modelValue
|
||||
@@ -727,6 +861,35 @@ function fieldsetValue(field) {
|
||||
return Object.prototype.hasOwnProperty.call(current, field.key) ? current[field.key] : field.default
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* accordion/fieldset 的分块手风琴:schema.sections 声明每块的标题与字段
|
||||
* key,默认全部收起,点击块标题展开。未声明 sections 时保持平铺渲染。
|
||||
* ------------------------------------------------------------------ */
|
||||
const fieldsetSections = computed(() => {
|
||||
return buildFieldsetSections(props.schema)
|
||||
})
|
||||
|
||||
const openFieldsetSections = ref(new Set())
|
||||
|
||||
function isFieldsetSectionOpen(index) {
|
||||
return openFieldsetSections.value.has(index)
|
||||
}
|
||||
|
||||
/** 分块内字段同样遵循 dependsOn,数据源是本 fieldset 的值对象。 */
|
||||
function sectionFields(section) {
|
||||
return (section.fields || []).filter((field) => matchDependency(field.dependsOn, props.modelValue))
|
||||
}
|
||||
|
||||
function toggleFieldsetSection(index) {
|
||||
const next = new Set(openFieldsetSections.value)
|
||||
if (next.has(index)) {
|
||||
next.delete(index)
|
||||
} else {
|
||||
next.add(index)
|
||||
}
|
||||
openFieldsetSections.value = next
|
||||
}
|
||||
|
||||
function updateFieldsetValue(key, value) {
|
||||
const current = props.modelValue && typeof props.modelValue === 'object' && !Array.isArray(props.modelValue)
|
||||
? props.modelValue
|
||||
@@ -1018,6 +1181,8 @@ const repeaterDraft = ref({})
|
||||
function createRepeaterItem() {
|
||||
const newItem = { _id: `rpt_${Date.now()}_${++repeaterIdCounter}` }
|
||||
for (const field of (props.schema.fields || [])) {
|
||||
// UI-only chrome rows (notice / alert) are presentation only.
|
||||
if (field.ui_only) continue
|
||||
if (field.default !== undefined) {
|
||||
newItem[field.key] = field.default
|
||||
} else if (field.type === 'switch') {
|
||||
@@ -1047,7 +1212,16 @@ function removeRepeaterItem(index) {
|
||||
|
||||
function updateRepeaterItem(index, fieldKey, value) {
|
||||
const current = Array.isArray(props.modelValue) ? [...props.modelValue] : []
|
||||
current[index] = { ...current[index], [fieldKey]: value }
|
||||
const row = current[index]
|
||||
if (row && typeof row === 'object') {
|
||||
// 必须原地写,不能展开成新对象:drag key 存在以「对象身份」为键的 WeakMap
|
||||
// 里,而行数据是 reactive 的——换一个新对象后,渲染时读到的是它的一个新
|
||||
// reactive 代理,WeakMap 认不出它便生成新 key,整行重挂载、输入框失焦,
|
||||
// 表现为打一个字就退出编辑。经代理原地写既保持身份,也照样触发响应式。
|
||||
row[fieldKey] = value
|
||||
} else {
|
||||
current[index] = { [fieldKey]: value }
|
||||
}
|
||||
emit('update:modelValue', current)
|
||||
}
|
||||
|
||||
@@ -1149,6 +1323,15 @@ function confirmRepeaterModal() {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 结构化分组(会员商品/续费商品/积分兑换/升级价格)与相邻字段之间留出更明确的间隔,
|
||||
* 避免两个分组上下贴在一起,和参考主题的留白一致。 */
|
||||
.aksrt-field--group,
|
||||
.aksrt-field--repeater,
|
||||
.aksrt-field--accordion,
|
||||
.aksrt-field--fieldset {
|
||||
margin-bottom: 26px;
|
||||
}
|
||||
|
||||
.aksrt-field__input-wrap {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
@@ -1414,6 +1597,48 @@ function confirmRepeaterModal() {
|
||||
background: var(--aksrt-color-canvas);
|
||||
}
|
||||
|
||||
.aksrt-field__accordion-section + .aksrt-field__accordion-section {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.aksrt-field__accordion-section-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--aksrt-color-border);
|
||||
border-radius: 6px;
|
||||
background: var(--aksrt-color-surface);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--aksrt-color-text);
|
||||
transition: border-color 0.15s, color 0.15s;
|
||||
}
|
||||
|
||||
.aksrt-field__accordion-section-head:hover {
|
||||
border-color: var(--aksrt-color-primary);
|
||||
color: var(--aksrt-color-primary);
|
||||
}
|
||||
|
||||
.aksrt-field__accordion-section-title {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.aksrt-field__accordion-section-toggle {
|
||||
flex-shrink: 0;
|
||||
color: var(--aksrt-color-text-secondary);
|
||||
}
|
||||
|
||||
.aksrt-field__accordion-section-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 10px 2px 2px;
|
||||
}
|
||||
|
||||
.aksrt-field__repeater {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1431,9 +1656,68 @@ function confirmRepeaterModal() {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.aksrt-field__repeater-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.aksrt-field__repeater-header.is-clickable {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
border-radius: 6px;
|
||||
margin: -4px -4px 0;
|
||||
padding: 4px 4px 0;
|
||||
}
|
||||
|
||||
.aksrt-field__repeater-header.is-clickable:hover .aksrt-field__repeater-index {
|
||||
color: var(--aksrt-color-primary-hover);
|
||||
}
|
||||
|
||||
.aksrt-field__repeater-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.aksrt-field__repeater-toggle {
|
||||
color: var(--aksrt-color-text-secondary);
|
||||
transition: transform 0.15s;
|
||||
}
|
||||
|
||||
.aksrt-field__repeater-summary {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
color: var(--aksrt-color-text-secondary);
|
||||
}
|
||||
|
||||
.aksrt-field__repeater-item.is-collapsible {
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.aksrt-field__repeater-item.is-collapsed .aksrt-field__repeater-header {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.aksrt-field__repeater-handle {
|
||||
cursor: grab;
|
||||
color: var(--aksrt-color-text-tertiary);
|
||||
}
|
||||
|
||||
.aksrt-field__repeater-ghost {
|
||||
opacity: 0.45;
|
||||
border-color: var(--aksrt-color-primary);
|
||||
}
|
||||
|
||||
.aksrt-field__repeater-index {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
|
||||
@@ -64,9 +64,9 @@
|
||||
<n-tabs type="line" animated>
|
||||
<n-tab-pane
|
||||
v-for="tab in field.tabs"
|
||||
:key="tab.title || tab.key"
|
||||
:name="tab.title || tab.key"
|
||||
:tab="tab.title || tab.key"
|
||||
:key="tab.key || tab.title"
|
||||
:name="tabName(tab)"
|
||||
:tab="tabName(tab)"
|
||||
>
|
||||
<template v-for="sub in (tab.fields || [])" :key="sub.key">
|
||||
<AksField
|
||||
@@ -231,6 +231,18 @@ function isFieldVisible(field) {
|
||||
return matchDependsOn(field.dependsOn)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tab label. `titleKey` binds the label to a live form value (e.g. the VIP level
|
||||
* display name) so renaming a level renames its tab; `titleFallback` is used
|
||||
* while that value is still empty.
|
||||
*/
|
||||
function tabName(tab) {
|
||||
if (!tab) return ''
|
||||
const dynamic = tab.titleKey ? props.formData?.[tab.titleKey] : ''
|
||||
const label = typeof dynamic === 'string' ? dynamic.trim() : ''
|
||||
return label || tab.titleFallback || tab.title || tab.key || ''
|
||||
}
|
||||
|
||||
/** Whole group can be hidden (e.g. optional advanced blocks). */
|
||||
function isGroupVisible(group) {
|
||||
return matchDependsOn(group.dependsOn)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 把 accordion / fieldset 的 `schema.sections` 声明展开成「块标题 + 字段定义」。
|
||||
*
|
||||
* 参考主题把「升级价格」这类复合配置拆成若干可折叠块,每块用 keys 声明自己
|
||||
* 包含哪些字段。这里是纯函数,方便在 Node 契约测试里直接用真实 schema 校验,
|
||||
* 避免渲染层的一个小疏忽让整块手风琴静默退化成平铺。
|
||||
*/
|
||||
export function buildFieldsetSections(schema) {
|
||||
const declared = schema && Array.isArray(schema.sections) ? schema.sections : []
|
||||
if (!declared.length) return []
|
||||
|
||||
const byKey = {}
|
||||
const nested = Array.isArray(schema.fields) ? schema.fields : []
|
||||
for (const field of nested) {
|
||||
if (field && field.key) byKey[field.key] = field
|
||||
}
|
||||
|
||||
return declared.map((section) => ({
|
||||
title: String((section && section.title) || ''),
|
||||
fields: (Array.isArray(section && section.keys) ? section.keys : [])
|
||||
.map((key) => byKey[key])
|
||||
.filter(Boolean),
|
||||
}))
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { renderFaIcon, schemaPage, customPage } from './helpers'
|
||||
import { renderFaIcon, schemaPage } from './helpers'
|
||||
|
||||
export const extensionsSection = {
|
||||
key: 'extensions',
|
||||
@@ -6,6 +6,16 @@ export const extensionsSection = {
|
||||
icon: renderFaIcon('fa fas fa-puzzle-piece'),
|
||||
route: '/extensions',
|
||||
children: [
|
||||
schemaPage({
|
||||
key: 'extensions-system',
|
||||
name: 'ExtensionsSystem',
|
||||
label: '系统工具',
|
||||
route: '/extensions/system',
|
||||
title: '系统工具',
|
||||
desc: 'WordPress 优化、上传优化与编辑器显示的系统级开关。',
|
||||
canonicalDomain: 'extensions',
|
||||
section: 'system',
|
||||
}),
|
||||
schemaPage({
|
||||
key: 'extensions-ip',
|
||||
name: 'ExtensionsIp',
|
||||
@@ -32,19 +42,19 @@ export const extensionsSection = {
|
||||
label: '百度推送',
|
||||
route: '/extensions/push',
|
||||
title: '百度推送',
|
||||
desc: '配置搜索引擎主动推送、批量提交和状态追踪。',
|
||||
desc: '配置百度自动推送、收录通道与触发规则;推送结果与「重新提交」在文章/分类编辑页查看。',
|
||||
canonicalDomain: 'extensions',
|
||||
section: 'push',
|
||||
}),
|
||||
customPage({
|
||||
key: 'extensions-ai',
|
||||
name: 'ExtensionAI',
|
||||
label: 'AI 连接配置',
|
||||
route: '/extensions/ai',
|
||||
component: 'ExtensionAI',
|
||||
status: 'available',
|
||||
title: 'AI 连接配置',
|
||||
desc: '配置 AI 生成连接;SEO 在文章独立 SEO 框,摘要/标签在前台投稿页。',
|
||||
schemaPage({
|
||||
key: 'extensions-security',
|
||||
name: 'ExtensionsSecurity',
|
||||
label: '网站安全',
|
||||
route: '/extensions/security',
|
||||
title: '网站安全',
|
||||
desc: '防刷限制、验证码发送限制与站点安全加固。',
|
||||
canonicalDomain: 'extensions',
|
||||
section: 'security',
|
||||
}),
|
||||
],
|
||||
}
|
||||
@@ -16,7 +16,7 @@ const SECTION_DEFAULT_CHILDREN = {
|
||||
permissions: 'permissions-comment',
|
||||
users: 'users-auth',
|
||||
payment: 'payment-knowledge',
|
||||
extensions: 'extensions-ip',
|
||||
extensions: 'extensions-system',
|
||||
tools: 'tools-import-export',
|
||||
'theme-info': 'theme-info-dashboard',
|
||||
docs: 'docs-dashboard',
|
||||
|
||||
@@ -88,5 +88,16 @@ export const paymentSection = {
|
||||
canonicalDomain: 'payment',
|
||||
section: 'withdrawal',
|
||||
}),
|
||||
schemaPage({
|
||||
key: 'payment-after-sale',
|
||||
name: 'PaymentAfterSale',
|
||||
label: '售后策略',
|
||||
route: '/payment/after-sale',
|
||||
status: 'available',
|
||||
title: '售后策略',
|
||||
desc: '售后功能开关、申请时效、超时效提示与申请表单(预置原因、所需材料、图片上传)配置。',
|
||||
canonicalDomain: 'payment',
|
||||
section: 'after_sale',
|
||||
}),
|
||||
],
|
||||
}
|
||||
@@ -60,7 +60,7 @@ export const usersSection = {
|
||||
route: '/users/verify',
|
||||
component: 'UsersVerify',
|
||||
title: '身份认证',
|
||||
desc: '配置认证展示、申请类型与申请门槛;审核、驳回和取消认证统一在 WordPress 用户菜单处理。',
|
||||
desc: '配置通用认证申请规则;申请审核和认证资料管理位于 WordPress 用户菜单。',
|
||||
canonicalDomain: 'users',
|
||||
section: 'verify',
|
||||
}),
|
||||
@@ -108,16 +108,6 @@ export const usersSection = {
|
||||
canonicalDomain: 'users',
|
||||
section: 'sms',
|
||||
}),
|
||||
schemaPage({
|
||||
key: 'users-wechat',
|
||||
name: 'UsersWechat',
|
||||
label: '微信通知',
|
||||
route: '/users/wechat',
|
||||
title: '微信通知',
|
||||
desc: '配置微信模板消息、订阅/解绑和站外通知策略。',
|
||||
canonicalDomain: 'users',
|
||||
section: 'wechat',
|
||||
}),
|
||||
customPage({
|
||||
key: 'users-wechat-template',
|
||||
name: 'UsersWechatTemplate',
|
||||
|
||||
@@ -23,15 +23,13 @@
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
<n-card title="统一资产账本" class="aksrt-section-card">
|
||||
<n-card title="用户近期资产记录" class="aksrt-section-card">
|
||||
<n-space vertical :size="12">
|
||||
<n-space wrap>
|
||||
<n-select v-model:value="ledgerFilters.asset" :options="assetOptions" clearable placeholder="资产" style="width: 130px" />
|
||||
<n-input v-model:value="ledgerFilters.transaction_id" placeholder="交易号" clearable style="width: 130px" />
|
||||
<n-input v-model:value="ledgerFilters.operation_key" placeholder="幂等键" clearable style="width: 240px" />
|
||||
<n-input v-model:value="ledgerFilters.ref_type" placeholder="引用类型" clearable style="width: 130px" />
|
||||
<n-input v-model:value="ledgerFilters.ref_key" placeholder="引用键" clearable style="width: 180px" />
|
||||
<n-input v-model:value="ledgerFilters.actor_user_id" placeholder="操作者 ID" clearable style="width: 130px" />
|
||||
<n-input v-model:value="ledgerFilters.user_id" placeholder="用户 ID" clearable style="width: 130px" />
|
||||
<n-select v-model:value="ledgerFilters.asset" :options="assetOptions" placeholder="资产" style="width: 130px" />
|
||||
<n-input v-model:value="ledgerFilters.date_from" type="date" placeholder="开始日期" style="width: 160px" />
|
||||
<n-input v-model:value="ledgerFilters.date_to" type="date" placeholder="结束日期" style="width: 160px" />
|
||||
<n-button type="primary" :loading="ledgerLoading" @click="searchLedger">查询</n-button>
|
||||
<n-button @click="resetLedger">重置</n-button>
|
||||
</n-space>
|
||||
@@ -40,13 +38,13 @@
|
||||
:data="ledgerRows"
|
||||
:loading="ledgerLoading"
|
||||
:row-key="row => row.id"
|
||||
:scroll-x="1600"
|
||||
:scroll-x="1100"
|
||||
size="small"
|
||||
/>
|
||||
<n-pagination
|
||||
v-model:page="ledgerPage"
|
||||
:page-count="ledgerPageCount"
|
||||
:page-size="20"
|
||||
:page-size="10"
|
||||
@update:page="loadLedger"
|
||||
/>
|
||||
</n-space>
|
||||
@@ -169,40 +167,41 @@ const ledgerRows = ref([])
|
||||
const ledgerPage = ref(1)
|
||||
const ledgerPageCount = ref(1)
|
||||
const ledgerFilters = reactive({
|
||||
asset: null,
|
||||
transaction_id: '',
|
||||
operation_key: '',
|
||||
ref_type: '',
|
||||
ref_key: '',
|
||||
actor_user_id: '',
|
||||
user_id: '',
|
||||
asset: 'points',
|
||||
date_from: '',
|
||||
date_to: '',
|
||||
})
|
||||
const assetOptions = [
|
||||
{ label: '积分', value: 'points' },
|
||||
{ label: '余额', value: 'balance' },
|
||||
]
|
||||
const ledgerColumns = [
|
||||
{ title: '交易号', key: 'transaction_label', width: 100, fixed: 'left' },
|
||||
{ title: '幂等键', key: 'operation_key', width: 260, ellipsis: { tooltip: true } },
|
||||
{ title: '资产', key: 'assets_label', width: 100 },
|
||||
{ title: '原因', key: 'reason', width: 150, ellipsis: { tooltip: true } },
|
||||
{ title: '业务引用', key: 'reference_label', width: 200, ellipsis: { tooltip: true } },
|
||||
{ title: '操作者', key: 'actor_label', width: 100 },
|
||||
{ title: '增减 / 手续费', key: 'delta_label', width: 180, ellipsis: { tooltip: true } },
|
||||
{ title: '变动后余额', key: 'balance_label', width: 150, ellipsis: { tooltip: true } },
|
||||
{ title: '备注', key: 'remark', width: 180, ellipsis: { tooltip: true } },
|
||||
{ title: 'UTC 时间', key: 'created_at', width: 190 },
|
||||
{ title: '类型', key: 'reason', width: 150 },
|
||||
{ title: '订单号', key: 'ref_key', width: 200 },
|
||||
{ title: '变动 / 变动后', key: 'delta_label', width: 190 },
|
||||
{ title: '说明', key: 'remark', width: 240, ellipsis: { tooltip: true } },
|
||||
{ title: '时间', key: 'created_at', width: 170 },
|
||||
]
|
||||
|
||||
function formatAssetValue(asset, value) {
|
||||
if (asset === 'balance') return typeof value === 'string' && /^[+-]?\d+\.\d{2}$/.test(value) ? value : '0.00'
|
||||
const raw = String(value ?? '0')
|
||||
return /^-?\d+$/.test(raw) ? raw : '0'
|
||||
const raw = Number(value ?? 0)
|
||||
if (!Number.isFinite(raw)) return asset === 'balance' ? '0.00' : '0'
|
||||
if (asset !== 'balance') return String(raw)
|
||||
const negative = raw < 0
|
||||
const digits = String(Math.abs(Math.trunc(raw))).padStart(3, '0')
|
||||
const whole = digits.slice(0, -2).replace(/^0+(?=\d)/, '')
|
||||
return (negative ? '-' : '') + whole + '.' + digits.slice(-2)
|
||||
}
|
||||
|
||||
async function loadLedger() {
|
||||
ledgerLoading.value = true
|
||||
try {
|
||||
const params = { page: ledgerPage.value, per_page: 20 }
|
||||
if (!/^\d+$/.test(String(ledgerFilters.user_id).trim())) {
|
||||
message.warning('请输入用户 ID 后查询该用户近期记录')
|
||||
return
|
||||
}
|
||||
const params = { page: ledgerPage.value, per_page: 10 }
|
||||
Object.entries(ledgerFilters).forEach(([key, value]) => {
|
||||
if (value !== null && String(value).trim() !== '') params[key] = value
|
||||
})
|
||||
@@ -210,23 +209,16 @@ async function loadLedger() {
|
||||
const data = response?.data || response || {}
|
||||
ledgerRows.value = (data.items || []).map((row) => {
|
||||
const entries = Array.isArray(row.entries) ? row.entries : []
|
||||
const entry = entries[0] || {}
|
||||
return {
|
||||
...row,
|
||||
transaction_label: `#${row.id}`,
|
||||
assets_label: (row.assets || []).join(' / '),
|
||||
reference_label: [row.ref_type, row.ref_key].filter(Boolean).join(':') || '—',
|
||||
actor_label: row.actor_user_id ? `#${row.actor_user_id}` : '系统',
|
||||
delta_label: entries.map((entry) => {
|
||||
const value = formatAssetValue(entry.asset, entry.delta)
|
||||
const signed = entry.asset === 'points' && !value.startsWith('-') && value !== '0' ? `+${value}` : value
|
||||
return `${entry.asset}:${signed}`
|
||||
}).join(';'),
|
||||
balance_label: entries.map((entry) => `${entry.asset}:${formatAssetValue(entry.asset, entry.balance_after)}`).join(';'),
|
||||
delta_label: `${formatAssetValue(entry.asset, entry.delta)} / ${formatAssetValue(entry.asset, entry.balance_after)}`,
|
||||
remark: row.remark || '—',
|
||||
}
|
||||
})
|
||||
ledgerPageCount.value = Math.max(1, Math.ceil(Number(data.total || 0) / Number(data.per_page || 20)))
|
||||
ledgerPageCount.value = Math.max(1, Math.ceil(Number(data.total || 0) / Number(data.per_page || 10)))
|
||||
} catch (e) {
|
||||
message.error(e.message || '加载账本失败')
|
||||
message.error(e.message || '加载资产记录失败')
|
||||
} finally {
|
||||
ledgerLoading.value = false
|
||||
}
|
||||
@@ -238,8 +230,10 @@ function searchLedger() {
|
||||
}
|
||||
|
||||
function resetLedger() {
|
||||
Object.assign(ledgerFilters, { asset: null, transaction_id: '', operation_key: '', ref_type: '', ref_key: '', actor_user_id: '' })
|
||||
searchLedger()
|
||||
Object.assign(ledgerFilters, { user_id: '', asset: 'points', date_from: '', date_to: '' })
|
||||
ledgerRows.value = []
|
||||
ledgerPage.value = 1
|
||||
ledgerPageCount.value = 1
|
||||
}
|
||||
|
||||
async function loadCfg() {
|
||||
@@ -270,6 +264,5 @@ async function saveCfg() {
|
||||
|
||||
onMounted(() => {
|
||||
loadCfg()
|
||||
loadLedger()
|
||||
})
|
||||
</script>
|
||||
@@ -6,7 +6,7 @@
|
||||
:icon="pageMeta.icon"
|
||||
:schema="pageMeta.groups"
|
||||
:form-data="formData"
|
||||
:dirty="dirty"
|
||||
:dirty="isSmsPage ? isSmsSettingsDirty : dirty"
|
||||
:loading="loading"
|
||||
:saving="saving"
|
||||
:load-error="loadError"
|
||||
@@ -131,6 +131,7 @@ const showTestModal = ref(false)
|
||||
const testLoading = ref(false)
|
||||
const testResult = ref(null)
|
||||
const serverSchema = ref(null)
|
||||
const smsSavedSettings = ref('')
|
||||
const captchaSecretsToClear = ref(new Set())
|
||||
const captchaProviders = [
|
||||
{ key: 'tencent', label: '腾讯云 Captcha', component: TencentConfig },
|
||||
@@ -144,8 +145,17 @@ const captchaProviderTab = ref('tencent')
|
||||
const resolvedDomain = computed(() => normalizeSchemaDomain(props.domain || route.meta?.canonicalDomain || route.meta?.schemaDomain || resolveDomainFromPath(route.path)))
|
||||
const resolvedSection = computed(() => props.section || route.meta?.section || route.meta?.schemaSection || resolveSectionFromPath(route.path))
|
||||
const isEmailPage = computed(() => resolvedDomain.value === 'global' && resolvedSection.value === 'email')
|
||||
const isSmsPage = computed(() => resolvedDomain.value === 'users' && resolvedSection.value === 'sms')
|
||||
const isPermissionsLogin = computed(() => resolvedDomain.value === 'permissions' && resolvedSection.value === 'login')
|
||||
const isPermissionsModeration = computed(() => resolvedDomain.value === 'permissions' && resolvedSection.value === 'moderation')
|
||||
const isSmsSettingsDirty = computed(() => smsSavedSettings.value !== '' && smsSavedSettings.value !== smsSettingsSnapshot(formData.value))
|
||||
|
||||
function smsSettingsSnapshot(data) {
|
||||
const settings = { ...(data || {}) }
|
||||
delete settings.sms_test_phone
|
||||
delete settings.sms_test_send
|
||||
return JSON.stringify(settings)
|
||||
}
|
||||
|
||||
function resolveDomainFromPath(path) {
|
||||
const parts = path.split('/').filter(Boolean)
|
||||
@@ -216,6 +226,10 @@ const {
|
||||
delete payload[key]
|
||||
}
|
||||
})
|
||||
if (isSmsPage.value) {
|
||||
delete payload.sms_test_phone
|
||||
delete payload.sms_test_send
|
||||
}
|
||||
if (clearSecrets.length) payload.__clear_secrets = clearSecrets
|
||||
if (isPermissionsLogin.value && captchaSecretsToClear.value.size) {
|
||||
payload.__clear_secrets = [...(payload.__clear_secrets || []), ...captchaSecretsToClear.value]
|
||||
@@ -226,12 +240,34 @@ const {
|
||||
return response
|
||||
},
|
||||
defaults: defaults.value,
|
||||
onLoaded: (data) => {
|
||||
if (isSmsPage.value) smsSavedSettings.value = smsSettingsSnapshot(data)
|
||||
},
|
||||
onSaved: () => {
|
||||
if (isSmsPage.value) smsSavedSettings.value = smsSettingsSnapshot(formData.value)
|
||||
message.success('设置已保存')
|
||||
},
|
||||
})
|
||||
|
||||
async function handleFieldAction(key) {
|
||||
if (key === 'sms_test_send' && isSmsPage.value) {
|
||||
const phone = String(formData.value?.sms_test_phone || '').trim()
|
||||
if (!phone) {
|
||||
message.warning('请先填写测试手机号')
|
||||
return
|
||||
}
|
||||
if (isSmsSettingsDirty.value) {
|
||||
message.warning('请先保存当前配置,再发送测试短信')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const response = await api.testSms(phone)
|
||||
message.success(response?.message || '测试短信已发送')
|
||||
} catch (error) {
|
||||
message.error(error?.message || '测试短信发送失败')
|
||||
}
|
||||
return
|
||||
}
|
||||
if (key !== 'email_test_send' || !isEmailPage.value) return
|
||||
const to = String(formData.value?.email_test_to || '').trim()
|
||||
if (!to) {
|
||||
|
||||
@@ -1,316 +0,0 @@
|
||||
<template>
|
||||
<div class="aksrt-page-container">
|
||||
<PageHeader title="AI 连接配置" desc="配置 AI 生成能力的连接方式。优先使用 WordPress AI Connectors;也可填写 OpenAI 兼容接口。SEO 生成入口在文章「独立 SEO」框,摘要/标签入口在前台投稿页。" />
|
||||
|
||||
<div v-if="loadError" class="aksrt-ai__form-card aksrt-ai__load-error" role="alert">
|
||||
<strong>加载失败:</strong>{{ loadError }}
|
||||
</div>
|
||||
|
||||
<div class="aksrt-ai__status">
|
||||
<div class="aksrt-ai__status-card">
|
||||
<div class="aksrt-ai__status-icon" :class="status.available ? 'aksrt-ai__status-icon--ok' : 'aksrt-ai__status-icon--off'">
|
||||
<i class="fa fab fa-android"></i>
|
||||
</div>
|
||||
<div class="aksrt-ai__status-info">
|
||||
<div class="aksrt-ai__status-label">可用状态</div>
|
||||
<div class="aksrt-ai__status-value">{{ status.available ? '可用' : '不可用' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aksrt-ai__status-card">
|
||||
<div class="aksrt-ai__status-icon" :class="status.wp_ai_available ? 'aksrt-ai__status-icon--ok' : 'aksrt-ai__status-icon--warn'">
|
||||
<i class="fa fas fa-plug"></i>
|
||||
</div>
|
||||
<div class="aksrt-ai__status-info">
|
||||
<div class="aksrt-ai__status-label">WP AI Client</div>
|
||||
<div class="aksrt-ai__status-value">{{ status.wp_ai_available ? '已就绪' : '不可用' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aksrt-ai__status-card">
|
||||
<div class="aksrt-ai__status-icon" :class="status.theme_available ? 'aksrt-ai__status-icon--ok' : 'aksrt-ai__status-icon--warn'">
|
||||
<i class="fa fas fa-key"></i>
|
||||
</div>
|
||||
<div class="aksrt-ai__status-info">
|
||||
<div class="aksrt-ai__status-label">主题 Provider</div>
|
||||
<div class="aksrt-ai__status-value">{{ status.theme_available ? '已配置' : '未配置' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aksrt-ai__status-card">
|
||||
<div class="aksrt-ai__status-icon aksrt-ai__status-icon--info">
|
||||
<i class="fa fas fa-road"></i>
|
||||
</div>
|
||||
<div class="aksrt-ai__status-info">
|
||||
<div class="aksrt-ai__status-label">当前后端</div>
|
||||
<div class="aksrt-ai__status-value">{{ backendLabel }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="aksrt-ai__form-card">
|
||||
<h3>主题 Provider 配置(回退链路)</h3>
|
||||
<p class="aksrt-form-hint">已配置主题 Provider 时优先走下方接口;未配置时才尝试 WordPress AI Connectors。NewAPI/兼容网关请填到 <code>/v1</code>(也可只填域名,保存时会自动补全)。</p>
|
||||
<div class="aksrt-form-group">
|
||||
<label>
|
||||
<input type="checkbox" v-model="form.enabled"> 启用主题 AI Provider
|
||||
</label>
|
||||
</div>
|
||||
<div class="aksrt-form-group">
|
||||
<label>AI Provider</label>
|
||||
<select v-model="form.provider" class="aksrt-select">
|
||||
<option value="none">不使用</option>
|
||||
<option value="openai">OpenAI 兼容接口</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="aksrt-form-group" v-if="form.provider !== 'none'">
|
||||
<label>API Key</label>
|
||||
<input v-model="form.api_key" type="password" class="aksrt-input" placeholder="sk-...">
|
||||
<p class="aksrt-form-hint">保存后不会回显完整密钥</p>
|
||||
</div>
|
||||
<div class="aksrt-form-group" v-if="form.provider === 'openai'">
|
||||
<label>API Base URL</label>
|
||||
<input v-model="form.api_base" type="text" class="aksrt-input" placeholder="https://llm.example.com/v1">
|
||||
<p class="aksrt-form-hint">示例:https://api.openai.com/v1 或 https://你的网关/v1(不要填到 /chat/completions)</p>
|
||||
</div>
|
||||
<div class="aksrt-form-group" v-if="form.provider !== 'none'">
|
||||
<label>模型</label>
|
||||
<input v-model="form.model" type="text" class="aksrt-input" placeholder="gpt-3.5-turbo">
|
||||
</div>
|
||||
<div class="aksrt-form-group" v-if="form.provider !== 'none'">
|
||||
<label>最大 Token 数</label>
|
||||
<input v-model.number="form.max_tokens" type="number" min="100" max="4000" class="aksrt-input">
|
||||
</div>
|
||||
<div class="aksrt-form-group" v-if="form.provider !== 'none'">
|
||||
<label>每分钟请求限制</label>
|
||||
<input v-model.number="form.rate_limit" type="number" min="1" max="100" class="aksrt-input">
|
||||
</div>
|
||||
<div class="aksrt-form-actions">
|
||||
<button class="aksrt-button aksrt-button--primary" :disabled="saving" @click="saveSettings">
|
||||
{{ saving ? '保存中...' : '保存设置' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="aksrt-ai__form-card">
|
||||
<h3>已接通能力</h3>
|
||||
<table class="aksrt-table aksrt-table--sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>能力</th>
|
||||
<th>入口</th>
|
||||
<th>状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="feat in features" :key="feat.key">
|
||||
<td>{{ feat.name }}</td>
|
||||
<td>{{ feat.scene }}</td>
|
||||
<td>
|
||||
<span class="aksrt-badge" :class="feat.ready ? 'aksrt-badge--success' : 'aksrt-badge--warning'">
|
||||
{{ feat.ready ? '可用' : '未就绪' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="aksrt-form-hint">SEO 开关与提示词请到「全局 → SEO」配置(seo_ai_enabled / ai_seo_*_prompt)。</p>
|
||||
</div>
|
||||
|
||||
<div class="aksrt-ai__form-card aksrt-ai__notice">
|
||||
<h3><i class="fa fas fa-exclamation-triangle"></i> 重要说明</h3>
|
||||
<ul>
|
||||
<li>AI 输出仅作为<strong>建议</strong>,不会自动写库;需用户点「更新/提交」才保存</li>
|
||||
<li>主题 API Key 已配置时优先使用;否则回退 WordPress AI Connectors</li>
|
||||
<li>生成接口要求登录与编辑/投稿权限;配置接口仍需管理员</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import PageHeader from '@/components/PageHeader.vue';
|
||||
import api from '@/api';
|
||||
|
||||
export default {
|
||||
name: 'ExtensionAI',
|
||||
components: { PageHeader },
|
||||
setup() {
|
||||
const status = ref({
|
||||
enabled: false,
|
||||
provider: 'none',
|
||||
configured: false,
|
||||
model: '',
|
||||
api_key: '',
|
||||
rate_limit: 10,
|
||||
available: false,
|
||||
backend: 'none',
|
||||
wp_ai_available: false,
|
||||
theme_available: false,
|
||||
seo_ai_enabled: false,
|
||||
write_assist: false,
|
||||
});
|
||||
|
||||
const form = reactive({
|
||||
enabled: false,
|
||||
provider: 'none',
|
||||
api_key: '',
|
||||
api_base: 'https://api.openai.com/v1',
|
||||
model: 'gpt-3.5-turbo',
|
||||
max_tokens: 1000,
|
||||
rate_limit: 10,
|
||||
});
|
||||
|
||||
const saving = ref(false);
|
||||
const loadError = ref('');
|
||||
|
||||
const backendLabel = computed(() => {
|
||||
const map = { wp_ai: 'WP AI Client', theme: '主题 Provider', none: '无' };
|
||||
return map[status.value.backend] || status.value.backend || '无';
|
||||
});
|
||||
|
||||
const features = computed(() => {
|
||||
const avail = !!status.value.available;
|
||||
const seoReady = avail && !!status.value.seo_ai_enabled;
|
||||
return [
|
||||
{ key: 'seo', name: 'AI 生成 SEO', scene: '文章/分类「独立 SEO」框', ready: seoReady },
|
||||
{ key: 'summary', name: 'AI 生成摘要', scene: '前台投稿页', ready: avail },
|
||||
{ key: 'tags', name: 'AI 建议标签', scene: '前台投稿页', ready: avail },
|
||||
];
|
||||
});
|
||||
|
||||
function hydrateFromStatus(data) {
|
||||
if (!data || typeof data !== 'object') return;
|
||||
status.value = {
|
||||
enabled: !!data.enabled,
|
||||
provider: data.provider || 'none',
|
||||
configured: !!data.configured,
|
||||
model: data.model || '',
|
||||
api_key: '',
|
||||
rate_limit: data.rate_limit || 10,
|
||||
available: !!data.available,
|
||||
backend: data.backend || 'none',
|
||||
wp_ai_available: !!data.wp_ai_available,
|
||||
theme_available: !!data.theme_available,
|
||||
seo_ai_enabled: !!data.seo_ai_enabled,
|
||||
write_assist: !!data.write_assist,
|
||||
};
|
||||
form.enabled = !!data.enabled;
|
||||
form.provider = data.provider || 'none';
|
||||
form.model = data.model || 'gpt-3.5-turbo';
|
||||
form.rate_limit = data.rate_limit || 10;
|
||||
if (data.api_base) form.api_base = data.api_base;
|
||||
if (typeof data.max_tokens === 'number' && data.max_tokens > 0) {
|
||||
form.max_tokens = data.max_tokens;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadStatus() {
|
||||
loadError.value = '';
|
||||
try {
|
||||
const json = await api.getAiStatus();
|
||||
hydrateFromStatus(json.data || {});
|
||||
} catch (e) {
|
||||
console.error('Load AI status failed:', e);
|
||||
loadError.value = e?.message || '加载 AI 状态失败';
|
||||
}
|
||||
}
|
||||
|
||||
async function saveSettings() {
|
||||
saving.value = true;
|
||||
try {
|
||||
const payload = {
|
||||
enabled: form.enabled,
|
||||
provider: form.provider,
|
||||
api_base: form.api_base,
|
||||
model: form.model,
|
||||
max_tokens: form.max_tokens,
|
||||
rate_limit: form.rate_limit,
|
||||
};
|
||||
// Only send api_key when the user entered a new one.
|
||||
if (form.api_key && form.api_key !== '***') {
|
||||
payload.api_key = form.api_key;
|
||||
}
|
||||
const json = await api.updateAiSettings(payload);
|
||||
alert(json.message || '设置已保存');
|
||||
await loadStatus();
|
||||
} catch (e) {
|
||||
console.error('Save failed:', e);
|
||||
alert(e?.message || '保存失败');
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => loadStatus());
|
||||
|
||||
return { status, form, saving, loadError, features, backendLabel, saveSettings };
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-ai__status {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.aksrt-ai__status-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e8e8e8;
|
||||
}
|
||||
.aksrt-ai__status-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
}
|
||||
.aksrt-ai__status-icon--ok { background: #d4edda; color: #155724; }
|
||||
.aksrt-ai__status-icon--warn { background: #fff3cd; color: #856404; }
|
||||
.aksrt-ai__status-icon--off { background: #f8d7da; color: #721c24; }
|
||||
.aksrt-ai__status-icon--info { background: #d1ecf1; color: #0c5460; }
|
||||
.aksrt-ai__status-value { font-size: 18px; font-weight: 600; color: var(--aksrt-color-text); }
|
||||
.aksrt-ai__status-label { font-size: 12px; color: #888; }
|
||||
.aksrt-ai__form-card {
|
||||
background: #fff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.aksrt-ai__form-card h3 { margin: 0 0 16px; font-size: 16px; font-weight: 600; }
|
||||
.aksrt-form-hint { font-size: 12px; color: #888; margin-top: 4px; }
|
||||
.aksrt-ai__load-error {
|
||||
background: #fdecea;
|
||||
border-color: #f5c2c0;
|
||||
color: #842029;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.aksrt-ai__notice { background: #fff8e1; border-color: #ffe082; }
|
||||
.aksrt-ai__notice h3 { color: #e65100; }
|
||||
.aksrt-ai__notice ul { margin: 0; padding-left: 20px; }
|
||||
.aksrt-ai__notice li { margin-bottom: 8px; font-size: 14px; }
|
||||
.aksrt-ai__notice code {
|
||||
background: rgba(0,0,0,0.06);
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.aksrt-badge { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 12px; }
|
||||
.aksrt-badge--success { background: #d4edda; color: #155724; }
|
||||
.aksrt-badge--warning { background: #fff3cd; color: #856404; }
|
||||
.aksrt-table { width: 100%; border-collapse: collapse; }
|
||||
.aksrt-table th, .aksrt-table td { border-bottom: 1px solid #eee; padding: 10px 8px; text-align: left; }
|
||||
.aksrt-form-group { margin-bottom: 14px; }
|
||||
.aksrt-input, .aksrt-select { width: 100%; max-width: 480px; padding: 8px 10px; }
|
||||
.aksrt-button { padding: 8px 16px; border: 0; border-radius: 6px; cursor: pointer; }
|
||||
.aksrt-button--primary { background: #1677ff; color: #fff; }
|
||||
.aksrt-button:disabled { opacity: .6; cursor: not-allowed; }
|
||||
</style>
|
||||
@@ -1,220 +1,52 @@
|
||||
<template>
|
||||
<div class="aksrt-page-container">
|
||||
<PageHeader
|
||||
title="身份认证"
|
||||
desc="统一管理认证申请规则与云服务商凭证;审核、驳回和取消认证仍在 WordPress 用户菜单处理。"
|
||||
/>
|
||||
|
||||
<PageHeader title="身份认证" desc="配置通用认证申请规则;申请审核和用户认证资料管理位于 WordPress 用户菜单。" />
|
||||
<n-spin :show="loading" style="width: 100%; min-height: 240px">
|
||||
<n-grid :cols="3" :x-gap="12" :y-gap="12" responsive="screen" item-responsive>
|
||||
<n-grid-item span="3 m:1">
|
||||
<n-card class="aksrt-status-card" size="small">
|
||||
<n-statistic label="身份认证功能">
|
||||
<template #default>{{ verifyForm.user_verify_enabled ? '已启用' : '已停用' }}</template>
|
||||
</n-statistic>
|
||||
<n-tag :type="verifyForm.user_verify_enabled ? 'success' : 'default'" round size="small">
|
||||
{{ verifyForm.user_verify_enabled ? '运行中' : '未运行' }}
|
||||
</n-tag>
|
||||
</n-card>
|
||||
</n-grid-item>
|
||||
<n-grid-item span="3 m:1" v-for="key in providerKeys" :key="key">
|
||||
<n-card class="aksrt-status-card" size="small">
|
||||
<n-statistic :label="providerLabel(key) + '凭证'">
|
||||
<template #default>{{ providerState(key).configured ? '已配置' : '未配置' }}</template>
|
||||
</n-statistic>
|
||||
<n-tag :type="providerState(key).configured ? 'success' : 'warning'" round size="small">
|
||||
{{ identityForm.provider === key ? '当前服务商' : '备用服务商' }}
|
||||
</n-tag>
|
||||
</n-card>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
|
||||
<n-card class="aksrt-section-card" title="通用认证设置">
|
||||
<n-form :model="verifyForm" label-placement="left" label-width="190">
|
||||
<n-form-item label="启用身份认证"><n-switch v-model:value="verifyForm.user_verify_enabled" /></n-form-item>
|
||||
<n-form-item label="公开页显示认证徽标"><n-switch v-model:value="verifyForm.user_verify_public_display" /></n-form-item>
|
||||
<n-form-item label="允许用户前台申请"><n-switch v-model:value="verifyForm.user_verify_allow_apply" /></n-form-item>
|
||||
<n-form-item label="云失败转人工"><n-switch v-model:value="verifyForm.user_verify_allow_manual_fallback" /></n-form-item>
|
||||
<n-form-item label="认证会话有效期(分钟)"><n-input-number v-model:value="verifyForm.user_verify_session_ttl_minutes" :min="5" :max="1440" /></n-form-item>
|
||||
<n-form-item label="结果轮询间隔(秒)"><n-input-number v-model:value="verifyForm.user_verify_poll_interval_seconds" :min="3" :max="60" /></n-form-item>
|
||||
<n-form-item label="个人认证费用"><n-input v-model:value="verifyForm.user_verify_personal_fee" placeholder="0.00" /></n-form-item>
|
||||
<n-form-item label="企业认证费用"><n-input v-model:value="verifyForm.user_verify_enterprise_fee" placeholder="0.00" /></n-form-item>
|
||||
<n-form-item label="个人认证徽标文案">
|
||||
<n-input v-model:value="verifyForm.user_verify_personal_badge_text" placeholder="个人认证" />
|
||||
<n-card class="aksrt-section-card" title="认证申请设置">
|
||||
<n-form :model="form" label-placement="left" label-width="190">
|
||||
<n-form-item label="启用身份认证"><n-switch v-model:value="form.user_auth_s" /></n-form-item>
|
||||
<n-form-item label="允许用户前台申请"><n-switch v-model:value="form.user_auth_apply_s" :disabled="!form.user_auth_s" /></n-form-item>
|
||||
<n-form-item v-if="!form.user_auth_apply_s" label="停用申请说明">
|
||||
<n-input v-model:value="form.user_auth_apply_disable_desc" type="textarea" :rows="2" />
|
||||
</n-form-item>
|
||||
<n-form-item label="企业认证徽标文案">
|
||||
<n-input v-model:value="verifyForm.user_verify_enterprise_badge_text" placeholder="企业认证" />
|
||||
<n-form-item v-if="form.user_auth_apply_s" label="申请最低用户等级">
|
||||
<n-input-number v-model:value="form.user_auth_apply_limit_level" :min="0" :max="30" />
|
||||
<template #feedback>设置为 0 时不限制用户等级。</template>
|
||||
</n-form-item>
|
||||
<n-form-item label="停用申请说明">
|
||||
<n-input v-model:value="verifyForm.user_verify_disable_desc" type="textarea" :rows="2" />
|
||||
</n-form-item>
|
||||
<n-form-item label="申请介绍">
|
||||
<n-input v-model:value="verifyForm.user_verify_desc" type="textarea" :rows="3" />
|
||||
<n-form-item v-if="form.user_auth_apply_s" label="申请介绍">
|
||||
<n-input v-model:value="form.user_auth_apply_desc" type="textarea" :rows="3" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
<n-card class="aksrt-section-card" title="云服务商">
|
||||
<n-form :model="identityForm" label-placement="left" label-width="190">
|
||||
<n-form-item label="当前服务商">
|
||||
<n-radio-group v-model:value="identityForm.provider">
|
||||
<n-space>
|
||||
<n-radio-button value="aliyun">阿里云</n-radio-button>
|
||||
<n-radio-button value="tencent">腾讯云</n-radio-button>
|
||||
</n-space>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
<n-form-item label="回调地址">
|
||||
<n-input-group>
|
||||
<n-input :value="callbackUrlForCurrentProvider" readonly />
|
||||
<n-button @click="copyCallback">复制</n-button>
|
||||
</n-input-group>
|
||||
<template #feedback>将此地址填入服务商控制台需要的回调地址配置。</template>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
<n-tabs v-model:value="activeTab" type="segment" animated class="aksrt-provider-tabs">
|
||||
<n-tab-pane v-for="key in providerKeys" :key="key" :name="key" :tab="providerLabel(key)">
|
||||
<n-card class="aksrt-section-card">
|
||||
<template #header>
|
||||
<n-space align="center">
|
||||
<span>{{ providerLabel(key) }}凭证</span>
|
||||
<n-tag :type="providerState(key).configured ? 'success' : 'warning'" size="small" round>
|
||||
{{ providerState(key).configured ? '已配置' : '未配置' }}
|
||||
</n-tag>
|
||||
</n-space>
|
||||
</template>
|
||||
<n-alert type="info" :show-icon="false" style="margin-bottom: 18px">
|
||||
已保存凭证不会回显。留空表示保留原值;输入新值并保存会替换对应凭证。
|
||||
</n-alert>
|
||||
<n-form label-placement="left" label-width="190">
|
||||
<n-form-item v-if="key === 'aliyun'" label="Region">
|
||||
<n-input v-model:value="verifyForm.user_verify_aliyun_region" placeholder="cn-hangzhou" />
|
||||
</n-form-item>
|
||||
<n-form-item v-if="key === 'aliyun'" label="个人认证 SceneId">
|
||||
<n-input v-model:value="verifyForm.user_verify_aliyun_personal_flow" placeholder="从阿里云实人认证控制台获取 SceneId" />
|
||||
</n-form-item>
|
||||
<n-form-item v-if="key === 'aliyun'" label="个人认证 ProductCode">
|
||||
<n-select v-model:value="verifyForm.user_verify_aliyun_product_code" :options="aliyunProductOptions" />
|
||||
</n-form-item>
|
||||
<n-form-item v-if="key === 'aliyun'" label="企业认证业务标识">
|
||||
<n-input v-model:value="verifyForm.user_verify_aliyun_enterprise_flow" placeholder="可留空,默认复用个人认证 SceneId" />
|
||||
</n-form-item>
|
||||
<n-form-item v-if="key === 'tencent'" label="Region">
|
||||
<n-input v-model:value="verifyForm.user_verify_tencent_region" placeholder="ap-guangzhou" />
|
||||
</n-form-item>
|
||||
<n-form-item v-if="key === 'tencent'" label="个人认证 RuleId">
|
||||
<n-input v-model:value="verifyForm.user_verify_tencent_personal_flow" placeholder="从腾讯云实名核身控制台获取 RuleId" />
|
||||
</n-form-item>
|
||||
<n-form-item v-if="key === 'tencent'" label="企业认证流程标识">
|
||||
<n-input v-model:value="verifyForm.user_verify_tencent_enterprise_flow" placeholder="可留空,企业四要素后复用个人认证 RuleId" />
|
||||
</n-form-item>
|
||||
<n-form-item v-for="field in credentialFields[key]" :key="field.key" :label="field.label">
|
||||
<n-input
|
||||
v-model:value="credentials[key][field.key]"
|
||||
type="password"
|
||||
show-password-on="click"
|
||||
:placeholder="credentialPlaceholder(key, field.key)"
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="显式清除凭证">
|
||||
<n-checkbox v-model:checked="clearCredentials[key]">
|
||||
保存时删除 {{ providerLabel(key) }}全部凭证
|
||||
</n-checkbox>
|
||||
<template #feedback>清除后该服务商立即变为未配置;此操作不会因输入框留空而触发。</template>
|
||||
</n-form-item>
|
||||
<n-form-item label="配置测试">
|
||||
<n-button :loading="testing === key" :disabled="saving" @click="testProvider(key)">
|
||||
测试 {{ providerLabel(key) }}配置
|
||||
</n-button>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
|
||||
<div class="aksrt-form-actions">
|
||||
<n-button :disabled="!dirty" @click="load">放弃更改</n-button>
|
||||
<n-button type="primary" :loading="saving" @click="save">保存全部配置</n-button>
|
||||
<n-button :disabled="!dirty || saving" @click="load">放弃更改</n-button>
|
||||
<n-button type="primary" :disabled="!dirty" :loading="saving" @click="save">保存设置</n-button>
|
||||
</div>
|
||||
</n-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { useDialog, useMessage } from 'naive-ui'
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import api from '@/api'
|
||||
|
||||
const defaults = {
|
||||
user_auth_s: true,
|
||||
user_auth_apply_s: true,
|
||||
user_auth_apply_disable_desc: '由于系统正在维护,暂时无法申请身份认证\n详情请联系管理员',
|
||||
user_auth_apply_limit_level: 0,
|
||||
user_auth_apply_desc: '请填写认证身份、简介和申请说明,提交后由管理员审核。',
|
||||
}
|
||||
const message = useMessage()
|
||||
const dialog = useDialog()
|
||||
const providerKeys = ['aliyun', 'tencent']
|
||||
const aliyunProductOptions = [
|
||||
{ label: '实人认证(ID_PRO)', value: 'ID_PRO' },
|
||||
{ label: '活体人脸验证(PV_FV)', value: 'PV_FV' },
|
||||
{ label: '活体检测(LR_FR)', value: 'LR_FR' },
|
||||
]
|
||||
const credentialFields = {
|
||||
aliyun: [
|
||||
{ key: 'access_key_id', label: 'AccessKey ID' },
|
||||
{ key: 'access_key_secret', label: 'AccessKey Secret' },
|
||||
],
|
||||
tencent: [
|
||||
{ key: 'secret_id', label: 'SecretId' },
|
||||
{ key: 'secret_key', label: 'SecretKey' },
|
||||
],
|
||||
}
|
||||
const verifyDefaults = {
|
||||
user_verify_enabled: true,
|
||||
user_verify_public_display: true,
|
||||
user_verify_allow_apply: true,
|
||||
user_verify_allow_manual_fallback: true,
|
||||
user_verify_session_ttl_minutes: 30,
|
||||
user_verify_poll_interval_seconds: 5,
|
||||
user_verify_personal_fee: '0.00',
|
||||
user_verify_enterprise_fee: '0.00',
|
||||
user_verify_personal_badge_text: '个人认证',
|
||||
user_verify_enterprise_badge_text: '企业认证',
|
||||
user_verify_disable_desc: '由于系统正在维护,暂时无法申请身份认证\n详情请联系管理员',
|
||||
user_verify_desc: '认证用户会在昵称后显示认证徽章,同时享受多种优先待遇。申请请确保信息属实。',
|
||||
user_verify_aliyun_region: 'cn-hangzhou',
|
||||
user_verify_aliyun_personal_flow: '',
|
||||
user_verify_aliyun_product_code: 'ID_PRO',
|
||||
user_verify_aliyun_enterprise_flow: '',
|
||||
user_verify_tencent_region: 'ap-guangzhou',
|
||||
user_verify_tencent_personal_flow: '',
|
||||
user_verify_tencent_enterprise_flow: '',
|
||||
}
|
||||
|
||||
const form = reactive({ ...defaults })
|
||||
const initial = ref('')
|
||||
const loading = ref(true)
|
||||
const saving = ref(false)
|
||||
const testing = ref('')
|
||||
const activeTab = ref('aliyun')
|
||||
const callbackUrls = reactive({ aliyun: '', tencent: '' })
|
||||
const callbackUrlForCurrentProvider = computed(() => callbackUrls[identityForm.provider] || '')
|
||||
const initialSnapshot = ref('')
|
||||
const states = reactive({ aliyun: {}, tencent: {} })
|
||||
const verifyForm = reactive({ ...verifyDefaults })
|
||||
const identityForm = reactive({ provider: 'aliyun' })
|
||||
const credentials = reactive({ aliyun: {}, tencent: {} })
|
||||
const clearCredentials = reactive({ aliyun: false, tencent: false })
|
||||
const snapshot = computed(() => JSON.stringify(form))
|
||||
const dirty = computed(() => initial.value !== '' && snapshot.value !== initial.value)
|
||||
|
||||
const snapshot = computed(() => JSON.stringify({ verifyForm, identityForm, credentials, clearCredentials }))
|
||||
const dirty = computed(() => initialSnapshot.value !== '' && snapshot.value !== initialSnapshot.value)
|
||||
watch(() => identityForm.provider, value => { activeTab.value = value })
|
||||
|
||||
function providerLabel(key) {
|
||||
return key === 'aliyun' ? '阿里云' : '腾讯云'
|
||||
}
|
||||
function providerState(key) {
|
||||
return states[key] || {}
|
||||
}
|
||||
function credentialPlaceholder(provider, field) {
|
||||
return providerState(provider).configured_fields?.[field] ? '已配置,留空则保留' : '尚未配置'
|
||||
}
|
||||
function extractData(response) {
|
||||
return response?.data ?? response ?? {}
|
||||
}
|
||||
@@ -222,123 +54,27 @@ function extractData(response) {
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const [verifyResponse, identityResponse] = await Promise.all([
|
||||
api.getSchemaPage('users', 'verify'),
|
||||
api.getIdentitySettings(),
|
||||
])
|
||||
Object.assign(verifyForm, verifyDefaults, extractData(verifyResponse))
|
||||
const identity = extractData(identityResponse)
|
||||
identityForm.provider = providerKeys.includes(identity.provider) ? identity.provider : 'aliyun'
|
||||
Object.assign(callbackUrls, identity.callback_url || {})
|
||||
providerKeys.forEach(key => {
|
||||
states[key] = identity.providers?.[key] || {}
|
||||
credentials[key] = {}
|
||||
clearCredentials[key] = false
|
||||
})
|
||||
activeTab.value = identityForm.provider
|
||||
initialSnapshot.value = snapshot.value
|
||||
Object.assign(form, defaults, extractData(await api.getSchemaPage('users', 'verify')))
|
||||
initial.value = snapshot.value
|
||||
} catch (error) {
|
||||
message.error(error.message || '加载身份认证配置失败')
|
||||
message.error(error.message || '加载身份认证设置失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function buildProviderPayload(key) {
|
||||
const replacement = {}
|
||||
credentialFields[key].forEach(({ key: field }) => {
|
||||
const value = String(credentials[key]?.[field] || '').trim()
|
||||
if (value) replacement[field] = value
|
||||
})
|
||||
return {
|
||||
credentials: replacement,
|
||||
clear_credentials: clearCredentials[key],
|
||||
}
|
||||
}
|
||||
|
||||
async function persist() {
|
||||
const [verifyResponse, identityResponse] = await Promise.all([
|
||||
api.saveSchemaPage('users', 'verify', { ...verifyForm }),
|
||||
api.saveIdentitySettings({
|
||||
provider: identityForm.provider,
|
||||
providers: {
|
||||
aliyun: buildProviderPayload('aliyun'),
|
||||
tencent: buildProviderPayload('tencent'),
|
||||
},
|
||||
}),
|
||||
])
|
||||
const identity = extractData(identityResponse)
|
||||
Object.assign(verifyForm, extractData(verifyResponse))
|
||||
providerKeys.forEach(key => {
|
||||
states[key] = identity.providers?.[key] || states[key]
|
||||
credentials[key] = {}
|
||||
clearCredentials[key] = false
|
||||
})
|
||||
Object.assign(callbackUrls, identity.callback_url || callbackUrls)
|
||||
initialSnapshot.value = snapshot.value
|
||||
}
|
||||
|
||||
async function save() {
|
||||
const hasClear = providerKeys.some(key => clearCredentials[key])
|
||||
const run = async () => {
|
||||
saving.value = true
|
||||
try {
|
||||
await persist()
|
||||
message.success('身份认证配置已保存')
|
||||
} catch (error) {
|
||||
message.error(error.message || '保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
if (!hasClear) return run()
|
||||
dialog.warning({
|
||||
title: '确认清除凭证',
|
||||
content: '已勾选的服务商凭证将被永久删除,是否继续保存?',
|
||||
positiveText: '清除并保存',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: run,
|
||||
})
|
||||
}
|
||||
|
||||
async function testProvider(provider) {
|
||||
testing.value = provider
|
||||
saving.value = true
|
||||
try {
|
||||
const response = await api.testIdentitySettings({ provider })
|
||||
message.success(response?.message || `${providerLabel(provider)}配置测试通过`)
|
||||
Object.assign(form, defaults, extractData(await api.saveSchemaPage('users', 'verify', { ...form })))
|
||||
initial.value = snapshot.value
|
||||
message.success('身份认证设置已保存')
|
||||
} catch (error) {
|
||||
message.error(error.message || `${providerLabel(provider)}配置测试失败`)
|
||||
message.error(error.message || '保存身份认证设置失败')
|
||||
} finally {
|
||||
testing.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
async function copyCallback() {
|
||||
if (!callbackUrlForCurrentProvider.value) return message.warning('回调地址暂不可用')
|
||||
try {
|
||||
await navigator.clipboard.writeText(callbackUrlForCurrentProvider.value)
|
||||
message.success('回调地址已复制')
|
||||
} catch {
|
||||
message.error('复制失败,请手动复制')
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-status-card :deep(.n-card__content) {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
.aksrt-section-card { margin-top: 16px; }
|
||||
.aksrt-provider-tabs { margin-top: 16px; }
|
||||
.aksrt-form-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,3 +0,0 @@
|
||||
export const extensionsPageLoaders = {
|
||||
ExtensionAI: () => import('@/pages/extensions/ExtensionAI.vue'),
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
import { corePageLoaders } from './core'
|
||||
import { usersPageLoaders } from './users'
|
||||
import { toolsPageLoaders } from './tools'
|
||||
import { extensionsPageLoaders } from './extensions'
|
||||
|
||||
export const componentLoaders = {
|
||||
...corePageLoaders,
|
||||
...usersPageLoaders,
|
||||
...toolsPageLoaders,
|
||||
...extensionsPageLoaders,
|
||||
}
|
||||
@@ -3,12 +3,16 @@
|
||||
* Each key corresponds to a sub-page under the "扩展&增强" section.
|
||||
*/
|
||||
|
||||
import { systemSchema } from './system.js'
|
||||
import { ipSchema } from './ip.js'
|
||||
import { auditSchema } from './audit.js'
|
||||
import { pushSchema } from './push.js'
|
||||
import { securitySchema } from './security.js'
|
||||
|
||||
export const extensionsSchemas = {
|
||||
system: systemSchema,
|
||||
ip: ipSchema,
|
||||
audit: auditSchema,
|
||||
push: pushSchema,
|
||||
security: securitySchema,
|
||||
}
|
||||
@@ -2,21 +2,16 @@ export const pushSchema = {
|
||||
key: 'extensions.push',
|
||||
title: '百度推送',
|
||||
icon: 'fa fas fa-paper-plane',
|
||||
description: '配置搜索引擎主动推送、批量提交和状态追踪。',
|
||||
description: '配置百度主动推送、收录通道和推送状态。',
|
||||
defaults: {
|
||||
baidu_push_enabled: false,
|
||||
baidu_push_token: '',
|
||||
baidu_push_auto: true,
|
||||
baidu_push_on_publish: true,
|
||||
baidu_push_on_update: false,
|
||||
baidu_push_batch_size: 50,
|
||||
baidu_push_normal: true,
|
||||
baidu_push_fast: false,
|
||||
baidu_push_log_enabled: true,
|
||||
xzh_on: false,
|
||||
xzh_appid: '',
|
||||
xzh_render_tail: true,
|
||||
xzh_jsonld_single: true,
|
||||
xzh_jsonld_page: false,
|
||||
xzh_jsonld_img: false,
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
@@ -25,7 +20,7 @@ export const pushSchema = {
|
||||
icon: 'fa fas fa-rocket',
|
||||
fields: [
|
||||
{ key: 'baidu_push_enabled', label: '启用百度推送', type: 'switch', default: false, help: '开启后将自动向百度搜索推送页面链接' },
|
||||
{ key: 'baidu_push_token', label: '推送 Token', type: 'input', default: '', dependsOn: { key: 'baidu_push_enabled', value: true }, help: '在百度搜索资源平台获取的推送准入 Token' },
|
||||
{ key: 'baidu_push_token', label: '推送 Token', type: 'input', default: '', dependsOn: { key: 'baidu_push_enabled', value: true }, help: '在百度搜索资源平台获取的推送准入 Token(仅填 Token,勿填完整链接),并确保平台中的站点为本站' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -34,21 +29,17 @@ export const pushSchema = {
|
||||
icon: 'fa fas fa-bolt',
|
||||
fields: [
|
||||
{ key: 'baidu_push_auto', label: '自动推送 (页面加载时)', type: 'switch', default: true, dependsOn: { key: 'baidu_push_enabled', value: true }, help: '在前台页面中嵌入百度自动推送 JS 代码' },
|
||||
{ key: 'baidu_push_on_publish', label: '发布时主动推送', type: 'switch', default: true, dependsOn: { key: 'baidu_push_enabled', value: true } },
|
||||
{ key: 'baidu_push_on_update', label: '更新时推送', type: 'switch', default: false, dependsOn: { key: 'baidu_push_enabled', value: true } },
|
||||
{ key: 'baidu_push_on_publish', label: '发布时主动推送', type: 'switch', default: true, dependsOn: { key: 'baidu_push_enabled', value: true }, help: '文章 / 分类首次发布时推送链接;推送成功后会记录,不再重复提交' },
|
||||
{ key: 'baidu_push_on_update', label: '更新时推送', type: 'switch', default: false, dependsOn: { key: 'baidu_push_enabled', value: true }, help: '已发布内容再次保存时,若上次推送未成功则重试;需主动再次提交请在编辑页勾选「重新提交」' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'xzh',
|
||||
title: '熊掌号 / JSON-LD',
|
||||
icon: 'fa fas fa-paw',
|
||||
key: 'channel',
|
||||
title: '收录通道',
|
||||
icon: 'fa fas fa-inbox',
|
||||
fields: [
|
||||
{ key: 'xzh_on', label: '启用熊掌号', type: 'switch', default: false },
|
||||
{ key: 'xzh_appid', label: '百度 AppID', type: 'input', default: '', dependsOn: { key: 'xzh_on', value: true } },
|
||||
{ key: 'xzh_render_tail', label: '文章底部显示熊掌号尾部', type: 'switch', default: true, dependsOn: { key: 'xzh_on', value: true } },
|
||||
{ key: 'xzh_jsonld_single', label: '文章页 JSON-LD', type: 'switch', default: true, dependsOn: { key: 'xzh_on', value: true } },
|
||||
{ key: 'xzh_jsonld_page', label: '页面 JSON-LD', type: 'switch', default: false, dependsOn: { key: 'xzh_on', value: true } },
|
||||
{ key: 'xzh_jsonld_img', label: '不添加图片', type: 'switch', default: false, dependsOn: { key: 'xzh_on', value: true } },
|
||||
{ key: 'baidu_push_normal', label: '普通收录', type: 'switch', default: true, dependsOn: { key: 'baidu_push_enabled', value: true }, help: '每天可提交最多10万条有价值内容,收录速度较慢' },
|
||||
{ key: 'baidu_push_fast', label: '快速收录', type: 'switch', default: false, dependsOn: { key: 'baidu_push_enabled', value: true }, help: '百度新推出的高效收录接口,目前仅对部分优质站点开放,请确保您的站点已开放快速收录功能' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -56,8 +47,7 @@ export const pushSchema = {
|
||||
title: '高级设置',
|
||||
icon: 'fa fas fa-cog',
|
||||
fields: [
|
||||
{ key: 'baidu_push_batch_size', label: '批量推送条数', type: 'number', default: 50, min: 1, max: 500, dependsOn: { key: 'baidu_push_enabled', value: true } },
|
||||
{ key: 'baidu_push_log_enabled', label: '记录推送日志', type: 'switch', default: true, dependsOn: { key: 'baidu_push_enabled', value: true } },
|
||||
{ key: 'baidu_push_log_enabled', label: '记录推送日志', type: 'switch', default: true, dependsOn: { key: 'baidu_push_enabled', value: true }, help: 'WP_DEBUG 开启时,将推送结果写入错误日志' },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
export const securitySchema = {
|
||||
key: 'extensions.security',
|
||||
title: '网站安全',
|
||||
icon: 'fa fas fa-umbrella',
|
||||
description: '防刷限制、验证码发送限制与站点安全加固。',
|
||||
defaults: {
|
||||
security_brush_post_10min: 20,
|
||||
security_brush_post_day: 50,
|
||||
security_brush_comment_10min: 20,
|
||||
security_brush_comment_day: 100,
|
||||
security_brush_upload_10min: 50,
|
||||
security_brush_upload_day: 200,
|
||||
security_brush_register_10min: 20,
|
||||
security_brush_register_day: 100,
|
||||
security_brush_order_10min: 30,
|
||||
security_brush_order_day: 100,
|
||||
security_captcha_send_cooldown: 60,
|
||||
security_captcha_to_hour: 15,
|
||||
security_captcha_to_day: 30,
|
||||
security_captcha_ip_10min: 20,
|
||||
security_captcha_ip_day: 50,
|
||||
security_error_frequency_limit: true,
|
||||
security_disabled_pingback: true,
|
||||
security_disabled_xmlrpc: true,
|
||||
security_user_disable_admin: true,
|
||||
close_attachment_page: true,
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'brush_post',
|
||||
title: '[防刷机制]发布限制',
|
||||
icon: 'fa fas fa-pen',
|
||||
description: '限制单个用户发布文章、帖子、板块的总数量(为0则不限制)。',
|
||||
fields: [
|
||||
{ key: 'security_brush_post_10min', label: '10分钟内最多发布 (篇)', type: 'number', default: 20, min: 0, max: 200 },
|
||||
{ key: 'security_brush_post_day', label: '一天内最多发布 (篇)', type: 'number', default: 50, min: 0, max: 2000 },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'brush_comment',
|
||||
title: '[防刷机制]评论限制',
|
||||
icon: 'fa fas fa-comments',
|
||||
description: '限制单个用户发布评论的总数量(为0则不限制)。',
|
||||
fields: [
|
||||
{ key: 'security_brush_comment_10min', label: '10分钟内最多评论 (条)', type: 'number', default: 20, min: 0, max: 200 },
|
||||
{ key: 'security_brush_comment_day', label: '一天内最多评论 (条)', type: 'number', default: 100, min: 0, max: 2000, help: '如果启用了免登陆可评论,则根据用户IP地址判断' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'brush_upload',
|
||||
title: '[防刷机制]上传限制',
|
||||
icon: 'fa fas fa-upload',
|
||||
description: '限制单个用户上传内容的总数量(为0则不限制)。防刷机制主要为了防止用户恶意多次提交内容,请根据网站的用户量合理设置,此机制不会限制管理员。',
|
||||
fields: [
|
||||
{ key: 'security_brush_upload_10min', label: '10分钟内最多上传 (个)', type: 'number', default: 50, min: 0, max: 200 },
|
||||
{ key: 'security_brush_upload_day', label: '一天内最多上传 (个)', type: 'number', default: 200, min: 0, max: 2000 },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'brush_register',
|
||||
title: '[防刷机制]注册限制',
|
||||
icon: 'fa fas fa-user-plus',
|
||||
description: '限制每个IP地址用户注册数量,此项无法判断管理员身份,所以管理员也会受到限制。',
|
||||
fields: [
|
||||
{ key: 'security_brush_register_10min', label: '10分钟内最多注册 (个)', type: 'number', default: 20, min: 0, max: 200 },
|
||||
{ key: 'security_brush_register_day', label: '一天内最多注册 (个)', type: 'number', default: 100, min: 0, max: 2000, help: '如果您的服务器不能正确的获取用户真实IP地址,请关闭此项(0为不限制)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'brush_order',
|
||||
title: '[防刷机制]下单限制',
|
||||
icon: 'fa fas fa-shopping-cart',
|
||||
description: '限制每个用户下单数量,未登录用户按IP地址判断。',
|
||||
fields: [
|
||||
{ key: 'security_brush_order_10min', label: '10分钟内最多下单 (个订单)', type: 'number', default: 30, min: 0, max: 200 },
|
||||
{ key: 'security_brush_order_day', label: '一天内最多下单 (个订单)', type: 'number', default: 100, min: 0, max: 2000, help: '如果您的服务器不能正确的获取用户真实IP地址,请关闭此项(0为不限制)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'brush_captcha',
|
||||
title: '[防刷机制]验证码发送限制',
|
||||
icon: 'fa fas fa-paper-plane',
|
||||
description: '限制验证码邮件/短信的发送频率,防止恶意程序批量轰炸特定邮箱或手机号。',
|
||||
fields: [
|
||||
{ key: 'security_captcha_send_cooldown', label: '同一收件人/IP发送冷却 (秒)', type: 'number', default: 60, min: 0, max: 600, help: '同一邮箱或手机号、同一IP地址发送验证码后,需间隔此时间才可再次发送(0为不限制)' },
|
||||
{ key: 'security_captcha_to_hour', label: '同一收件人1小时内最多 (次)', type: 'number', default: 15, min: 0, max: 500 },
|
||||
{ key: 'security_captcha_to_day', label: '同一收件人一天内最多 (次)', type: 'number', default: 30, min: 0, max: 1000 },
|
||||
{ key: 'security_captcha_ip_10min', label: '同一IP 10分钟内最多 (次)', type: 'number', default: 20, min: 0, max: 500 },
|
||||
{ key: 'security_captcha_ip_day', label: '同一IP一天内最多 (次)', type: 'number', default: 50, min: 0, max: 2000, help: '如果您的服务器不能正确的获取用户真实IP地址,请关闭此项(0为不限制)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'harden',
|
||||
title: '安全加固',
|
||||
icon: 'fa fas fa-shield-alt',
|
||||
fields: [
|
||||
{ key: 'security_error_frequency_limit', label: '错误频率限制', type: 'switch', default: true, help: '用户进行密码、邮箱、手机号、验证码等验证时,验证错误次数超过5次后限制30秒验证一次,错误超过10次后,限制3分钟验证一次,有效防止暴力破解' },
|
||||
{ key: 'security_disabled_pingback', label: '防pingback攻击', type: 'switch', default: true },
|
||||
{ key: 'security_disabled_xmlrpc', label: '禁用XML-RPC', type: 'switch', default: true, help: '如果无需使用xmlrpc功能,建议直接删除WordPress目录下的xmlrpc.php文件' },
|
||||
{ key: 'security_user_disable_admin', label: '非管理员禁止进入后台', type: 'switch', default: true, help: '为了安全,如非必要请勿允许非管理员进入后台' },
|
||||
{ key: 'close_attachment_page', label: '非管理员屏蔽附件页面', type: 'switch', default: true, help: '非管理员前台不显示附件(图片、文件等)页面,直接显示404,能有效提高文件的安全性' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
export const systemSchema = {
|
||||
key: 'extensions.system',
|
||||
title: '系统工具',
|
||||
icon: 'fa fas fa-gavel',
|
||||
description: 'WordPress 优化、上传优化与编辑器显示的系统级开关。',
|
||||
defaults: {
|
||||
hide_admin_bar: true,
|
||||
remove_emoji: true,
|
||||
remove_open_sans: true,
|
||||
remove_more_wp_head: true,
|
||||
remove_dashboard_widgets: true,
|
||||
display_wp_update: true,
|
||||
newfilename: false,
|
||||
newfilename_type: 'time',
|
||||
split_upload_s: true,
|
||||
split_minimum_size: 20,
|
||||
disabled_large_thumbnail: true,
|
||||
admin_user_del_fields: true,
|
||||
admin_frontend_set: true,
|
||||
close_gutenberg: false,
|
||||
disabled_autoembed: true,
|
||||
mce_float_toolbar: ['pc_s', 'm_s'],
|
||||
time_ago_s: true,
|
||||
time_format: 'n月j日 H:i',
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'optimize',
|
||||
title: 'WordPress 优化',
|
||||
icon: 'fa fas fa-bolt',
|
||||
description: '精简WordPress自带的冗余功能,提升前台与后台的加载速度。',
|
||||
fields: [
|
||||
{ key: 'hide_admin_bar', label: '前台关闭顶部admin_bar', type: 'switch', default: true, help: '开启后前台不显示WordPress顶部黑条' },
|
||||
{ key: 'remove_emoji', label: '删除WordPress自带Emoji表情', type: 'switch', default: true },
|
||||
{ key: 'remove_open_sans', label: '禁用Google字体', type: 'switch', default: true },
|
||||
{ key: 'remove_more_wp_head', label: '清理多余的头部Meta标签', type: 'switch', default: true },
|
||||
{ key: 'remove_dashboard_widgets', label: 'WordPress 后台加速', type: 'switch', default: true, help: '移除仪表盘的站点健康和WordPress活动等不必要功能,并禁止检测浏览器和PHP版本(这两个非常耗时),优化后台加载速度' },
|
||||
{ key: 'display_wp_update', label: '禁止WordPress检测更新', type: 'switch', default: true, help: '开启后将禁用WordPress核心、插件和主题的自动更新及更新检测' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'upload',
|
||||
title: '上传优化',
|
||||
icon: 'fa fas fa-cloud-upload-alt',
|
||||
description: '上传文件的命名与缩略图裁剪优化,减少硬盘占用并提高上传效率。',
|
||||
fields: [
|
||||
{ key: 'split_upload_s', label: '大文件分片上传', type: 'switch', default: true, help: '分片上传大文件,优化大文件上传效率(支持断点续传)' },
|
||||
{ key: 'split_minimum_size', label: '分片上传最小文件', type: 'number', default: 20, min: 4, max: 100, unit: 'M', dependsOn: { key: 'split_upload_s', value: true }, help: '大于此大小的文件才会分片上传,请根据服务器性能合理设置;较大文件直接上传会比较耗时且无法突破系统上传大小限制,分片上传可以有效解决此问题;前端上传大小限制在「功能权限 → 上传权限」中设置。注意:能否顺利上传大文件还取决于服务器性能、带宽、CDN等因素,如果网站使用了OSS/COS等第三方云储存,则不建议在网站上传大文件' },
|
||||
{ key: 'newfilename', label: '上传文件自动重命名', type: 'switch', default: false, help: '上传文件时系统会查询重名文件,开启此功能后可减少重名文件,从而提高上传效率' },
|
||||
{
|
||||
key: 'newfilename_type',
|
||||
label: '重命名格式',
|
||||
type: 'radio',
|
||||
buttonStyle: false,
|
||||
default: 'time',
|
||||
dependsOn: { key: 'newfilename', value: true },
|
||||
options: [
|
||||
{ label: '随机字串符', value: 'random' },
|
||||
{ label: '时间+原文件名', value: 'time' },
|
||||
],
|
||||
},
|
||||
{ key: 'disabled_large_thumbnail', label: '禁止WP大尺寸缩略图裁剪', type: 'switch', default: true, help: '开启后将禁用WordPress自动裁剪medium_large、1536x1536、2048x2048的缩略图,可减少服务器硬盘压力,同时提高上传效率;如果您使用的插件必须要这些尺寸的缩略图,或使用了云储存的图片处理功能,则请关闭' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'editor',
|
||||
title: '编辑器与显示',
|
||||
icon: 'fa fas fa-edit',
|
||||
description: '编辑器模式、用户资料页与前台时间显示格式的定制。',
|
||||
fields: [
|
||||
{ key: 'admin_user_del_fields', label: '关闭后台用户编辑多余的选项', type: 'switch', default: true, help: '开启后在后台编辑用户资料时将不显示无用的多余选项' },
|
||||
{ key: 'admin_frontend_set', label: '前端页面编辑', type: 'switch', default: true, help: '管理员可在前台快速修改页面、文章参数' },
|
||||
{ key: 'close_gutenberg', label: '禁用古腾堡编辑器', type: 'switch', default: false, help: '后台文章编辑器将退回使用经典编辑器;禁用后主题的编辑器增强功能将不能使用,请酌情禁用!' },
|
||||
{ key: 'disabled_autoembed', label: '禁用Auto Embed', type: 'switch', default: true, help: 'Auto Embed是WordPress自动检测并嵌入链接的一种功能,主要适配海外媒体网站,国内几乎用不上,建议禁用,可大幅度提高页面渲染速度' },
|
||||
{
|
||||
key: 'mce_float_toolbar',
|
||||
label: '经典编辑器增强浮动工具栏',
|
||||
type: 'checkbox',
|
||||
default: ['pc_s', 'm_s'],
|
||||
options: [
|
||||
{ label: 'PC端开启', value: 'pc_s' },
|
||||
{ label: '移动端开启', value: 'm_s' },
|
||||
],
|
||||
help: '开启后前端编辑器会显示浮动的工具栏,更加方便书写',
|
||||
},
|
||||
{ key: 'time_ago_s', label: '倒计时显示', type: 'switch', default: true, help: '时间格式化为:X分钟前,X小时前,X天前....' },
|
||||
{ key: 'time_format', label: '自定义时间格式', type: 'input', default: 'n月j日 H:i', dependsOn: { key: 'time_ago_s', operator: 'falsy' }, help: '关闭倒计时显示后生效,时间格式接受标准PHP时间格式,请注意控制长度!' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -2,7 +2,7 @@ export const seoSchema = {
|
||||
key: 'global.seo',
|
||||
title: 'SEO 优化',
|
||||
icon: 'fa fas fa-search',
|
||||
description: '站点标题、描述、关键词与 AI SEO。AI 生成入口在文章「独立 SEO」框;连接配置在「扩展 → AI 连接配置」。优先使用 WordPress AI Connectors。',
|
||||
description: '站点标题、描述、关键词与 AI SEO。AI 生成入口在文章「独立 SEO」框,连接由 WordPress AI Connectors 提供。',
|
||||
defaults: {
|
||||
seo_enabled: true,
|
||||
seo_ai_enabled: false,
|
||||
@@ -13,10 +13,6 @@ export const seoSchema = {
|
||||
ai_seo_title_prompt: '',
|
||||
ai_seo_keywords_prompt: '',
|
||||
ai_seo_description_prompt: '',
|
||||
seo_baidu_push: false,
|
||||
seo_baidu_normal: false,
|
||||
seo_baidu_fast: false,
|
||||
seo_baidu_token: '',
|
||||
seo_image_optimize: true,
|
||||
seo_image_default: '',
|
||||
seo_image_always_default: false,
|
||||
@@ -48,7 +44,7 @@ export const seoSchema = {
|
||||
label: '启用 AI SEO 生成',
|
||||
type: 'switch',
|
||||
default: false,
|
||||
help: '开启后文章/分类「独立 SEO」框显示「AI 生成 SEO」按钮。需先配置 AI 连接(WP Connectors 或扩展 → AI 连接配置)。',
|
||||
help: '开启后文章/分类「独立 SEO」框显示「AI 生成 SEO」按钮。需先在 WordPress 配置 AI Connectors。',
|
||||
dependsOn: { key: 'seo_enabled', value: true },
|
||||
},
|
||||
{ key: 'seo_connector', label: '标题连接符', type: 'input', default: '-', placeholder: '例如:- 或 |', help: 'AI 生成标题时会用此连接符追加站点名。' },
|
||||
@@ -95,17 +91,6 @@ export const seoSchema = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'baidu',
|
||||
title: '百度推送',
|
||||
icon: 'fa fas fa-paper-plane',
|
||||
fields: [
|
||||
{ key: 'seo_baidu_push', label: '启用百度自动推送', type: 'switch', default: false },
|
||||
{ key: 'seo_baidu_normal', label: '普通收录推送', type: 'switch', default: false },
|
||||
{ key: 'seo_baidu_fast', label: '快速收录推送', type: 'switch', default: false },
|
||||
{ key: 'seo_baidu_token', label: '百度推送 Token', type: 'input', default: '', placeholder: '百度资源平台 Token' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'image',
|
||||
title: '图片 SEO',
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 售后策略 — 售后开关、申请时效、超时效提示与申请表单配置。
|
||||
*
|
||||
* 字段键、顺序、类型与默认值需与 src/Admin/Schema/SchemaPayment.php 的
|
||||
* after_sale 分区保持 1:1(tests/widget-system/after-sale-policy-contract.test.cjs 守护)。
|
||||
*/
|
||||
export const afterSaleSchema = {
|
||||
key: 'payment.after_sale',
|
||||
title: '售后策略',
|
||||
icon: 'fa fas fa-undo-alt',
|
||||
description: '售后功能开关、申请时效、超时效提示与申请表单(预置原因、申请说明、所需材料、图片上传)配置。',
|
||||
defaults: {
|
||||
after_sale_s: true,
|
||||
after_sale_time_limit: 7,
|
||||
after_sale_expired_text: '<div>抱歉,您的订单已超过售后申请时效</div>',
|
||||
after_sale_reasons: [
|
||||
{ t: '质量问题' },
|
||||
{ t: '假冒品牌' },
|
||||
{ t: '其他原因' },
|
||||
],
|
||||
after_sale_desc: '',
|
||||
after_sale_materials: [
|
||||
{ t: '联系邮箱' },
|
||||
{ t: '联系电话' },
|
||||
],
|
||||
after_sale_reason_placeholder: '必填,请您详细填写申请原因',
|
||||
after_sale_img_s: true,
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'main',
|
||||
title: '售后功能',
|
||||
icon: 'fa fas fa-undo-alt',
|
||||
fields: [
|
||||
{ key: 'after_sale_s', label: '售后功能', type: 'switch', default: true, help: '开启后用户可以申请售后;关闭后不再处理售后,前台「我的订单」不再显示申请售后按钮' },
|
||||
{ key: 'after_sale_time_limit', label: '申请售后时效(天)', type: 'number', default: 7, min: 0, max: 3650, help: '订单支付后多少天内可以申请售后,超过时效将无法申请;0 表示不限制' },
|
||||
{ key: 'after_sale_expired_text', label: '超过时效提示', type: 'textarea', sanitize_type: 'html', default: '<div>抱歉,您的订单已超过售后申请时效</div>', rows: 3, help: '订单超过售后时效后,用户点击申请售后时弹窗提示的内容(支持HTML代码,请注意代码规范)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'form',
|
||||
title: '申请表单',
|
||||
icon: 'fa fas fa-file-alt',
|
||||
fields: [
|
||||
{
|
||||
key: 'after_sale_reasons',
|
||||
label: '预置原因',
|
||||
type: 'group',
|
||||
button_title: '添加原因',
|
||||
help: '用户申请售后时可选择的原因',
|
||||
default: [
|
||||
{ t: '质量问题' },
|
||||
{ t: '假冒品牌' },
|
||||
{ t: '其他原因' },
|
||||
],
|
||||
fields: [
|
||||
{ key: 't', label: '原因', type: 'input', default: '' },
|
||||
],
|
||||
},
|
||||
{ key: 'after_sale_desc', label: '申请售后说明', type: 'textarea', sanitize_type: 'html', default: '', rows: 5, help: '用户申请售后时显示的内容(支持HTML代码,请注意代码规范)' },
|
||||
{
|
||||
key: 'after_sale_materials',
|
||||
label: '申请所需材料',
|
||||
type: 'group',
|
||||
button_title: '添加材料',
|
||||
help: '用户提交售后申请时需要提供的资料,如联系邮箱、联系电话;此处的信息收集,仅用于售后联系与审核',
|
||||
default: [
|
||||
{ t: '联系邮箱' },
|
||||
{ t: '联系电话' },
|
||||
],
|
||||
fields: [
|
||||
{ key: 't', label: '资料项', type: 'input', default: '' },
|
||||
],
|
||||
},
|
||||
{ key: 'after_sale_reason_placeholder', label: '申请说明占位文案', type: 'input', default: '必填,请您详细填写申请原因', help: '前台申请售后弹窗中「申请说明」输入框的提示文字' },
|
||||
{ key: 'after_sale_img_s', label: '申请售后可上传图片', type: 'switch', default: true, help: '开启后申请售后弹窗中出现图片上传入口(最多上传3张)' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import { revenueSchema } from './revenue.js'
|
||||
import { affiliateSchema } from './affiliate.js'
|
||||
import { gatewaySchema } from './gateway.js'
|
||||
import { withdrawalSchema } from './withdrawal.js'
|
||||
import { afterSaleSchema } from './afterSale.js'
|
||||
|
||||
export const paymentSchemas = {
|
||||
knowledge: knowledgeSchema,
|
||||
@@ -18,4 +19,5 @@ export const paymentSchemas = {
|
||||
affiliate: affiliateSchema,
|
||||
gateway: gatewaySchema,
|
||||
withdrawal: withdrawalSchema,
|
||||
after_sale: afterSaleSchema,
|
||||
}
|
||||
@@ -25,13 +25,10 @@ export const knowledgeSchema = {
|
||||
payment_show_pay_time: true,
|
||||
payment_order_status_pending: '待支付',
|
||||
payment_order_status_paid: '已支付',
|
||||
payment_order_status_refunding: '退款中',
|
||||
payment_order_status_refunded: '已退款',
|
||||
payment_order_status_closed: '已关闭',
|
||||
pay_service: [],
|
||||
down_alone_page: false,
|
||||
down_user_log: 50,
|
||||
down_post_log: 50,
|
||||
down_remote_to_local_s: true,
|
||||
down_remote_to_local_size: 2048,
|
||||
down_remote_to_local_ext: 'zip,rar,7z,text,png,jpg,jpeg,gif,mp3,mp4,avi,wmv,flv,swf,doc,docx,xls,xlsx,ppt,pptx,pdf',
|
||||
@@ -124,7 +121,7 @@ export const knowledgeSchema = {
|
||||
{ key: 'pay_mark', label: '付费标记', type: 'input', default: '付费' },
|
||||
{ key: 'payment_show_ip', label: '显示用户IP', type: 'switch', default: false },
|
||||
{ key: 'payment_show_device', label: '显示设备信息', type: 'switch', default: false },
|
||||
{ key: 'payment_show_order_no', label: '显示流水号', type: 'switch', default: true },
|
||||
{ key: 'payment_show_order_no', label: '显示订单号', type: 'switch', default: true },
|
||||
{ key: 'payment_show_create_time', label: '显示创建时间', type: 'switch', default: true },
|
||||
{ key: 'payment_show_pay_time', label: '显示支付时间', type: 'switch', default: true },
|
||||
],
|
||||
@@ -136,7 +133,6 @@ export const knowledgeSchema = {
|
||||
fields: [
|
||||
{ key: 'payment_order_status_pending', label: '待支付状态文案', type: 'input', default: '待支付' },
|
||||
{ key: 'payment_order_status_paid', label: '已支付状态文案', type: 'input', default: '已支付' },
|
||||
{ key: 'payment_order_status_refunding', label: '退款中状态文案', type: 'input', default: '退款中' },
|
||||
{ key: 'payment_order_status_refunded', label: '已退款状态文案', type: 'input', default: '已退款' },
|
||||
{ key: 'payment_order_status_closed', label: '已关闭状态文案', type: 'input', default: '已关闭' },
|
||||
],
|
||||
@@ -174,8 +170,6 @@ export const knowledgeSchema = {
|
||||
title: '付费下载',
|
||||
fields: [
|
||||
{ key: 'down_alone_page', label: '独立下载页面', type: 'switch', default: false, help: '开启后,付费资源的下载链接会跳转独立页面显示' },
|
||||
{ key: 'down_user_log', label: '保存用户下载记录(条)', type: 'number', default: 50, min: 0, help: '填0则不保存,注意记录量太大可能会影响网站性能' },
|
||||
{ key: 'down_post_log', label: '保存文章下载记录(条)', type: 'number', default: 50, min: 0, help: '填0则不保存,注意记录量太大可能会影响网站性能' },
|
||||
{ key: 'down_remote_to_local_s', label: '远程文件转本地文件下载', type: 'switch', default: true, help: '当下载地址为外链文件时,先将远程文件下载到本地服务器,再提供给用户下载,可完全隐藏远程下载地址' },
|
||||
{ key: 'down_remote_to_local_size', label: '文件大小限制(Kb)', type: 'number', default: 2048, min: 0 },
|
||||
{ key: 'down_remote_to_local_ext', label: '文件格式限制', type: 'input', default: 'zip,rar,7z,text,png,jpg,jpeg,gif,mp3,mp4,avi,wmv,flv,swf,doc,docx,xls,xlsx,ppt,pptx,pdf', help: '限制使用此功能的文件格式,多个用英文逗号隔开' },
|
||||
|
||||
@@ -111,7 +111,7 @@ export const rechargeSchema = {
|
||||
{ key: 'points_pass_exchange_desc', label: '卡密兑换积分说明', type: 'textarea', default: '', rows: 2, dependsOn: { key: 'points_pass_exchange_s', value: true }, help: '用户使用卡密兑换积分时显示的内容,建议为提醒事项、兑换协议等,支持HTML代码' },
|
||||
{ key: 'points_pass_exchange_only_password', label: '积分卡密单密码模式', type: 'switch', default: false, dependsOn: { key: 'points_pass_exchange_s', value: true }, help: '积分卡密仅需输入密码,不需要卡号' },
|
||||
{ key: 'pay_vip_pass_charge_s', label: '启用卡密兑换会员功能', type: 'switch', default: false, help: '开启后用户可在会员购买/续费弹窗选择「卡密」渠道,使用会员卡密直接兑换会员' },
|
||||
{ key: 'pay_vip_pass_charge_desc', label: '卡密兑换会员说明', type: 'textarea', default: '', rows: 2, dependsOn: { key: 'pay_vip_pass_charge_s', value: true }, help: '用户使用卡密兑换会员时显示的内容,建议为提醒事项、兑换协议等,支持HTML代码' },
|
||||
{ key: 'pay_vip_pass_charge_desc', label: '卡密兑换会员说明', type: 'textarea', sanitize_type: 'html', default: '', rows: 2, dependsOn: { key: 'pay_vip_pass_charge_s', value: true }, help: '用户使用卡密兑换会员时显示的内容,建议为提醒事项、兑换协议等,支持HTML代码' },
|
||||
{ key: 'pay_vip_pass_charge_only_password', label: '会员卡密单密码模式', type: 'switch', default: false, dependsOn: { key: 'pay_vip_pass_charge_s', value: true }, help: '会员卡密仅需输入密码,不需要卡号;默认关闭,会员卡需输入卡号和密码(与其他卡密兑换一致)' },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,27 +1,127 @@
|
||||
/**
|
||||
* VIP会员 — 参考主题结构:会员等级、会员权益(普通/一级/二级 tab)、
|
||||
* 会员购买选项(一级/二级/续费/升级 tab)、卡密兑换与积分兑换。
|
||||
* VIP会员 — 结构 1:1 对照参考主题 Zibll:会员等级(开关)、会员权益
|
||||
* (普通/一级/二级 tab)、会员购买选项(一级/二级/续费/升级 tab)、
|
||||
* 积分兑换会员与界面文案。
|
||||
*
|
||||
* 会员商品 / 续费商品 / 升级价格 / 积分兑换明细均为结构化 group 或
|
||||
* accordion,不再使用 `|` 分隔的 textarea。等级 Tab 标题通过 titleKey
|
||||
* 绑定「会员显示名称」,改名即改 Tab 名。
|
||||
*/
|
||||
|
||||
const VIP_EQUITY_DEFAULT = '<li>全站资源折扣购买</li><li>部分内容免费阅读</li><li>一对一技术指导</li><li>VIP用户专属QQ群</li>';
|
||||
|
||||
const vipTagPalette = [
|
||||
{ label: '#f74b3d', value: 'b-red' },
|
||||
{ label: '#f3920a', value: 'b-yellow' },
|
||||
{ label: '#08c4c1', value: 'b-cyan' },
|
||||
{ label: '#0a8cf3', value: 'b-blue' },
|
||||
{ label: '#1fd05a', value: 'b-green' },
|
||||
{ label: '#c133f5', value: 'b-purple' },
|
||||
{ label: '#121517', value: 'b-black' },
|
||||
{ label: 'linear-gradient(135deg, #ffbeb4 10%, #f61a1a 100%)', value: 'jb-red' },
|
||||
{ label: 'linear-gradient(135deg, #ff5e7f 30%, #ff967e 100%)', value: 'jb-pink' },
|
||||
{ label: 'linear-gradient(135deg, #ffd6b2 10%, #ff651c 100%)', value: 'jb-yellow' },
|
||||
{ label: 'linear-gradient(140deg, #039ab3 10%, #58dbcf 90%)', value: 'jb-cyan' },
|
||||
{ label: 'linear-gradient(135deg, #b6e6ff 10%, #198aff 100%)', value: 'jb-blue' },
|
||||
{ label: 'linear-gradient(135deg, #ccffcd 10%, #52bb51 100%)', value: 'jb-green' },
|
||||
{ label: 'linear-gradient(135deg, #fec2ff 10%, #d000de 100%)', value: 'jb-purple' },
|
||||
{ label: 'linear-gradient(25deg, #eab869 10%, #fbecd4 60%, #ffe0ae 100%)', value: 'jb-vip1' },
|
||||
{ label: 'linear-gradient(317deg, #4d4c4c 30%, #878787 70%, #5f5c5c 100%)', value: 'jb-vip2' },
|
||||
];
|
||||
|
||||
const vipUnitOptions = [
|
||||
{ label: '天', value: 'day' },
|
||||
{ label: '月', value: 'month' },
|
||||
];
|
||||
|
||||
// 会员商品/续费商品的手风琴配置:默认收起,标题行显示价格与时长摘要。
|
||||
const vipProductGroupOptions = {
|
||||
collapsible: true,
|
||||
defaultCollapsed: true,
|
||||
listPreview: { prefix: '价格:', titleKey: 'price', descKey: 'time', descUnitKey: 'unit' },
|
||||
};
|
||||
|
||||
// 积分兑换明细的手风琴配置:默认收起,标题行显示积分与时长摘要。
|
||||
const vipPointsGroupOptions = {
|
||||
collapsible: true,
|
||||
defaultCollapsed: true,
|
||||
listPreview: { prefix: '积分:', titleKey: 'points', descKey: 'time', descUnitKey: 'unit' },
|
||||
};
|
||||
|
||||
/**
|
||||
* 会员商品字段集(参考主题 CFS_Module::vip_product)。
|
||||
*/
|
||||
function vipProductFields() {
|
||||
return [
|
||||
{ key: 'price', label: '执行价', type: 'number', sanitize_type: 'money_decimal', default: '699.00', min: 0, suffix: '元', help: '会员的实际购买价格' },
|
||||
{ key: 'show_price', label: '原价', type: 'number', sanitize_type: 'money_decimal', default: '999.00', min: 0, suffix: '元', help: '显示在执行价格前面,并划掉' },
|
||||
{ key: 'tag', label: '促销标签', type: 'textarea', sanitize_type: 'html', default: '', rows: 1, help: '支持HTML,请注意控制长度' },
|
||||
{ key: 'tag_class', label: '标签颜色', type: 'palette', default: 'jb-yellow', dependsOn: { key: 'tag', operator: 'not_empty' }, options: vipTagPalette },
|
||||
{ key: 'time', label: '会员有效时间', type: 'spinner', default: 3, min: 0, max: 3600, step: 1, help: '开通会员的时长。填0则为永久会员' },
|
||||
{ key: 'unit', label: '时长单位', type: 'radio', default: 'month', inline: true, dependsOn: { key: 'time', operator: 'truthy' }, options: vipUnitOptions },
|
||||
{ key: '_time_permanent_hint', label: '', type: 'alert', alertType: 'success', ui_only: true, dependsOn: { key: 'time', operator: 'eq', value: 0 }, content: '会员有效时间已设置为:永久会员' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 会员等级 Tab 字段集(参考主题 CFS_Module::vip_tab,显示名称并入 Tab)。
|
||||
*/
|
||||
function vipLevelTabFields(level) {
|
||||
const dep = { key: `pay_user_vip_${level}_s`, value: true };
|
||||
return [
|
||||
{ key: `pay_user_vip_${level}_name`, label: '会员显示名称', type: 'input', sanitize_type: 'html', default: level === 1 ? '黄金会员' : '钻石会员', dependsOn: dep, help: '支持HTML,请注意控制长度' },
|
||||
{ key: `pay_user_vip_${level}_desc`, label: '一句话简介', type: 'input', sanitize_type: 'html', default: '尊享专属特权', dependsOn: dep, help: '支持HTML' },
|
||||
{ key: `pay_user_vip_${level}_equity`, label: '会员权益 HTML', type: 'textarea', sanitize_type: 'html', default: VIP_EQUITY_DEFAULT, rows: 4, dependsOn: dep, help: '支持安全HTML,建议使用 <li> 列表项' },
|
||||
{ key: `vip_${level}_product_s`, label: '购买会员', type: 'switch', default: true, dependsOn: dep, help: '关闭后则不能付费购买会员,但可通过积分兑换会员,请确保已开启积分以及积分兑换会员功能' },
|
||||
{
|
||||
key: `vip_${level}_product`,
|
||||
label: '会员商品',
|
||||
type: 'group',
|
||||
max: 8,
|
||||
button_title: '添加会员商品',
|
||||
default: [
|
||||
{ price: 99, show_price: 199, tag: '<i class="fa fas fa-fw fa-bolt"></i> 限时特惠', time: 3, unit: 'month' },
|
||||
{ price: 199, show_price: 299, tag: '<i class="fa fas fa-fw fa-bolt"></i> 站长推荐', time: 6, unit: 'month' },
|
||||
],
|
||||
...vipProductGroupOptions,
|
||||
dependsOn: { all: [dep, { key: `vip_${level}_product_s`, value: true }] },
|
||||
help: '会员购买价格配置:执行价、原价、促销标签、标签颜色与会员有效时间',
|
||||
fields: vipProductFields(),
|
||||
},
|
||||
{ key: `vip_${level}_img_icon`, label: '会员图标', type: 'upload', default: '', dependsOn: dep, help: `留空使用内置 vip-${level}.svg 图标;建议尺寸300x300` },
|
||||
];
|
||||
}
|
||||
|
||||
export const vipSchema = {
|
||||
key: 'payment.vip',
|
||||
title: 'VIP会员',
|
||||
icon: 'fa fas fa-gem',
|
||||
description: '双级会员:等级名称、权益(下载次数与限速)、套餐、续费/升级与积分兑换。',
|
||||
description: '双级会员:等级开关、权益(下载次数与限速)、会员商品、续费/升级与积分兑换。',
|
||||
defaults: {
|
||||
nav_pay_vip: true,
|
||||
nav_user_pay_vip: true,
|
||||
pay_user_vip_1_s: true,
|
||||
pay_user_vip_1_name: '一级会员',
|
||||
pay_user_vip_1_desc: '尊享一级会员特权',
|
||||
pay_user_vip_1_equity: '<li>免费下载部分资源</li><li>会员专属身份标识</li><li>付费内容会员价</li>',
|
||||
pay_user_vip_1_name: '黄金会员',
|
||||
pay_user_vip_1_desc: '尊享专属特权',
|
||||
pay_user_vip_1_equity: VIP_EQUITY_DEFAULT,
|
||||
vip_1_product_s: true,
|
||||
vip_1_product: [
|
||||
{ price: 99, show_price: 199, tag: '<i class="fa fas fa-fw fa-bolt"></i> 限时特惠', time: 3, unit: 'month' },
|
||||
{ price: 199, show_price: 299, tag: '<i class="fa fas fa-fw fa-bolt"></i> 站长推荐', time: 6, unit: 'month' },
|
||||
],
|
||||
vip_1_img_icon: '',
|
||||
pay_user_vip_2_s: true,
|
||||
pay_user_vip_2_name: '二级会员',
|
||||
pay_user_vip_2_desc: '尊享二级会员特权',
|
||||
pay_user_vip_2_equity: '<li>更高免费下载额度</li><li>更高等级身份标识</li><li>更多付费内容会员价</li>',
|
||||
pay_user_vip_2_name: '钻石会员',
|
||||
pay_user_vip_2_desc: '尊享专属特权',
|
||||
pay_user_vip_2_equity: VIP_EQUITY_DEFAULT,
|
||||
vip_2_product_s: true,
|
||||
vip_2_product: [
|
||||
{ price: 99, show_price: 199, tag: '<i class="fa fas fa-fw fa-bolt"></i> 限时特惠', time: 3, unit: 'month' },
|
||||
{ price: 199, show_price: 299, tag: '<i class="fa fas fa-fw fa-bolt"></i> 站长推荐', time: 6, unit: 'month' },
|
||||
],
|
||||
vip_2_img_icon: '',
|
||||
pay_user_vip_desc: '',
|
||||
pay_user_vip_more: '',
|
||||
pay_user_vip_desc: '开通VIP会员,享受会员专属折扣以及多项特权',
|
||||
pay_user_vip_more: '<div>购买后不支持退款</div><div>VIP权益仅适用于本站</div><div>欢迎与站长联系</div>',
|
||||
pay_download_limit: 0,
|
||||
pay_download_speed: 0,
|
||||
pay_download_limit_vip_1: 0,
|
||||
@@ -29,19 +129,45 @@ export const vipSchema = {
|
||||
pay_download_limit_vip_2: 0,
|
||||
pay_download_speed_vip_2: 0,
|
||||
vip_1_product_s: true,
|
||||
payment_vip_1_products: '30|59|1|month|限时|月卡\n98|199|3|month|特惠|季卡\n365|599|12|month|超值|年卡\n999|1299|0|day|永久|永久卡',
|
||||
vip_2_product_s: true,
|
||||
payment_vip_2_products: '58|99|1|month|限时|月卡\n168|299|3|month|特惠|季卡\n598|999|12|month|超值|年卡\n1299|1999|0|day|永久|永久卡',
|
||||
vip_renew: true,
|
||||
vip_renew_desc: '',
|
||||
vip_renew_desc: '立即续费会员,畅享VIP权益',
|
||||
vip_renew_price_type: 'discount',
|
||||
vip_renew_discount: 8,
|
||||
vip_renew_reduce: '20.00',
|
||||
vip_1_renew_product: [
|
||||
{ price: 69, show_price: 199, tag: '<i class="fa fas fa-fw fa-bolt"></i> 限时特惠', time: 3, unit: 'month' },
|
||||
{ price: 169, show_price: 299, tag: '<i class="fa fas fa-fw fa-bolt"></i> 站长推荐', time: 6, unit: 'month' },
|
||||
],
|
||||
vip_2_renew_product: [
|
||||
{ price: 269, show_price: 599, tag: '<i class="fa fas fa-fw fa-bolt"></i> 限时特惠', time: 3, unit: 'month' },
|
||||
{ price: 369, show_price: 899, tag: '<i class="fa fas fa-fw fa-bolt"></i> 站长推荐', time: 6, unit: 'month' },
|
||||
],
|
||||
vip_2_product_s: true,
|
||||
vip_renew: true,
|
||||
vip_renew_discount: 8,
|
||||
vip_renew_reduce: '20.00',
|
||||
vip_upgrade: true,
|
||||
vip_upgrade_desc: '',
|
||||
vip_upgrade_product: 'unit|1|按剩余天数升级\njump|199|升级永久二级会员\npermanent|299|永久一级升永久二级',
|
||||
vip_upgrade_desc: '升级VIP会员,享更多会员权益',
|
||||
vip_upgrade_product: {
|
||||
month_to_month_s: true,
|
||||
unit_price: '10.00',
|
||||
unit_tag: '<i class="fa fas fa-fw fa-bolt"></i> 站长推荐',
|
||||
unit_tag_class: 'jb-yellow',
|
||||
jump_s: false,
|
||||
jump_price: '199.00',
|
||||
jump_show_price: '299.00',
|
||||
jump_tag: '<i class="fa fas fa-fw fa-bolt"></i> 升级特惠',
|
||||
jump_tag_class: 'jb-red',
|
||||
permanent_price: '199.00',
|
||||
permanent_show_price: '299.00',
|
||||
permanent_tag: '<i class="fa fas fa-fw fa-bolt"></i> 升级特惠',
|
||||
permanent_tag_class: 'jb-red',
|
||||
},
|
||||
pay_vip_points_exchange_s: false,
|
||||
payment_vip_points_packages: '1|500|30|积分月卡\n1|5000|0|积分永久',
|
||||
pay_vip_points_exchange_product: [
|
||||
{ points: 500, level: 1, time: 30, unit: 'day' },
|
||||
{ points: 5000, level: 1, time: 'Permanent' },
|
||||
],
|
||||
pay_vip_points_exchange_desc: '',
|
||||
},
|
||||
groups: [
|
||||
@@ -67,17 +193,16 @@ export const vipSchema = {
|
||||
{ key: 'nav_pay_vip', label: '导航栏购买按钮', type: 'switch', default: true, help: '在顶部导航栏显示开通会员按钮;请注意顶部导航的整体宽度。' },
|
||||
{ key: 'nav_user_pay_vip', label: '用户框购买按钮', type: 'switch', default: true, help: '在导航栏用户框内显示开通会员按钮。' },
|
||||
{ key: 'pay_user_vip_1_s', label: '启用一级会员', type: 'switch', default: true },
|
||||
{ key: 'pay_user_vip_1_name', label: '一级会员显示名称', type: 'input', default: '一级会员', dependsOn: { key: 'pay_user_vip_1_s', value: true } },
|
||||
{ key: 'pay_user_vip_1_desc', label: '一级会员一句话简介', type: 'input', default: '尊享一级会员特权', dependsOn: { key: 'pay_user_vip_1_s', value: true } },
|
||||
{ key: 'pay_user_vip_1_equity', label: '一级会员权益 HTML', type: 'textarea', default: '<li>免费下载部分资源</li><li>会员专属身份标识</li><li>付费内容会员价</li>', dependsOn: { key: 'pay_user_vip_1_s', value: true }, help: '建议使用 <li> 列表项' },
|
||||
{ key: 'vip_1_img_icon', label: '一级会员图标 URL', type: 'input', default: '', dependsOn: { key: 'pay_user_vip_1_s', value: true }, help: '留空使用内置 vip-1.svg 图标' },
|
||||
{
|
||||
key: 'vip_level_order_notice',
|
||||
label: '',
|
||||
type: 'notice',
|
||||
ui_only: true,
|
||||
alertType: 'danger',
|
||||
dependsOn: { all: [{ key: 'pay_user_vip_2_s', value: true }, { key: 'pay_user_vip_1_s', operator: 'falsy' }] },
|
||||
content: '<div style="text-align:center"><b>必须先开启一级会员后才能开启二级会员!</b></div>',
|
||||
},
|
||||
{ key: 'pay_user_vip_2_s', label: '启用二级会员', type: 'switch', default: true },
|
||||
{ key: 'pay_user_vip_2_name', label: '二级会员显示名称', type: 'input', default: '二级会员', dependsOn: { key: 'pay_user_vip_2_s', value: true } },
|
||||
{ key: 'pay_user_vip_2_desc', label: '二级会员一句话简介', type: 'input', default: '尊享二级会员特权', dependsOn: { key: 'pay_user_vip_2_s', value: true } },
|
||||
{ key: 'pay_user_vip_2_equity', label: '二级会员权益 HTML', type: 'textarea', default: '<li>更高免费下载额度</li><li>更高等级身份标识</li><li>更多付费内容会员价</li>', dependsOn: { key: 'pay_user_vip_2_s', value: true } },
|
||||
{ key: 'vip_2_img_icon', label: '二级会员图标 URL', type: 'input', default: '', dependsOn: { key: 'pay_user_vip_2_s', value: true }, help: '留空使用内置 vip-2.svg 图标' },
|
||||
{ key: 'pay_user_vip_desc', label: '弹窗顶部会员说明', type: 'textarea', default: '' },
|
||||
{ key: 'pay_user_vip_more', label: '弹窗底部补充说明 HTML', type: 'textarea', default: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -101,6 +226,8 @@ export const vipSchema = {
|
||||
{
|
||||
key: 'vip1',
|
||||
title: '一级会员',
|
||||
titleKey: 'pay_user_vip_1_name',
|
||||
titleFallback: '一级会员',
|
||||
fields: [
|
||||
{ key: 'pay_download_limit_vip_1', label: '付费资源:免费资源每日可下载(个)', type: 'number', default: 0, min: 0, max: 9999, dependsOn: { key: 'pay_user_vip_1_s', value: true }, help: '一级会员每日最多可下载几个免费资源(为0则不限制)' },
|
||||
{ key: 'pay_download_speed_vip_1', label: '付费资源:下载限速(KB/s)', type: 'number', default: 0, min: 0, dependsOn: { key: 'pay_user_vip_1_s', value: true }, help: '付费下载限制最大下载速度(为0则不限制);只对本地服务器文件有效' },
|
||||
@@ -109,6 +236,8 @@ export const vipSchema = {
|
||||
{
|
||||
key: 'vip2',
|
||||
title: '二级会员',
|
||||
titleKey: 'pay_user_vip_2_name',
|
||||
titleFallback: '二级会员',
|
||||
fields: [
|
||||
{ key: 'pay_download_limit_vip_2', label: '付费资源:免费资源每日可下载(个)', type: 'number', default: 0, min: 0, max: 9999, dependsOn: { key: 'pay_user_vip_2_s', value: true }, help: '二级会员每日最多可下载几个免费资源(为0则不限制)' },
|
||||
{ key: 'pay_download_speed_vip_2', label: '付费资源:下载限速(KB/s)', type: 'number', default: 0, min: 0, dependsOn: { key: 'pay_user_vip_2_s', value: true }, help: '付费下载限制最大下载速度(为0则不限制);只对本地服务器文件有效' },
|
||||
@@ -131,53 +260,111 @@ export const vipSchema = {
|
||||
{
|
||||
key: 'vip1',
|
||||
title: '一级会员',
|
||||
titleKey: 'pay_user_vip_1_name',
|
||||
titleFallback: '一级会员',
|
||||
fields: [
|
||||
{ key: 'vip_1_product_s', label: '允许购买一级会员', type: 'switch', default: true, dependsOn: { key: 'pay_user_vip_1_s', value: true } },
|
||||
{
|
||||
key: 'payment_vip_1_products',
|
||||
label: '一级会员套餐',
|
||||
type: 'textarea',
|
||||
default: '30|59|1|month|限时|月卡\n98|199|3|month|特惠|季卡\n365|599|12|month|超值|年卡\n999|1299|0|day|永久|永久卡',
|
||||
dependsOn: { key: 'vip_1_product_s', value: true },
|
||||
help: '每行:执行价|原价|时长|单位(day|month)|标签|标题;时长 0=永久',
|
||||
},
|
||||
...vipLevelTabFields(1),
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'vip2',
|
||||
title: '二级会员',
|
||||
titleKey: 'pay_user_vip_2_name',
|
||||
titleFallback: '二级会员',
|
||||
fields: [
|
||||
{ key: 'vip_2_product_s', label: '允许购买二级会员', type: 'switch', default: true, dependsOn: { key: 'pay_user_vip_2_s', value: true } },
|
||||
{
|
||||
key: 'payment_vip_2_products',
|
||||
label: '二级会员套餐',
|
||||
type: 'textarea',
|
||||
default: '58|99|1|month|限时|月卡\n168|299|3|month|特惠|季卡\n598|999|12|month|超值|年卡\n1299|1999|0|day|永久|永久卡',
|
||||
dependsOn: { key: 'vip_2_product_s', value: true },
|
||||
help: '每行:执行价|原价|时长|单位(day|month)|标签|标题;时长 0=永久',
|
||||
},
|
||||
...vipLevelTabFields(2),
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'renew',
|
||||
title: '会员续费',
|
||||
fields: [
|
||||
{ key: 'vip_renew', label: '允许续费', type: 'switch', default: true, help: '关闭后则不能续费,只能会员到期之后再购买。永久会员不需要续费' },
|
||||
{ key: 'vip_renew_desc', label: '续费说明', type: 'textarea', default: '', dependsOn: { key: 'vip_renew', value: true } },
|
||||
{
|
||||
key: 'vip_renew',
|
||||
label: '允许续费',
|
||||
type: 'switch',
|
||||
default: true,
|
||||
help: '关闭后则不能续费,只能会员到期之后再购买。永久会员不需要续费',
|
||||
},
|
||||
{
|
||||
key: 'vip_renew_desc',
|
||||
label: '续费介绍',
|
||||
type: 'input',
|
||||
sanitize_type: 'html',
|
||||
default: '立即续费会员,畅享VIP权益',
|
||||
dependsOn: { key: 'vip_renew', value: true },
|
||||
help: '续费会员的一句话简介,支持HTML',
|
||||
},
|
||||
{
|
||||
key: 'vip_renew_price_type',
|
||||
label: '续费价格类型',
|
||||
type: 'radio',
|
||||
type: 'select',
|
||||
default: 'discount',
|
||||
dependsOn: { key: 'vip_renew', value: true },
|
||||
options: [
|
||||
{ label: '相同价格', value: 'same' },
|
||||
{ label: '折扣价格', value: 'discount' },
|
||||
{ label: '优惠立减', value: 'reduce' },
|
||||
{ label: '自定义商品', value: 'customize' },
|
||||
],
|
||||
help: '自定义商品时,按下方「一级会员续费」「二级会员续费」的商品选项计价',
|
||||
},
|
||||
{
|
||||
key: 'vip_renew_discount',
|
||||
label: '续费折扣',
|
||||
type: 'slider',
|
||||
sanitize_type: 'float',
|
||||
default: 8,
|
||||
min: 0.1,
|
||||
max: 9.9,
|
||||
step: 0.1,
|
||||
unit: '折',
|
||||
dependsOn: { key: 'vip_renew_price_type', value: 'discount' },
|
||||
help: '在会员商品基础上打几折,如 8 = 八折',
|
||||
},
|
||||
{
|
||||
key: 'vip_renew_reduce',
|
||||
label: '续费减免金额',
|
||||
type: 'spinner',
|
||||
sanitize_type: 'money_decimal',
|
||||
default: '20.00',
|
||||
min: 0,
|
||||
max: 500,
|
||||
step: 5,
|
||||
unit: '元',
|
||||
dependsOn: { key: 'vip_renew_price_type', value: 'reduce' },
|
||||
help: '在会员商品基础上优惠的金额,优惠后总金额不能为0元',
|
||||
},
|
||||
{
|
||||
key: 'vip_1_renew_product',
|
||||
label: '一级会员续费',
|
||||
type: 'group',
|
||||
max: 8,
|
||||
button_title: '添加续费商品',
|
||||
default: [
|
||||
{ price: 69, show_price: 199, tag: '<i class="fa fas fa-fw fa-bolt"></i> 限时特惠', time: 3, unit: 'month' },
|
||||
{ price: 169, show_price: 299, tag: '<i class="fa fas fa-fw fa-bolt"></i> 站长推荐', time: 6, unit: 'month' },
|
||||
],
|
||||
dependsOn: { key: 'vip_renew_price_type', value: 'customize' },
|
||||
help: '一级会员续费的商品选项',
|
||||
...vipProductGroupOptions,
|
||||
fields: vipProductFields(),
|
||||
},
|
||||
{
|
||||
key: 'vip_2_renew_product',
|
||||
label: '二级会员续费',
|
||||
type: 'group',
|
||||
max: 8,
|
||||
button_title: '添加续费商品',
|
||||
default: [
|
||||
{ price: 269, show_price: 599, tag: '<i class="fa fas fa-fw fa-bolt"></i> 限时特惠', time: 3, unit: 'month' },
|
||||
{ price: 369, show_price: 899, tag: '<i class="fa fas fa-fw fa-bolt"></i> 站长推荐', time: 6, unit: 'month' },
|
||||
],
|
||||
dependsOn: { key: 'vip_renew_price_type', value: 'customize' },
|
||||
help: '二级会员续费的商品选项',
|
||||
...vipProductGroupOptions,
|
||||
fields: vipProductFields(),
|
||||
},
|
||||
{ key: 'vip_renew_discount', label: '续费折扣(折,如 8=八折)', type: 'number', default: 8, min: 1, max: 100, dependsOn: { key: 'vip_renew_price_type', value: 'discount' } },
|
||||
{ key: 'vip_renew_reduce', label: '续费减免金额', type: 'input', sanitize_type: 'money_decimal', default: '20.00', min: 0, max: 9999, dependsOn: { key: 'vip_renew_price_type', value: 'reduce' } },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -185,14 +372,50 @@ export const vipSchema = {
|
||||
title: '会员升级',
|
||||
fields: [
|
||||
{ key: 'vip_upgrade', label: '允许升级', type: 'switch', default: true },
|
||||
{ key: 'vip_upgrade_desc', label: '升级说明', type: 'textarea', default: '', dependsOn: { key: 'vip_upgrade', value: true } },
|
||||
{ key: 'vip_upgrade_desc', label: '升级介绍', type: 'input', sanitize_type: 'html', default: '升级VIP会员,享更多会员权益', dependsOn: { key: 'vip_upgrade', value: true }, help: '升级会员的一句话简介,支持HTML' },
|
||||
{
|
||||
key: 'vip_upgrade_product',
|
||||
label: '升级产品',
|
||||
type: 'textarea',
|
||||
default: 'unit|1|按剩余天数升级\njump|199|升级永久二级会员\npermanent|299|永久一级升永久二级',
|
||||
label: '升级价格',
|
||||
type: 'accordion',
|
||||
default: {
|
||||
month_to_month_s: true,
|
||||
unit_price: '10.00',
|
||||
unit_tag: '<i class="fa fas fa-fw fa-bolt"></i> 站长推荐',
|
||||
unit_tag_class: 'jb-yellow',
|
||||
jump_s: false,
|
||||
jump_price: '199.00',
|
||||
jump_show_price: '299.00',
|
||||
jump_tag: '<i class="fa fas fa-fw fa-bolt"></i> 升级特惠',
|
||||
jump_tag_class: 'jb-red',
|
||||
permanent_price: '199.00',
|
||||
permanent_show_price: '299.00',
|
||||
permanent_tag: '<i class="fa fas fa-fw fa-bolt"></i> 升级特惠',
|
||||
permanent_tag_class: 'jb-red',
|
||||
},
|
||||
dependsOn: { key: 'vip_upgrade', value: true },
|
||||
help: '每行一条。永久VIP1升级:permanent|价格|标题;按剩余天:unit|每日单价|标题;限时跳转永久:jump|价格|标题',
|
||||
help: '会员升级的商品选项',
|
||||
sections: [
|
||||
{ title: '月费会员升级月费会员', keys: ['_upgrade_unit_notice', 'month_to_month_s', 'unit_price', 'unit_tag', 'unit_tag_class'] },
|
||||
{ title: '月费会员升级永久会员', keys: ['_upgrade_jump_notice', 'jump_s', 'jump_price', 'jump_show_price', 'jump_tag', 'jump_tag_class'] },
|
||||
{ title: '永久会员升级永久会员', keys: ['permanent_price', 'permanent_show_price', 'permanent_tag', 'permanent_tag_class'] },
|
||||
],
|
||||
fields: [
|
||||
{ key: '_upgrade_unit_notice', label: '', type: 'notice', ui_only: true, alertType: 'warning', content: '月费会员升级为月费会员,用户会员有效期不会改变;价格按照会员剩余天数计算升级价格,请在下方设置每天的单价' },
|
||||
{ key: 'month_to_month_s', label: '月费会员升级月费会员', type: 'switch', default: true },
|
||||
{ key: 'unit_price', label: '天单价', type: 'number', sanitize_type: 'money_decimal', default: '10.00', min: 0, unit: '元', dependsOn: { key: 'month_to_month_s', value: true } },
|
||||
{ key: 'unit_tag', label: '促销标签', type: 'textarea', sanitize_type: 'html', default: '<i class="fa fas fa-fw fa-bolt"></i> 站长推荐', rows: 1, dependsOn: { key: 'month_to_month_s', value: true }, help: '支持HTML,请注意控制长度' },
|
||||
{ key: 'unit_tag_class', label: '标签颜色', type: 'palette', default: 'jb-yellow', dependsOn: { all: [{ key: 'month_to_month_s', value: true }, { key: 'unit_tag', operator: 'not_empty' }] }, options: vipTagPalette },
|
||||
{ key: '_upgrade_jump_notice', label: '', type: 'notice', ui_only: true, alertType: 'warning', content: '如果网站可以购买月费会员和永久会员,则可以开启此项;允许用户由月费会员直接升级到更高一级的永久会员,用户之前还未到期的部分将自动一起转为永久会员,可能会涉及到差价问题,请自行告知用户;如果网站没有永久会员的购买选项请务必关闭此项' },
|
||||
{ key: 'jump_s', label: '月费会员升级永久会员', type: 'switch', default: false },
|
||||
{ key: 'jump_price', label: '执行价', type: 'number', sanitize_type: 'money_decimal', default: '199.00', min: 0, unit: '元', dependsOn: { key: 'jump_s', value: true }, help: '月费会员升级永久会员的价格' },
|
||||
{ key: 'jump_show_price', label: '原价', type: 'number', sanitize_type: 'money_decimal', default: '299.00', min: 0, unit: '元', dependsOn: { key: 'jump_s', value: true }, help: '显示在执行价格前面,并划掉' },
|
||||
{ key: 'jump_tag', label: '促销标签', type: 'textarea', sanitize_type: 'html', default: '<i class="fa fas fa-fw fa-bolt"></i> 升级特惠', rows: 1, dependsOn: { key: 'jump_s', value: true }, help: '支持HTML,请注意控制长度' },
|
||||
{ key: 'jump_tag_class', label: '标签颜色', type: 'palette', default: 'jb-red', dependsOn: { all: [{ key: 'jump_s', value: true }, { key: 'jump_tag', operator: 'not_empty' }] }, options: vipTagPalette },
|
||||
{ key: 'permanent_price', label: '执行价', type: 'number', sanitize_type: 'money_decimal', default: '199.00', min: 0, unit: '元', help: '永久会员升级永久会员的价格' },
|
||||
{ key: 'permanent_show_price', label: '原价', type: 'number', sanitize_type: 'money_decimal', default: '299.00', min: 0, unit: '元', help: '显示在执行价格前面,并划掉' },
|
||||
{ key: 'permanent_tag', label: '促销标签', type: 'textarea', sanitize_type: 'html', default: '<i class="fa fas fa-fw fa-bolt"></i> 升级特惠', rows: 1, help: '支持HTML,请注意控制长度' },
|
||||
{ key: 'permanent_tag_class', label: '标签颜色', type: 'palette', default: 'jb-red', dependsOn: { key: 'permanent_tag', operator: 'not_empty' }, options: vipTagPalette },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -205,16 +428,72 @@ export const vipSchema = {
|
||||
title: '积分兑换会员',
|
||||
icon: 'fa fas fa-database',
|
||||
fields: [
|
||||
{ key: 'pay_vip_points_exchange_s', label: '启用积分兑换', type: 'switch', default: false },
|
||||
{ key: 'pay_vip_points_exchange_s', label: '启用积分兑换', type: 'switch', default: false, help: '开启后用户可使用积分兑换会员;由于积分可免费获取,所以积分兑换会员功能不支持升级及续费' },
|
||||
{
|
||||
key: 'payment_vip_points_packages',
|
||||
label: '兑换套餐',
|
||||
type: 'textarea',
|
||||
default: '1|500|30|积分月卡\n1|5000|0|积分永久',
|
||||
key: 'pay_vip_points_exchange_product',
|
||||
label: '积分兑换明细',
|
||||
type: 'group',
|
||||
max: 16,
|
||||
button_title: '添加积分兑换商品',
|
||||
default: [
|
||||
{ points: 500, level: 1, time: 30, unit: 'day' },
|
||||
{ points: 5000, level: 1, time: 'Permanent' },
|
||||
],
|
||||
dependsOn: { key: 'pay_vip_points_exchange_s', value: true },
|
||||
help: '每行:等级|积分|天数|标题(天数 0=永久;仅未开通会员可兑)',
|
||||
help: '每一行为一个可兑换的会员套餐',
|
||||
...vipPointsGroupOptions,
|
||||
fields: [
|
||||
{ key: 'points', label: '使用积分', type: 'number', default: 100, min: 0, unit: '积分' },
|
||||
{
|
||||
key: 'level',
|
||||
label: '兑换会员',
|
||||
type: 'radio',
|
||||
default: 1,
|
||||
inline: true,
|
||||
options: [
|
||||
{ label: '一级会员', value: 1 },
|
||||
{ label: '二级会员', value: 2 },
|
||||
],
|
||||
},
|
||||
{ key: 'time', label: '兑换时长', type: 'input', default: '', help: '填 Permanent 为永久' },
|
||||
{
|
||||
key: 'unit',
|
||||
label: '时长单位',
|
||||
type: 'radio',
|
||||
default: 'day',
|
||||
inline: true,
|
||||
dependsOn: { key: 'time', operator: 'neq', value: 'Permanent' },
|
||||
options: vipUnitOptions,
|
||||
},
|
||||
],
|
||||
},
|
||||
{ key: 'pay_vip_points_exchange_desc', label: '兑换说明', type: 'textarea', sanitize_type: 'html', default: '', rows: 2, dependsOn: { key: 'pay_vip_points_exchange_s', value: true }, help: '显示在积分兑换弹窗底部,可以为提醒事项、兑换协议等,支持HTML代码' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'modal-copy',
|
||||
title: '界面文案',
|
||||
icon: 'fa fas fa-align-left',
|
||||
fields: [
|
||||
{
|
||||
key: 'pay_user_vip_desc',
|
||||
label: '会员介绍',
|
||||
type: 'input',
|
||||
sanitize_type: 'html',
|
||||
default: '开通VIP会员,享受会员专属折扣以及多项特权',
|
||||
dependsOn: { any: [{ key: 'pay_user_vip_1_s', value: true }, { key: 'pay_user_vip_2_s', value: true }] },
|
||||
help: '显示在开通界面顶部一句话简介,可以为会员权益简介或者活动介绍,支持HTML',
|
||||
},
|
||||
{
|
||||
key: 'pay_user_vip_more',
|
||||
label: '开通会员更多内容',
|
||||
type: 'textarea',
|
||||
sanitize_type: 'html',
|
||||
default: '<div>购买后不支持退款</div><div>VIP权益仅适用于本站</div><div>欢迎与站长联系</div>',
|
||||
rows: 4,
|
||||
dependsOn: { any: [{ key: 'pay_user_vip_1_s', value: true }, { key: 'pay_user_vip_2_s', value: true }] },
|
||||
help: '显示在开通界面底部位置,可以为提醒事项、用户协议等,支持HTML代码',
|
||||
},
|
||||
{ key: 'pay_vip_points_exchange_desc', label: '兑换说明', type: 'textarea', default: '', dependsOn: { key: 'pay_vip_points_exchange_s', value: true } },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -84,6 +84,9 @@ export const permissionsSchemas = {
|
||||
{ label: '资源下载', value: 'download' },
|
||||
{ label: '每日签到', value: 'signin' },
|
||||
{ label: '前台投稿', value: 'front_submit' },
|
||||
{ label: '打赏用户', value: 'tip_user' },
|
||||
{ label: '申请售后', value: 'after_sale_apply' },
|
||||
{ label: '撤销售后', value: 'after_sale_cancel' },
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -237,9 +240,6 @@ export const permissionsSchemas = {
|
||||
{ type: 'admin', pattern: 'allow', val: 'svg=image/svg+xml' },
|
||||
{ type: 'all', pattern: 'allow', val: '' },
|
||||
],
|
||||
newfilename: false,
|
||||
newfilename_type: 'time',
|
||||
close_attachment_page: true,
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
@@ -314,27 +314,6 @@ export const permissionsSchemas = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'behavior',
|
||||
title: '上传行为',
|
||||
icon: 'fa fas fa-cogs',
|
||||
fields: [
|
||||
{ key: 'newfilename', label: '上传文件自动重命名', type: 'switch', help: '开启后上传文件按规则重命名,减少重名文件查询,提高上传效率。' },
|
||||
{
|
||||
key: 'newfilename_type',
|
||||
label: '重命名格式',
|
||||
type: 'radio',
|
||||
buttonStyle: false,
|
||||
default: 'time',
|
||||
dependsOn: { key: 'newfilename', value: true },
|
||||
options: [
|
||||
{ label: '时间 + 原文件名', value: 'time' },
|
||||
{ label: '随机字符串', value: 'random' },
|
||||
],
|
||||
},
|
||||
{ key: 'close_attachment_page', label: '非管理员屏蔽附件页面', type: 'switch', help: '开启后非管理员访问附件(图片、文件等)页面将直接显示 404,可有效提高文件的安全性。' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import { featuresSchema } from './features.js'
|
||||
import { verifySchema } from './verify.js'
|
||||
import { levelSchema } from './level.js'
|
||||
import { badgeSchema } from './badge.js'
|
||||
import { wechatSchema } from './wechat.js'
|
||||
import { reportSchema } from './report.js'
|
||||
import { smsSchema } from './sms.js'
|
||||
|
||||
@@ -23,7 +22,6 @@ export const usersSchemas = {
|
||||
verify: verifySchema,
|
||||
level: levelSchema,
|
||||
badge: badgeSchema,
|
||||
wechat: wechatSchema,
|
||||
report: reportSchema,
|
||||
sms: smsSchema,
|
||||
}
|
||||
@@ -2,143 +2,104 @@ export const smsSchema = {
|
||||
key: 'users.sms',
|
||||
title: '短信接口',
|
||||
icon: 'fa fas fa-comment',
|
||||
description: '配置短信验证码和短信通知服务的供应商、模板和限流策略。',
|
||||
description: '设置手机验证码服务。验证码为 6 位数字,有效期 30 分钟。',
|
||||
defaults: {
|
||||
sms_enabled: false,
|
||||
sms_provider: 'aliyun',
|
||||
sms_sdk: 'null',
|
||||
sms_number_limit_ch: true,
|
||||
sms_access_key: '',
|
||||
sms_access_secret: '',
|
||||
sms_sign_name: '',
|
||||
sms_template_code: '',
|
||||
sms_code_length: 6,
|
||||
sms_code_expire: 300,
|
||||
sms_rate_limit: 60,
|
||||
sms_daily_limit: 10,
|
||||
sms_notify_bind: true,
|
||||
sms_notify_login: false,
|
||||
sms_notify_register: true,
|
||||
sms_notify_reset_password: true,
|
||||
sms_ali_api_type: 'sms',
|
||||
sms_ali_keyid: '',
|
||||
sms_ali_keysecret: '',
|
||||
sms_ali_sign_name: '',
|
||||
sms_ali_template_code: '',
|
||||
sms_region_id: 'cn-hangzhou',
|
||||
sms_tencent_app_id: '',
|
||||
sms_tencent_app_key: '',
|
||||
sms_tencent_secret_id: '',
|
||||
sms_tencent_secret_key: '',
|
||||
sms_tencent_region: 'ap-guangzhou',
|
||||
sms_huawei_sender: '',
|
||||
sms_huawei_signature: '',
|
||||
sms_huawei_endpoint: '',
|
||||
sms_custom_url: '',
|
||||
sms_custom_method: 'POST',
|
||||
sms_custom_auth_type: 'header',
|
||||
sms_custom_auth_key: '',
|
||||
sms_custom_auth_value: '',
|
||||
sms_custom_headers: '',
|
||||
sms_custom_body: '',
|
||||
sms_custom_success_rule: '',
|
||||
sms_tencent_sign_name: '',
|
||||
sms_tencent_template_id: '',
|
||||
sms_smsbao_username: '',
|
||||
sms_smsbao_password: '',
|
||||
sms_smsbao_api_key: '',
|
||||
sms_smsbao_template: '',
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'provider',
|
||||
title: '供应商配置',
|
||||
title: '短信接口',
|
||||
icon: 'fa fas fa-plug',
|
||||
fields: [
|
||||
{ key: 'sms_enabled', label: '启用短信服务', type: 'switch', default: false, help: '开启后支持短信验证码和短信通知' },
|
||||
{
|
||||
key: 'sms_provider',
|
||||
label: '短信供应商',
|
||||
key: 'sms_sdk',
|
||||
label: '设置短信接口',
|
||||
type: 'select',
|
||||
default: 'aliyun',
|
||||
default: 'null',
|
||||
options: [
|
||||
{ label: '阿里云短信', value: 'aliyun' },
|
||||
{ label: '不使用', value: 'null' },
|
||||
{ label: '阿里云短信', value: 'ali' },
|
||||
{ label: '腾讯云短信', value: 'tencent' },
|
||||
{ label: '华为云短信', value: 'huawei' },
|
||||
{ label: '自定义接口', value: 'custom' },
|
||||
{ label: '短信宝', value: 'smsbao' },
|
||||
],
|
||||
dependsOn: { key: 'sms_enabled', value: true },
|
||||
},
|
||||
{ key: 'sms_number_limit_ch', label: '仅发国内', type: 'switch', default: true, dependsOn: { key: 'sms_enabled', value: true }, help: '开启后仅支持发送国内短信,关闭后可发送港澳台及国际短信' },
|
||||
{ key: 'sms_access_key', label: 'AccessKey / SecretId / AppKey', type: 'input', default: '', dependsOn: { key: 'sms_enabled', value: true }, help: '阿里云填 AccessKey,腾讯云填 SecretId,华为云填 AppKey' },
|
||||
{ key: 'sms_access_secret', label: 'AccessSecret / SecretKey / AppSecret', type: 'input', inputType: 'password', default: '', dependsOn: { key: 'sms_enabled', value: true } },
|
||||
{
|
||||
key: 'sms_number_limit_ch',
|
||||
label: '限制国内手机号',
|
||||
type: 'switch',
|
||||
default: true,
|
||||
help: '开启后只允许发送到中国大陆手机号。',
|
||||
},
|
||||
{ key: 'sms_test_phone', label: '测试手机号', type: 'input', default: '', ui_only: true, help: '保存接口配置后,发送固定验证码 888888 测试短信。' },
|
||||
{ key: 'sms_test_send', label: '发送测试短信', type: 'button', buttonType: 'primary', buttonText: '发送测试短信', ui_only: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'aliyun',
|
||||
title: '阿里云配置',
|
||||
title: '阿里云',
|
||||
icon: 'fa fas fa-cloud',
|
||||
collapsible: true,
|
||||
defaultCollapsed: true,
|
||||
fields: [
|
||||
{ key: 'sms_ali_api_type', label: '接口类型', type: 'radio', default: 'sms', options: [{ label: '短信服务', value: 'sms' }, { label: '号码认证', value: 'verify' }], dependsOn: { key: 'sms_provider', value: 'aliyun' } },
|
||||
{ key: 'sms_region_id', label: 'RegionId', type: 'input', default: 'cn-hangzhou', dependsOn: { key: 'sms_provider', value: 'aliyun' } },
|
||||
{
|
||||
key: 'sms_ali_api_type',
|
||||
label: '产品',
|
||||
type: 'radio',
|
||||
default: 'sms',
|
||||
options: [
|
||||
{ label: '短信服务', value: 'sms' },
|
||||
{ label: '号码认证:短信认证服务', value: 'verify' },
|
||||
],
|
||||
dependsOn: { key: 'sms_sdk', value: 'ali' },
|
||||
},
|
||||
{ key: 'sms_ali_keyid', label: 'AccessKey Id', type: 'input', default: '', dependsOn: { key: 'sms_sdk', value: 'ali' } },
|
||||
{ key: 'sms_ali_keysecret', label: 'AccessKey Secret', type: 'input', inputType: 'password', secret: true, default: '', dependsOn: { key: 'sms_sdk', value: 'ali' } },
|
||||
{ key: 'sms_ali_sign_name', label: '签名', type: 'input', default: '', dependsOn: { key: 'sms_sdk', value: 'ali' } },
|
||||
{ key: 'sms_ali_template_code', label: '模板 CODE', type: 'input', default: '', dependsOn: { all: [{ key: 'sms_sdk', value: 'ali' }, { key: 'sms_ali_api_type', not: true, value: 'verify' }] }, help: '短信服务需要填写。号码认证产品固定使用平台模板。' },
|
||||
{ key: 'sms_region_id', label: 'RegionId', type: 'input', default: 'cn-hangzhou', dependsOn: { key: 'sms_sdk', value: 'ali' } },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'tencent',
|
||||
title: '腾讯云配置',
|
||||
title: '腾讯云',
|
||||
icon: 'fa fas fa-cloud',
|
||||
collapsible: true,
|
||||
defaultCollapsed: true,
|
||||
fields: [
|
||||
{ key: 'sms_tencent_app_id', label: 'SdkAppId', type: 'input', default: '', dependsOn: { key: 'sms_provider', value: 'tencent' }, help: '腾讯云短信应用 ID' },
|
||||
{ key: 'sms_tencent_app_key', label: 'AppKey', type: 'input', inputType: 'password', default: '', dependsOn: { key: 'sms_provider', value: 'tencent' } },
|
||||
{ key: 'sms_tencent_secret_id', label: 'SecretId', type: 'input', default: '', dependsOn: { key: 'sms_provider', value: 'tencent' } },
|
||||
{ key: 'sms_tencent_secret_key', label: 'SecretKey', type: 'input', inputType: 'password', default: '', dependsOn: { key: 'sms_provider', value: 'tencent' } },
|
||||
{ key: 'sms_tencent_region', label: '地域', type: 'input', default: 'ap-guangzhou', dependsOn: { key: 'sms_provider', value: 'tencent' } },
|
||||
{ key: 'sms_tencent_app_id', label: 'SDK AppID', type: 'input', default: '', dependsOn: { key: 'sms_sdk', value: 'tencent' } },
|
||||
{ key: 'sms_tencent_app_key', label: 'App Key', type: 'input', inputType: 'password', secret: true, default: '', dependsOn: { key: 'sms_sdk', value: 'tencent' } },
|
||||
{ key: 'sms_tencent_sign_name', label: '签名', type: 'input', default: '', dependsOn: { key: 'sms_sdk', value: 'tencent' } },
|
||||
{ key: 'sms_tencent_template_id', label: '模板 ID', type: 'input', default: '', dependsOn: { key: 'sms_sdk', value: 'tencent' }, help: '模板包含验证码和有效分钟数两个参数。' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'huawei',
|
||||
title: '华为云配置',
|
||||
key: 'smsbao',
|
||||
title: '短信宝',
|
||||
icon: 'fa fas fa-cloud',
|
||||
collapsible: true,
|
||||
defaultCollapsed: true,
|
||||
fields: [
|
||||
{ key: 'sms_huawei_sender', label: '通道号 (sender)', type: 'input', default: '', dependsOn: { key: 'sms_provider', value: 'huawei' } },
|
||||
{ key: 'sms_huawei_signature', label: '短信签名', type: 'input', default: '', dependsOn: { key: 'sms_provider', value: 'huawei' }, help: '华为云短信签名内容' },
|
||||
{ key: 'sms_huawei_endpoint', label: 'API Endpoint', type: 'input', default: '', dependsOn: { key: 'sms_provider', value: 'huawei' }, help: '留空使用默认 endpoint' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'custom',
|
||||
title: '自定义接口配置',
|
||||
icon: 'fa fas fa-plug',
|
||||
fields: [
|
||||
{ key: 'sms_custom_url', label: '请求 URL', type: 'input', default: '', dependsOn: { key: 'sms_provider', value: 'custom' }, help: '支持 {phone} {code} {time} 变量' },
|
||||
{ key: 'sms_custom_method', label: '请求方法', type: 'select', default: 'POST', options: [{ label: 'POST', value: 'POST' }, { label: 'GET', value: 'GET' }, { label: 'PUT', value: 'PUT' }], dependsOn: { key: 'sms_provider', value: 'custom' } },
|
||||
{ key: 'sms_custom_auth_type', label: '鉴权方式', type: 'select', default: 'header', options: [{ label: 'Header', value: 'header' }], dependsOn: { key: 'sms_provider', value: 'custom' } },
|
||||
{ key: 'sms_custom_auth_key', label: '鉴权 Header 名', type: 'input', default: '', dependsOn: { key: 'sms_provider', value: 'custom' } },
|
||||
{ key: 'sms_custom_auth_value', label: '鉴权 Header 值', type: 'input', inputType: 'password', default: '', dependsOn: { key: 'sms_provider', value: 'custom' }, help: '支持 {phone} {code} {time} 变量' },
|
||||
{ key: 'sms_custom_headers', label: '自定义 Headers', type: 'textarea', default: '', dependsOn: { key: 'sms_provider', value: 'custom' }, help: '每行一个,格式:Key: Value' },
|
||||
{ key: 'sms_custom_body', label: '请求 Body', type: 'textarea', default: '', dependsOn: { key: 'sms_provider', value: 'custom' }, help: 'JSON 格式,支持 {phone} {code} {time} 变量' },
|
||||
{ key: 'sms_custom_success_rule', label: '成功判定规则', type: 'input', default: '', dependsOn: { key: 'sms_provider', value: 'custom' }, help: '格式:status:200 或 json:code=0 或 contains:success' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'template',
|
||||
title: '短信模板',
|
||||
icon: 'fa fas fa-comment',
|
||||
fields: [
|
||||
{ key: 'sms_sign_name', label: '短信签名', type: 'input', default: '', dependsOn: { key: 'sms_enabled', value: true }, help: '在短信服务商申请的短信签名(所有供应商共用此字段)' },
|
||||
{ key: 'sms_template_code', label: '验证码模板 ID', type: 'input', default: '', dependsOn: { key: 'sms_enabled', value: true }, help: '所有供应商共用此模板 ID' },
|
||||
{ key: 'sms_code_length', label: '验证码长度', type: 'number', default: 6, min: 4, max: 8, dependsOn: { key: 'sms_enabled', value: true } },
|
||||
{ key: 'sms_code_expire', label: '验证码有效期 (秒)', type: 'number', default: 300, min: 60, max: 600, dependsOn: { key: 'sms_enabled', value: true } },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'rate_limit',
|
||||
title: '限流策略',
|
||||
icon: 'fa fas fa-shield-alt',
|
||||
fields: [
|
||||
{ key: 'sms_rate_limit', label: '发送间隔 (秒)', type: 'number', default: 60, min: 10, max: 300, dependsOn: { key: 'sms_enabled', value: true }, help: '同一手机号两次发送的最小间隔' },
|
||||
{ key: 'sms_daily_limit', label: '每日发送上限', type: 'number', default: 10, min: 1, max: 100, dependsOn: { key: 'sms_enabled', value: true }, help: '同一手机号每天最多发送条数' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'triggers',
|
||||
title: '通知场景',
|
||||
icon: 'fa fas fa-bell',
|
||||
fields: [
|
||||
{ key: 'sms_notify_bind', label: '绑定手机时验证', type: 'switch', default: true, dependsOn: { key: 'sms_enabled', value: true } },
|
||||
{ key: 'sms_notify_login', label: '短信登录', type: 'switch', default: false, dependsOn: { key: 'sms_enabled', value: true } },
|
||||
{ key: 'sms_notify_register', label: '注册验证', type: 'switch', default: true, dependsOn: { key: 'sms_enabled', value: true } },
|
||||
{ key: 'sms_notify_reset_password', label: '找回密码', type: 'switch', default: true, dependsOn: { key: 'sms_enabled', value: true } },
|
||||
{ key: 'sms_smsbao_username', label: '用户名', type: 'input', default: '', dependsOn: { key: 'sms_sdk', value: 'smsbao' } },
|
||||
{ key: 'sms_smsbao_password', label: '密码', type: 'input', inputType: 'password', secret: true, default: '', dependsOn: { key: 'sms_sdk', value: 'smsbao' } },
|
||||
{ key: 'sms_smsbao_api_key', label: 'ApiKey', type: 'input', inputType: 'password', secret: true, default: '', dependsOn: { key: 'sms_sdk', value: 'smsbao' }, help: '可选;填写后以 ApiKey 认证,可与密码任选其一。' },
|
||||
{ key: 'sms_smsbao_template', label: '模板内容', type: 'input', default: '', dependsOn: { key: 'sms_sdk', value: 'smsbao' }, help: '必须包含 {code},有效时间变量为 {time}。' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -2,83 +2,52 @@ export const verifySchema = {
|
||||
key: 'users.verify',
|
||||
title: '身份认证',
|
||||
icon: 'fa fas fa-id-card',
|
||||
description: '配置公开认证徽标、申请类型与申请门槛;审核、驳回和取消认证请前往 WordPress 用户 → 身份认证。',
|
||||
description: '设置通用身份认证的申请条件;用户提交资料后由管理员审核。',
|
||||
defaults: {
|
||||
user_verify_enabled: true,
|
||||
user_verify_public_display: true,
|
||||
user_verify_personal_badge_text: '个人认证',
|
||||
user_verify_personal_fee: '0.00',
|
||||
user_verify_personal_free_applications: 0,
|
||||
user_verify_personal_require_real_name: false,
|
||||
user_verify_enterprise_badge_text: '企业认证',
|
||||
user_verify_enterprise_fee: '0.00',
|
||||
user_verify_enterprise_free_applications: 0,
|
||||
user_verify_enterprise_require_real_name: false,
|
||||
user_verify_allow_apply: true,
|
||||
user_verify_disable_desc:
|
||||
'由于系统正在维护,暂时无法申请身份认证\n详情请联系管理员',
|
||||
user_verify_desc:
|
||||
'认证用户会在昵称后显示认证徽章,同时享受多种优先待遇。申请请确保信息属实。',
|
||||
user_auth_s: true,
|
||||
user_auth_apply_s: true,
|
||||
user_auth_apply_disable_desc: '由于系统正在维护,暂时无法申请身份认证\n详情请联系管理员',
|
||||
user_auth_apply_limit_level: 0,
|
||||
user_auth_apply_desc: '请填写认证身份、简介和申请说明,提交后由管理员审核。',
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'display',
|
||||
title: '公开展示',
|
||||
icon: 'fa fas fa-eye',
|
||||
fields: [
|
||||
{ key: 'user_verify_enabled', label: '启用身份认证', type: 'switch', default: true },
|
||||
{
|
||||
key: 'user_verify_public_display',
|
||||
label: '公开页显示认证徽标',
|
||||
type: 'switch',
|
||||
default: true,
|
||||
dependsOn: { key: 'user_verify_enabled', value: true },
|
||||
},
|
||||
{
|
||||
key: 'user_verify_personal_badge_text', label: '个人认证徽标文案', type: 'input', default: '个人认证',
|
||||
dependsOn: { key: 'user_verify_public_display', value: true },
|
||||
},
|
||||
{
|
||||
key: 'user_verify_enterprise_badge_text', label: '企业认证徽标文案', type: 'input', default: '企业认证',
|
||||
dependsOn: { key: 'user_verify_public_display', value: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'workflow', title: '认证方式', icon: 'fa fas fa-tasks',
|
||||
fields: [{ type: 'alert', content: '认证方式固定为个人认证和企业认证,申请人不可自行新增或修改类型。' }],
|
||||
},
|
||||
{
|
||||
key: 'apply',
|
||||
title: '申请设置',
|
||||
title: '认证申请',
|
||||
icon: 'fa fas fa-edit',
|
||||
fields: [
|
||||
{ key: 'user_auth_s', label: '启用身份认证', type: 'switch', default: true },
|
||||
{
|
||||
key: 'user_verify_allow_apply',
|
||||
key: 'user_auth_apply_s',
|
||||
label: '允许用户前台申请',
|
||||
type: 'switch',
|
||||
default: true,
|
||||
dependsOn: { key: 'user_verify_enabled', value: true },
|
||||
dependsOn: { key: 'user_auth_s', value: true },
|
||||
},
|
||||
{
|
||||
key: 'user_verify_disable_desc',
|
||||
key: 'user_auth_apply_disable_desc',
|
||||
label: '停用申请说明',
|
||||
type: 'textarea',
|
||||
default: '由于系统正在维护,暂时无法申请身份认证\n详情请联系管理员',
|
||||
dependsOn: { key: 'user_verify_allow_apply', value: false },
|
||||
dependsOn: { key: 'user_auth_apply_s', value: false },
|
||||
},
|
||||
{ key: 'user_verify_personal_fee', label: '个人认证每次扣余额(元)', type: 'input', default: '0.00', help: '仅接受最多两位小数的十进制字符串。', dependsOn: { key: 'user_verify_allow_apply', value: true } },
|
||||
{ key: 'user_verify_personal_free_applications', label: '个人认证赠送初始次数', type: 'number', default: 0, min: 0, max: 9999, help: '每位用户前 N 次个人认证申请免费,之后按单次费用扣余额。', dependsOn: { key: 'user_verify_allow_apply', value: true } },
|
||||
{ key: 'user_verify_personal_require_real_name', label: '个人认证强制填写真实姓名', type: 'switch', default: false, dependsOn: { key: 'user_verify_allow_apply', value: true } },
|
||||
{ key: 'user_verify_enterprise_fee', label: '企业认证每次扣余额(元)', type: 'input', default: '0.00', help: '仅接受最多两位小数的十进制字符串。', dependsOn: { key: 'user_verify_allow_apply', value: true } },
|
||||
{ key: 'user_verify_enterprise_free_applications', label: '企业认证赠送初始次数', type: 'number', default: 0, min: 0, max: 9999, help: '每位用户前 N 次企业认证申请免费,之后按单次费用扣余额。', dependsOn: { key: 'user_verify_allow_apply', value: true } },
|
||||
{ key: 'user_verify_enterprise_require_real_name', label: '企业认证强制填写真实姓名', type: 'switch', default: false, dependsOn: { key: 'user_verify_allow_apply', value: true } },
|
||||
{
|
||||
key: 'user_verify_desc',
|
||||
key: 'user_auth_apply_limit_level',
|
||||
label: '申请最低用户等级',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
min: 0,
|
||||
max: 30,
|
||||
help: '设置为 0 时不限制用户等级。',
|
||||
dependsOn: { key: 'user_auth_apply_s', value: true },
|
||||
},
|
||||
{
|
||||
key: 'user_auth_apply_desc',
|
||||
label: '申请介绍',
|
||||
type: 'textarea',
|
||||
default: '认证用户会在昵称后显示认证徽章,同时享受多种优先待遇。申请请确保信息属实。',
|
||||
dependsOn: { key: 'user_verify_allow_apply', value: true },
|
||||
sanitize_type: 'html',
|
||||
default: '请填写认证身份、简介和申请说明,提交后由管理员审核。',
|
||||
dependsOn: { key: 'user_auth_apply_s', value: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
export const wechatSchema = {
|
||||
key: 'users.wechat',
|
||||
title: '微信通知',
|
||||
icon: 'fa fab fa-weixin',
|
||||
description: '配置微信模板消息、订阅/解绑和站外通知策略。',
|
||||
defaults: {
|
||||
wechat_notify_enabled: false,
|
||||
wechat_app_id: '',
|
||||
wechat_app_secret: '',
|
||||
wechat_template_id: '',
|
||||
wechat_token: '',
|
||||
wechat_encoding_aes_key: '',
|
||||
wechat_notify_comment: true,
|
||||
wechat_notify_reply: true,
|
||||
wechat_notify_system: true,
|
||||
wechat_bind_entry: true,
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'account',
|
||||
title: '公众号配置',
|
||||
icon: 'fa fab fa-weixin',
|
||||
fields: [
|
||||
{ key: 'wechat_notify_enabled', label: '启用微信通知', type: 'switch', default: false, help: '开启后用户可绑定微信接收通知' },
|
||||
{ key: 'wechat_app_id', label: 'AppID', type: 'input', default: '', dependsOn: { key: 'wechat_notify_enabled', value: true } },
|
||||
{ key: 'wechat_app_secret', label: 'AppSecret', type: 'input', default: '', dependsOn: { key: 'wechat_notify_enabled', value: true } },
|
||||
{ key: 'wechat_template_id', label: '模板消息 ID', type: 'input', default: '', dependsOn: { key: 'wechat_notify_enabled', value: true }, help: '在微信公众平台申请的模板消息 ID' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'verify',
|
||||
title: '服务器验证',
|
||||
icon: 'fa fas fa-check-circle',
|
||||
fields: [
|
||||
{ key: 'wechat_token', label: 'Token', type: 'input', default: '', dependsOn: { key: 'wechat_notify_enabled', value: true }, help: '微信公众平台服务器配置中的 Token' },
|
||||
{ key: 'wechat_encoding_aes_key', label: 'EncodingAESKey', type: 'input', default: '', dependsOn: { key: 'wechat_notify_enabled', value: true } },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'triggers',
|
||||
title: '通知触发器',
|
||||
icon: 'fa fas fa-bell',
|
||||
fields: [
|
||||
{ key: 'wechat_notify_comment', label: '新评论通知', type: 'switch', default: true, dependsOn: { key: 'wechat_notify_enabled', value: true } },
|
||||
{ key: 'wechat_notify_reply', label: '评论回复通知', type: 'switch', default: true, dependsOn: { key: 'wechat_notify_enabled', value: true } },
|
||||
{ key: 'wechat_notify_system', label: '系统通知', type: 'switch', default: true, dependsOn: { key: 'wechat_notify_enabled', value: true } },
|
||||
{ key: 'wechat_bind_entry', label: '显示绑定入口', type: 'switch', default: true, dependsOn: { key: 'wechat_notify_enabled', value: true }, help: '在用户中心显示微信绑定/解绑按钮' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -668,95 +668,6 @@ code {
|
||||
|
||||
/**---------------------通知结束-------------**/
|
||||
|
||||
/**模态框------*/
|
||||
.aksrt-modal-backdrop,
|
||||
.aksrt-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 100050;
|
||||
opacity: 0;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.aksrt-modal {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.aksrt-modal-backdrop {
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.aksrt-modal.in {
|
||||
opacity: 1;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.aksrt-modal.in .aksrt-modal-backdrop {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.aksrt-modal-dialog {
|
||||
max-width: calc(100% - 20px);
|
||||
margin: 0;
|
||||
transform: scale(0.9);
|
||||
transition: transform 0.5s cubic-bezier(0.32, 0.85, 0.45, 1.18), width 0.3s;
|
||||
}
|
||||
|
||||
.aksrt-modal-dialog.modal-mini {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.aksrt-modal.in .aksrt-modal-dialog {
|
||||
transform: scale(1);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.aksrt-modal .close.abs-close {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.aksrt-modal .border-title {
|
||||
padding-bottom: 12px;
|
||||
margin-bottom: 12px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.aksrt-modal-content {
|
||||
max-height: calc(100vh - 20px);
|
||||
overflow: hidden;
|
||||
overflow-y: hidden;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.aksrt-modal-body {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.aksrt-modal .loading-mask {
|
||||
z-index: 1;
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.aksrt-modal .hide-btn {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 6px;
|
||||
font-size: 25px;
|
||||
opacity: 0.6;
|
||||
cursor: pointer;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
/**---------批量编辑------------**/
|
||||
.aksrt-bulk-edit-box {
|
||||
margin: -1px;
|
||||
@@ -774,19 +685,18 @@ code {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
/**---------------TAB-栏目------------**/
|
||||
.aksrt-tab-pane {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
transition: 0.15s;
|
||||
.aksrt-bulk-edit-box select,
|
||||
.aksrt-bulk-edit-box input[type="text"] {
|
||||
max-width: 100%;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.aksrt-tab-pane.active {
|
||||
display: block;
|
||||
.inline-edit-row fieldset .aksrt-bulk-edit-box {
|
||||
min-width: 168px;
|
||||
}
|
||||
|
||||
.aksrt-tab-pane.in {
|
||||
opacity: 1;
|
||||
.aksrt-bulk-edit-desc {
|
||||
color: #787c82;
|
||||
}
|
||||
|
||||
/**---------------后台设置项目-修改或隐藏-----------**/
|
||||
|
||||
@@ -15,6 +15,17 @@
|
||||
margin-left: 8px !important;
|
||||
}
|
||||
|
||||
/* 骨架与真实卡片同宽:.posts-row 以 -8px 抵消卡片边距,loader 被上面的
|
||||
:not() 排除在补偿外,会宽出 16px;这里补上等同的水平边距。
|
||||
补偿落在骨架内层的 .posts-item 上(与真实卡片同一元素),不落外层
|
||||
.post_ajax_loader —— 否则内层 .posts-item.card 的 margin:8px 会与外层
|
||||
补偿叠成 16px,小工具 card 骨架因此窄出 8px。 */
|
||||
.posts-row > .post_ajax_loader .posts-item,
|
||||
.posts-row > .post_ajax_loader .posts-mini {
|
||||
margin-right: 8px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.posts-item {
|
||||
margin: 15px 0;
|
||||
padding: 20px;
|
||||
@@ -448,6 +459,17 @@
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
/* 骨架与卡片同宽同轴:.widget-ajaxpager / .tab-pane 用负边距抵消卡片的水平
|
||||
边距。补偿同样落在骨架内层的 .posts-item 上(与真实卡片同一元素),
|
||||
不落外层 .post_ajax_loader —— 否则内层 .posts-item.card 自带的 margin:8px
|
||||
会与外层补偿叠成 16px,card 骨架因此窄出 8px。 */
|
||||
.widget-main-post.style-list .widget-ajaxpager > .post_ajax_loader .posts-item,
|
||||
.widget-main-post.style-card .widget-ajaxpager > .post_ajax_loader .posts-item,
|
||||
.widget-tab-post .tab-content > .tab-pane > .widget-ajaxpager > .post_ajax_loader .posts-item {
|
||||
margin-right: 8px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.posts-mini:hover img,
|
||||
.posts-item:hover .item-thumbnail img,
|
||||
.posts-item.mult-thumb .thumb-items > span > img:hover {
|
||||
@@ -518,6 +540,12 @@
|
||||
margin-left: 5px !important;
|
||||
}
|
||||
|
||||
.posts-row > .post_ajax_loader .posts-item,
|
||||
.posts-row > .post_ajax_loader .posts-mini {
|
||||
margin-right: 5px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.posts-item .item-thumbnail {
|
||||
width: 138px;
|
||||
}
|
||||
@@ -564,6 +592,13 @@
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.widget-main-post.style-list .widget-ajaxpager > .post_ajax_loader .posts-item,
|
||||
.widget-main-post.style-card .widget-ajaxpager > .post_ajax_loader .posts-item,
|
||||
.widget-tab-post .tab-content > .tab-pane > .widget-ajaxpager > .post_ajax_loader .posts-item {
|
||||
margin-right: 5px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.swiper-scroll .posts-item {
|
||||
margin-right: 10px !important;
|
||||
}
|
||||
@@ -674,8 +709,9 @@
|
||||
color: var(--color-primary, #f04494);
|
||||
}
|
||||
|
||||
/* .widget a 的链接色不得覆盖 .but 按钮自身配色(zibll 的 zib-widget 不设置链接色) */
|
||||
.widget a.but {
|
||||
/* .widget a 的链接色不得覆盖 .but / .img-badge 自身配色(zibll 的 zib-widget 不设置链接色) */
|
||||
.widget a.but,
|
||||
.widget a.img-badge {
|
||||
color: var(--this-color, #fff);
|
||||
}
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
.posts-row{margin-right:-8px;margin-bottom:15px;margin-left:-8px}.posts-row>*:not(.post_ajax_loader){margin-right:8px!important;margin-left:8px!important}.posts-item{margin:15px 0;padding:20px;overflow:hidden;background:var(--main-bg-color, #fff);border-radius:var(--main-radius, 8px);box-shadow:0 0 10px var(--main-shadow, rgba(116, 116, 116, .08));transition:.2s}.posts-item.flex,.posts-item .flex,.posts-mini .flex{display:flex}.posts-item .flex.xx,.posts-mini .flex.xx{flex-direction:column}.posts-item .flex.ac,.posts-mini .flex.ac{align-items:center}.posts-item .flex.jsb,.posts-mini .flex.jsb{justify-content:space-between}.posts-item .flex1,.posts-mini .flex1{flex:1;overflow:hidden}.posts-item .post-graphic:not(.order1){margin-right:20px}.posts-item .order1.post-graphic{margin-right:0;margin-left:20px}.posts-item .item-thumbnail{position:relative;display:block;width:190px;height:0;padding-bottom:var(--posts-list-scale, 70%);overflow:hidden;border-radius:var(--main-radius, 8px)}.item-thumbnail.contain img{object-fit:contain}.swiper-slide.gradient-bg .dplayer-poster img,.swiper-slide.gradient-bg>*>img{object-fit:contain!important;border-radius:0!important}.item-thumbnail .swiper-wrapper{height:100%!important}.list .item-thumbnail .aksrt-slider,.list .item-thumbnail.new-swiper{position:absolute;width:100%;height:100%;margin:0!important}.item-thumbnail .dplayer.dplayer-thumb{position:absolute;inset:0;z-index:1;padding-bottom:var(--posts-list-scale, 70%);background:#000;opacity:0;transition:opacity .3s}.card .item-thumbnail .dplayer.dplayer-thumb{padding-bottom:var(--posts-card-scale, 70%)}.item-thumbnail .dplayer-thumb video,.item-thumbnail .swiper-wrapper,.item-thumbnail img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}.item-thumbnail .swiper-pagination{right:0!important;bottom:0!important;left:0!important;padding:0 2px!important;text-align:center!important}.posts-item.thumb-dplayer-playing .dplayer-thumb{opacity:1}.posts-item.thumb-dplayer-playing .abs-center{opacity:0}.posts-item .item-heading{display:-webkit-box;max-height:2.8em;margin-top:0;margin-bottom:5px;overflow:hidden;font-size:18px;line-height:1.4em;white-space:normal;-webkit-box-orient:vertical;-webkit-line-clamp:2}.font-bold{font-weight:700}.list.style2 .item-thumbnail{width:130px}.list.style2 .item-heading{display:unset;max-height:1.4em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.posts-item .item-heading>a{color:var(--key-color, #333)}.posts-item .item-heading>a:hover{color:var(--focus-color, #f04494)}.posts-item .item-excerpt{margin:0 0 6px;overflow:hidden;color:var(--muted-color, #777);text-overflow:ellipsis;white-space:nowrap}.posts-item .item-meta item{margin-right:8px;font-size:13px}.posts-item .item-meta svg.icon,.posts-mini .item-meta svg.icon{display:inline-block;width:1em;height:1em;margin-right:2px;fill:currentColor;overflow:hidden;vertical-align:-.15em}.posts-item .meta-right item:last-child{margin-right:0}.item-meta .avatar-mini{width:20px;height:20px;border-radius:50%;transform:translateY(-1px)}.item-meta .meta-pay{padding:0 4px}.item-meta .meta-pay img{width:1em;height:1em;object-fit:contain;vertical-align:-.15em}.posts-item .item-meta a,.posts-mini .item-meta a{color:inherit}.posts-item .item-tags a{margin-right:5px;padding:2px 5px;font-size:11px}.posts-item .item-tags a .fa{font-size:1em}.posts-item.card{display:inline-block;width:calc(33.333% - 16px);margin:8px;padding:10px;vertical-align:top;transition:.3s}.posts-item.card:last-child{margin-bottom:20px}.posts-item.card .item-body{width:100%;margin:10px 0 0;padding:0}.posts-item.card .item-meta item{font-size:12px}.posts-mini-con .item-heading,.posts-item.card .item-heading{font-size:14px}.posts-item.card .item-heading{min-height:2.8em}.posts-item.card .item-tags{min-height:23px}.posts-item.card .item-thumbnail{width:100%;padding-bottom:var(--posts-card-scale, 70%)}.posts-item.card.style2{background:transparent;box-shadow:none}.posts-item.card.style2:hover{background:var(--main-bg-color, #fff);box-shadow:0 0 10px var(--main-shadow, rgba(116, 116, 116, .08))}.posts-item.card.style3{padding:0}.posts-item.card.style3 .item-thumbnail,.posts-item.card.style3 .item-thumbnail img{border-radius:var(--main-radius, 8px) var(--main-radius, 8px) 0 0}.posts-item.card.style3 .item-body{margin:0;padding:10px}.posts-item.mult-thumb .thumb-items{margin:0 -3px 6px}.posts-item.mult-thumb .thumb-items>span{position:relative;display:inline-block;width:calc(25% - 6px);height:0;margin:0 3px;padding-bottom:calc(var(--posts-list-scale, 70%) / 4)}.posts-item.mult-thumb .thumb-items>span>img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}.posts-item.mult-thumb .thumb-items.contain>span{overflow:hidden;background:var(--main-border-color, rgba(50, 50, 50, .06));border-radius:var(--main-radius, 8px)}.posts-item.mult-thumb .thumb-items.contain>span>img{object-fit:contain;border-radius:0}.posts-item.no_margin{margin:0;border-radius:0;box-shadow:none}.postsLine truncated
|
||||
.posts-row{margin-right:-8px;margin-bottom:15px;margin-left:-8px}.posts-row>*:not(.post_ajax_loader){margin-right:8px!important;margin-left:8px!important}.posts-row>.post_ajax_loader .posts-item,.posts-row>.post_ajax_loader .posts-mini{margin-right:8px;margin-left:8px}.posts-item{margin:15px 0;padding:20px;overflow:hidden;background:var(--main-bg-color, #fff);border-radius:var(--main-radius, 8px);box-shadow:0 0 10px var(--main-shadow, rgba(116, 116, 116, .08));transition:.2s}.posts-item.flex,.posts-item .flex,.posts-mini .flex{display:flex}.posts-item .flex.xx,.posts-mini .flex.xx{flex-direction:column}.posts-item .flex.ac,.posts-mini .flex.ac{align-items:center}.posts-item .flex.jsb,.posts-mini .flex.jsb{justify-content:space-between}.posts-item .flex1,.posts-mini .flex1{flex:1;overflow:hidden}.posts-item .post-graphic:not(.order1){margin-right:20px}.posts-item .order1.post-graphic{margin-right:0;margin-left:20px}.posts-item .item-thumbnail{position:relative;display:block;width:190px;height:0;padding-bottom:var(--posts-list-scale, 70%);overflow:hidden;border-radius:var(--main-radius, 8px)}.item-thumbnail.contain img{object-fit:contain}.swiper-slide.gradient-bg .dplayer-poster img,.swiper-slide.gradient-bg>*>img{object-fit:contain!important;border-radius:0!important}.item-thumbnail .swiper-wrapper{height:100%!important}.list .item-thumbnail .aksrt-slider,.list .item-thumbnail.new-swiper{position:absolute;width:100%;height:100%;margin:0!important}.item-thumbnail .dplayer.dplayer-thumb{position:absolute;inset:0;z-index:1;padding-bottom:var(--posts-list-scale, 70%);background:#000;opacity:0;transition:opacity .3s}.card .item-thumbnail .dplayer.dplayer-thumb{padding-bottom:var(--posts-card-scale, 70%)}.item-thumbnail .dplayer-thumb video,.item-thumbnail .swiper-wrapper,.item-thumbnail img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}.item-thumbnail .swiper-pagination{right:0!important;bottom:0!important;left:0!important;padding:0 2px!important;text-align:center!important}.posts-item.thumb-dplayer-playing .dplayer-thumb{opacity:1}.posts-item.thumb-dplayer-playing .abs-center{opacity:0}.posts-item .item-heading{display:-webkit-box;max-height:2.8em;margin-top:0;margin-bottom:5px;overflow:hidden;font-size:18px;line-height:1.4em;white-space:normal;-webkit-box-orient:vertical;-webkit-line-clamp:2}.font-bold{font-weight:700}.list.style2 .item-thumbnail{width:130px}.list.style2 .item-heading{display:unset;max-height:1.4em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.posts-item .item-heading>a{color:var(--key-color, #333)}.posts-item .item-heading>a:hover{color:var(--focus-color, #f04494)}.posts-item .item-excerpt{margin:0 0 6px;overflow:hidden;color:var(--muted-color, #777);text-overflow:ellipsis;white-space:nowrap}.posts-item .item-meta item{margin-right:8px;font-size:13px}.posts-item .item-meta svg.icon,.posts-mini .item-meta svg.icon{display:inline-block;width:1em;height:1em;margin-right:2px;fill:currentColor;overflow:hidden;vertical-align:-.15em}.posts-item .meta-right item:last-child{margin-right:0}.item-meta .avatar-mini{width:20px;height:20px;border-radius:50%;transform:translateY(-1px)}.item-meta .meta-pay{padding:0 4px}.item-meta .meta-pay img{width:1em;height:1em;object-fit:contain;vertical-align:-.15em}.posts-item .item-meta a,.posts-mini .item-meta a{color:inherit}.posts-item .item-tags a{margin-right:5px;padding:2px 5px;font-size:11px}.posts-item .item-tags a .fa{font-size:1em}.posts-item.card{display:inline-block;width:calc(33.333% - 16px);margin:8px;padding:10px;vertical-align:top;transition:.3s}.posts-item.card:last-child{margin-bottom:20px}.posts-item.card .item-body{width:100%;margin:10px 0 0;padding:0}.posts-item.card .item-meta item{font-size:12px}.posts-mini-con .item-heading,.posts-item.card .item-heading{font-size:14px}.posts-item.card .item-heading{min-height:2.8em}.posts-item.card .item-tags{min-height:23px}.posts-item.card .item-thumbnail{width:100%;padding-bottom:var(--posts-card-scale, 70%)}.posts-item.card.style2{background:transparent;box-shadow:none}.posts-item.card.style2:hover{background:var(--main-bg-color, #fff);box-shadow:0 0 10px var(--main-shadow, rgba(116, 116, 116, .08))}.posts-item.card.style3{padding:0}.posts-item.card.style3 .item-thumbnail,.posts-item.card.style3 .item-thumbnail img{border-radius:var(--main-radius, 8px) var(--main-radius, 8px) 0 0}.posts-item.card.style3 .item-body{margin:0;padding:10px}.posts-item.mult-thumb .thumb-items{margin:0 -3px 6px}.posts-item.mult-thumb .thumb-items>span{position:relative;display:inline-block;width:calc(25% - 6px);height:0;margin:0 3px;padding-bottom:calc(var(--posts-list-scale, 70%) / 4)}.posts-item.mult-thumb .thumb-items>span>img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}.posts-item.mult-thumb .thumb-items.contain>span{overflow:hidden;background:var(--main-border-color, rgba(50, 50, 50, .06));border-radius:var(--main-radius, 8px)}.posts-item.mult-thumb .thumb-items.contaiLine truncated
|
||||
+635
-1025
File diff suppressed because it is too large.
Load diff
Vendored
+1
-1
@@ -1 +1 @@
|
||||
:root{--color-primary: #f04494;--color-primary-hover: #e0307e;--color-primary-active: #c9206d;--color-primary-soft: rgba(240, 68, 148, .1);--color-primary-softer: rgba(240, 68, 148, .05);--color-on-primary: #FFFFFF;--color-ink: #333333;--color-ink-2: #4e5358;--color-ink-3: #777777;--color-ink-4: #999999;--surface-page: #f5f6f7;--surface-1: #FFFFFF;--surface-2: #eeeeee;--surface-3: #e9ecef;--surface-4: #f8f8f8;--color-line: rgba(50, 50, 50, .06);--color-line-soft: rgba(0, 0, 0, .03);--color-line-stronger: rgba(50, 50, 50, .12);--color-success: #10B981;--color-warning: #F59E0B;--color-error: #EF4444;--color-info: #3B82F6;--color-accent: #18a058;--color-accent-soft: rgba(24, 160, 88, .12);--color-accent-shadow: rgba(24, 160, 88, .28);--font-display: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;--font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;--font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;--font-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;--text-xs: .75rem;--text-sm: .875rem;--text-base: .875rem;--text-md: 1.125rem;--text-lg: 1.25rem;--text-xl: 1.5rem;--text-2xl: 1.875rem;--text-3xl: 2.25rem;--text-4xl: 3rem;--text-5xl: 3.75rem;--leading-tight: 1.2;--leading-snug: 1.4;--leading-normal: 1.6;--leading-relaxed: 1.6;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--space-5: 20px;--space-6: 24px;--space-7: 32px;--space-8: 40px;--space-9: 48px;--space-10: 64px;--space-11: 80px;--space-12: 96px;--radius-xs: 3px;--radius-sm: 4px;--radius: 8px;--radius-lg: 8px;--radius-xl: 12px;--radius-2xl: 16px;--radius-pill: 100px;--radius-avatar: 50%;--shadow-xs: 0 1px 2px rgba(0, 0, 0, .03);--shadow-sm: 0 0 10px rgba(116, 116, 116, .08);--shadow: 0 0 10px rgba(116, 116, 116, .12);--shadow-lg: 0 12px 32px rgba(18, 18, 18, .1);--shadow-drawer: 0 18px 60px rgba(0, 0, 0, .2);--nav-height: 60px;--container: 1200px;--container-wide: 1440px;--container-read: 760px;--container-page: 880px;--sidebar-width: 311px;--content-gap: 24px;--section-gap: 24px;--sidebar-bp: 992px;--ease: cubic-bezier(.4, 0, .2, 1);--ease-out: cubic-bezier(.16, 1, .3, 1);--ease-spring: cubic-bezier(.34, 1.56, .64, 1);--dur-fast: .15s;--dur: .25s;--dur-slow: .4s;--focus-ring: 0 0 2px 3px rgba(253, 83, 161, .4);--z-dropdown: 100;--z-sticky: 200;--z-fixed: 300;--z-overlay: 400;--z-modal: 500;--z-popover: 600;--z-toast: 700;--z-tooltip: 800;--radius-btn: 4px;--btn-sm-h: 28px;--btn-md-h: 34px;--btn-lg-h: 44px;--card-padding: 15px;--card-padding-sm: 10px;--card-gap: 16px;--card-gap-lg: 20px;--skeleton-base: var(--surface-2);--skeleton-shine: var(--surface-1);--color-primary-light: var(--color-primary-soft);--color-surface: var(--surface-1);--color-line-strong: var(--color-line);--color-bg-hover: var(--surface-2);--color-border-light: var(--color-line-soft);--color-fill: var(--surface-2);--color-warning-bg: #fffbe6;--color-warning-text: #ad6800;--text-secondary: var(--color-ink-3);--text-tertiary: var(--color-ink-4);--surface-active: var(--surface-2);--surface-card: var(--surface-1);--surface-hover: var(--surface-2);--surface-secondary: var(--surface-2);--radius-code: var(--radius);--radius-full: 100px;--shadow-xl: 0 0 10px rgba(116, 116, 116, .12);--space-16: 64px;--z-floating: 900;--transition-fast: .15s ease;--transition-base: .25s ease;--header-bg: var(--blur-bg);--header-color: var(--color-ink-2);--footer-bg: var(--surface-1);--footer-color: var(--color-ink-4);--blur-bg: rgba(255, 255, 255, .8);--float-btn-bg: rgba(200, 200, 200, .4);--font-heading: var(--font-ui);--radius-card: var(--radius);--radius-md: var(--radius);--color-primary-50: var(--color-primary-soft);--color-primary-dark: var(--color-primary-active);--shadow-md: var(--shadow);--mini-radius: calc(var(--radius) / 1.4);--aksrt-avatar-size-xs: 24px;--aksrt-avatar-size-sm: 32px;--aksrt-avatar-size: 40px;--aksrt-avatar-size-lg: 56px;--aksrt-avatar-size-xl: 80px;--aksrt-card-thumb-scale: 56.25%;--aksrt-rose: #f43f5e;--aksrt-rose-soft: rgba(244, 63, 94, .12);--aksrt-blue: #3b82f6;--aksrt-blue-soft: rgba(59, 130, 246, .12);--aksrt-green: #10b981;--aksrt-green-soft: rgba(16, 185, 129, .12);--aksrt-orange: #f97316;--aksrt-orange-soft:rgba(249, 115, 22, .12);--aksrt-purple: #8b5cf6;--aksrt-purple-soft:rgba(139, 92, 246, .12);--aksrt-cyan: #06b6d4;--aksrt-cyan-soft: rgba(6, 182, 212, .12);--aksrt-yellow: #eab308;--aksrt-yellow-soft:rgba(234, 179, 8, .12);--aksrt-red: #ef4444;--aksrt-red-soft: rgba(239, 68, 68, .12);--aksrt-teal: #14b8a6;--aksrt-teal-soft: rgba(20, 184, 166, .12);--aksrt-indigo: #6366f1;--aksrt-indigo-soft:rgba(99, 102, 241, .12);--aksrt-pink: #ec4899;--aksrt-pink-soft: rgba(236, 72, 153, .12);--aksrt-vip-gold-1: #f6d365;--aksrt-vip-gold-2: #fda085;--akLine truncated
|
||||
:root{--color-primary: #f04494;--color-primary-hover: #e0307e;--color-primary-active: #c9206d;--color-primary-soft: rgba(240, 68, 148, .1);--color-primary-softer: rgba(240, 68, 148, .05);--color-on-primary: #FFFFFF;--color-ink: #333333;--color-ink-2: #4e5358;--color-ink-3: #777777;--color-ink-4: #999999;--surface-page: #f5f6f7;--surface-1: #FFFFFF;--surface-2: #eeeeee;--surface-3: #e9ecef;--surface-4: #f8f8f8;--color-line: rgba(50, 50, 50, .06);--color-line-soft: rgba(0, 0, 0, .03);--color-line-stronger: rgba(50, 50, 50, .12);--color-success: #10B981;--color-warning: #F59E0B;--color-error: #EF4444;--color-info: #3B82F6;--color-accent: #18a058;--color-accent-soft: rgba(24, 160, 88, .12);--color-accent-shadow: rgba(24, 160, 88, .28);--font-display: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;--font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;--font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;--font-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;--text-xs: .75rem;--text-sm: .875rem;--text-base: .875rem;--text-md: 1.125rem;--text-lg: 1.25rem;--text-xl: 1.5rem;--text-2xl: 1.875rem;--text-3xl: 2.25rem;--text-4xl: 3rem;--text-5xl: 3.75rem;--leading-tight: 1.2;--leading-snug: 1.4;--leading-normal: 1.6;--leading-relaxed: 1.6;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--space-5: 20px;--space-6: 24px;--space-7: 32px;--space-8: 40px;--space-9: 48px;--space-10: 64px;--space-11: 80px;--space-12: 96px;--radius-xs: 3px;--radius-sm: 4px;--radius: 8px;--radius-lg: 8px;--radius-xl: 12px;--radius-2xl: 16px;--radius-pill: 100px;--radius-avatar: 50%;--shadow-xs: 0 1px 2px rgba(0, 0, 0, .03);--shadow-sm: 0 0 10px rgba(116, 116, 116, .08);--shadow: 0 0 10px rgba(116, 116, 116, .12);--shadow-lg: 0 12px 32px rgba(18, 18, 18, .1);--shadow-drawer: 0 18px 60px rgba(0, 0, 0, .2);--nav-height: 60px;--container: 1200px;--container-wide: 1440px;--container-read: 760px;--container-page: 880px;--sidebar-width: 311px;--content-gap: 24px;--section-gap: 24px;--sidebar-bp: 992px;--ease: cubic-bezier(.4, 0, .2, 1);--ease-out: cubic-bezier(.16, 1, .3, 1);--ease-spring: cubic-bezier(.34, 1.56, .64, 1);--dur-fast: .15s;--dur: .25s;--dur-slow: .4s;--focus-ring: 0 0 2px 3px rgba(253, 83, 161, .4);--z-dropdown: 100;--z-sticky: 200;--z-fixed: 300;--z-overlay: 400;--z-modal: 500;--z-popover: 600;--z-toast: 700;--z-tooltip: 800;--radius-btn: 4px;--btn-sm-h: 28px;--btn-md-h: 34px;--btn-lg-h: 44px;--card-padding: 15px;--card-padding-sm: 10px;--card-gap: 16px;--card-gap-lg: 20px;--color-primary-light: var(--color-primary-soft);--color-surface: var(--surface-1);--color-line-strong: var(--color-line);--color-bg-hover: var(--surface-2);--color-border-light: var(--color-line-soft);--color-fill: var(--surface-2);--color-warning-bg: #fffbe6;--color-warning-text: #ad6800;--text-secondary: var(--color-ink-3);--text-tertiary: var(--color-ink-4);--surface-active: var(--surface-2);--surface-card: var(--surface-1);--surface-hover: var(--surface-2);--surface-secondary: var(--surface-2);--radius-code: var(--radius);--radius-full: 100px;--shadow-xl: 0 0 10px rgba(116, 116, 116, .12);--space-16: 64px;--z-floating: 900;--transition-fast: .15s ease;--transition-base: .25s ease;--header-bg: var(--blur-bg);--header-color: var(--color-ink-2);--footer-bg: var(--surface-1);--footer-color: var(--color-ink-4);--blur-bg: rgba(255, 255, 255, .8);--float-btn-bg: rgba(200, 200, 200, .4);--font-heading: var(--font-ui);--radius-card: var(--radius);--radius-md: var(--radius);--color-primary-50: var(--color-primary-soft);--color-primary-dark: var(--color-primary-active);--shadow-md: var(--shadow);--mini-radius: calc(var(--radius) / 1.4);--aksrt-avatar-size-xs: 24px;--aksrt-avatar-size-sm: 32px;--aksrt-avatar-size: 40px;--aksrt-avatar-size-lg: 56px;--aksrt-avatar-size-xl: 80px;--aksrt-card-thumb-scale: 56.25%;--aksrt-rose: #f43f5e;--aksrt-rose-soft: rgba(244, 63, 94, .12);--aksrt-blue: #3b82f6;--aksrt-blue-soft: rgba(59, 130, 246, .12);--aksrt-green: #10b981;--aksrt-green-soft: rgba(16, 185, 129, .12);--aksrt-orange: #f97316;--aksrt-orange-soft:rgba(249, 115, 22, .12);--aksrt-purple: #8b5cf6;--aksrt-purple-soft:rgba(139, 92, 246, .12);--aksrt-cyan: #06b6d4;--aksrt-cyan-soft: rgba(6, 182, 212, .12);--aksrt-yellow: #eab308;--aksrt-yellow-soft:rgba(234, 179, 8, .12);--aksrt-red: #ef4444;--aksrt-red-soft: rgba(239, 68, 68, .12);--aksrt-teal: #14b8a6;--aksrt-teal-soft: rgba(20, 184, 166, .12);--aksrt-indigo: #6366f1;--aksrt-indigo-soft:rgba(99, 102, 241, .12);--aksrt-pink: #ec4899;--aksrt-pink-soft: rgba(236, 72, 153, .12);--aksrt-vip-gold-1: #f6d365;--aksrt-vip-gold-2: #fda085;--aksrt-grad-sunset-1: #f093fb;--aksrt-grad-sunset-2: #f5576c;--aksrt-graLine truncated
|
||||
@@ -137,6 +137,7 @@
|
||||
|
||||
/* === Full width === */
|
||||
.btn-block {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -182,6 +183,8 @@
|
||||
.but > .fa:not(.fa-angle-right),
|
||||
.but > i[class*="fa-"]:not(.fa-angle-right) { margin-right: 0.25em; }
|
||||
|
||||
.but.block { display: block; }
|
||||
|
||||
/* hover / active */
|
||||
.but:focus,
|
||||
.but:hover { opacity: 0.8; color: var(--this-color); }
|
||||
@@ -313,7 +316,7 @@
|
||||
|
||||
/* gradient modifiers (legacy .jb-*) */
|
||||
.jb-red { --this-bg: linear-gradient(135deg, #fd7a64 10%, #fb2d2d 100%); }
|
||||
.jb-yellow { --this-bg: linear-gradient(135deg, #f99d4d 10%, #f7631d 100%); }
|
||||
.jb-yellow { --this-bg: linear-gradient(135deg, #f59f54 10%, #ff6922 100%); }
|
||||
.jb-blue { --this-bg: linear-gradient(135deg, #59c3fb 10%, #268df7 100%); }
|
||||
.jb-green { --this-bg: linear-gradient(135deg, #60e464 10%, #5cb85b 100%); }
|
||||
.jb-purple { --this-bg: linear-gradient(135deg, #f98dfb 10%, #ea00f9 100%); }
|
||||
|
||||
@@ -424,6 +424,22 @@
|
||||
.aksrt-avatar--lg { width: var(--aksrt-avatar-size-lg); height: var(--aksrt-avatar-size-lg); }
|
||||
.aksrt-avatar--xl { width: var(--aksrt-avatar-size-xl); height: var(--aksrt-avatar-size-xl); }
|
||||
|
||||
/* ============================================================
|
||||
* 8b. 头像形状统一 — 圆形基底
|
||||
* --radius-avatar 是前端用户头像形状的唯一来源。任何带头像类名的
|
||||
* <img>(含未包 .avatar-img 的裸 img)都必须是正圆,供后续头像
|
||||
* 挂件/装扮叠加。特异性 0,1,1 高于 main.css 的 .avatar{100px},
|
||||
* 同值为圆;路由 CSS 中显式声明圆角的头像框需各自对齐。
|
||||
* ============================================================ */
|
||||
img.avatar,
|
||||
img.avatar-mini,
|
||||
img.avatar-md,
|
||||
img.avatar-lg,
|
||||
.avatar-img img {
|
||||
border-radius: var(--radius-avatar, 50%);
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 9. 涟漪点击效果 — .aksrt-ripple
|
||||
* 容器加此类,点击时 JS 注入 span.aksrt-ripple__wave
|
||||
|
||||
@@ -141,10 +141,6 @@
|
||||
--card-gap: 16px;
|
||||
--card-gap-lg: 20px;
|
||||
|
||||
/* ===== Skeleton loader ===== */
|
||||
--skeleton-base: var(--surface-2);
|
||||
--skeleton-shine: var(--surface-1);
|
||||
|
||||
/* ===== Extended tokens (used by components) ===== */
|
||||
--color-primary-light: var(--color-primary-soft);
|
||||
--color-surface: var(--surface-1);
|
||||
@@ -372,9 +368,6 @@ body.dark-theme {
|
||||
--footer-color: var(--color-ink-4);
|
||||
--shadow-md: var(--shadow);
|
||||
|
||||
--skeleton-base: #3a3b3e;
|
||||
--skeleton-shine: #444548;
|
||||
|
||||
/* Multi-color palette — dark mode: same hue, softer tint backgrounds */
|
||||
--aksrt-rose-soft: rgba(244, 63, 94, 0.18);
|
||||
--aksrt-blue-soft: rgba(59, 130, 246, 0.18);
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
/* ========== AJAX 加载指示器 — 对齐 legacy loading spinner ========== */
|
||||
.aksrt-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px 20px;
|
||||
color: var(--color-ink-4, #999);
|
||||
font-size: 13px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aksrt-loading__spinner {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid var(--surface-3, #e8e8e8);
|
||||
border-top-color: var(--color-primary, #f04494);
|
||||
border-radius: 50%;
|
||||
animation: aksrt-spin 0.6s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes aksrt-spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 全屏加载遮罩 */
|
||||
.aksrt-loading-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.aksrt-loading-overlay__content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.aksrt-loading-overlay__spinner {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border: 3px solid var(--surface-3, #e8e8e8);
|
||||
border-top-color: var(--color-primary, #f04494);
|
||||
border-radius: 50%;
|
||||
animation: aksrt-spin 0.6s linear infinite;
|
||||
margin: 0 auto 12px;
|
||||
}
|
||||
|
||||
.aksrt-loading-overlay__text {
|
||||
font-size: 13px;
|
||||
color: var(--color-ink-3, #777);
|
||||
}
|
||||
|
||||
/* 行内加载(替换内容时) */
|
||||
.aksrt-loading-inline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
color: var(--color-ink-4, #999);
|
||||
font-size: 12px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.aksrt-loading-inline__dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: var(--color-primary, #f04494);
|
||||
border-radius: 50%;
|
||||
animation: aksrt-dot-pulse 1.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.aksrt-loading-inline__dot:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.aksrt-loading-inline__dot:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
@keyframes aksrt-dot-pulse {
|
||||
0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
|
||||
40% { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
|
||||
/* 页面加载进度条 */
|
||||
.aksrt-progress-bar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 2px;
|
||||
background: var(--color-primary, #f04494);
|
||||
z-index: 10000;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
/* 减弱动画偏好 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.aksrt-loading__spinner,
|
||||
.aksrt-loading-overlay__spinner {
|
||||
animation-duration: 1.5s;
|
||||
}
|
||||
|
||||
.aksrt-loading-inline__dot {
|
||||
animation: none;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
@@ -242,7 +242,8 @@ main.container,
|
||||
.user-card.author .user-name > name {
|
||||
overflow: visible;
|
||||
}
|
||||
.user-card.author .user-name > name > .display-name {
|
||||
.user-card.author .user-name > name > .display-name,
|
||||
.user-card.widget .user-name > name > .display-name {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
@import url("components/base.css");
|
||||
@import url("components/reward-amounts.css");
|
||||
@import url("components/custom-amount.css");
|
||||
@@ -1,147 +0,0 @@
|
||||
/* ============================================================
|
||||
* 8. 打赏弹窗
|
||||
* ============================================================ */
|
||||
.reward-modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 99999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.3s, visibility 0.3s;
|
||||
}
|
||||
|
||||
.reward-modal.is-open {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.reward-modal__overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.reward-modal__card {
|
||||
position: relative;
|
||||
background: var(--surface-1, #fff);
|
||||
border-radius: var(--radius-lg, 8px);
|
||||
padding: 32px;
|
||||
max-width: 360px;
|
||||
width: 90%;
|
||||
text-align: center;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.15);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.reward-modal__title {
|
||||
margin: 0 0 8px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.reward-modal__desc {
|
||||
margin: 0 0 20px;
|
||||
font-size: 14px;
|
||||
color: var(--color-ink-3);
|
||||
}
|
||||
|
||||
.reward-modal__qr {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
margin: 0 auto 16px;
|
||||
border-radius: var(--radius, 10px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.reward-modal__qr img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.reward-modal__close {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: var(--surface-2, #F4F5F7);
|
||||
color: var(--color-ink-3);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.reward-modal__close:hover {
|
||||
background: var(--surface-3, #ECEEF1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.reward-modal__content > p {
|
||||
margin: -4px 0 18px;
|
||||
}
|
||||
|
||||
.reward-amounts {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
margin: 18px 0;
|
||||
}
|
||||
|
||||
.reward-amount {
|
||||
min-height: 42px;
|
||||
border: 1px solid var(--color-line-soft, rgba(15, 23, 42, 0.1));
|
||||
border-radius: 10px;
|
||||
background: var(--surface-2);
|
||||
color: var(--color-ink-2);
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.reward-amount.is-active {
|
||||
border-color: var(--color-primary);
|
||||
background: var(--color-primary-soft);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.reward-custom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
color: var(--color-ink-3);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.reward-custom input {
|
||||
width: 140px;
|
||||
min-height: 40px;
|
||||
border: 1px solid var(--color-line-soft, rgba(15, 23, 42, 0.1));
|
||||
border-radius: 9px;
|
||||
background: var(--surface-1);
|
||||
color: var(--color-ink);
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.reward-modal__submit {
|
||||
width: 100%;
|
||||
min-height: 42px;
|
||||
}
|
||||
|
||||
.reward-modal__message {
|
||||
min-height: 20px;
|
||||
margin-top: 10px;
|
||||
color: var(--color-primary);
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -1,54 +1,3 @@
|
||||
/* ---- custom amount ---- */
|
||||
.reward-custom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.reward-custom__input {
|
||||
flex: 1;
|
||||
height: 40px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid var(--color-line, #e5e7eb);
|
||||
border-radius: var(--radius, 10px);
|
||||
background: var(--surface-2, #F4F5F7);
|
||||
color: var(--color-ink);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
outline: none;
|
||||
transition: border-color 0.2s;
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
.reward-custom__input::-webkit-inner-spin-button,
|
||||
.reward-custom__input::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.reward-custom__input:focus {
|
||||
border-color: var(--color-primary, #f04494);
|
||||
}
|
||||
|
||||
.reward-custom__confirm {
|
||||
flex-shrink: 0;
|
||||
height: 40px;
|
||||
padding: 0 20px;
|
||||
border: none;
|
||||
border-radius: var(--radius, 10px);
|
||||
background: var(--color-primary, #f04494);
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.reward-custom__confirm:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 9. 公告跑马灯 Widget
|
||||
* ============================================================ */
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
/* ---- reward amounts ---- */
|
||||
.reward-amounts {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.reward-amounts__btn {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 2px;
|
||||
padding: 8px 16px;
|
||||
border: 1px solid var(--color-line, #e5e7eb);
|
||||
border-radius: var(--radius, 10px);
|
||||
background: var(--surface-2, #F4F5F7);
|
||||
color: var(--color-ink);
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.reward-amounts__btn:hover {
|
||||
border-color: var(--color-primary, #f04494);
|
||||
background: rgba(240, 68, 148, 0.06);
|
||||
}
|
||||
|
||||
.reward-amounts__btn.is-active {
|
||||
border-color: var(--color-primary, #f04494);
|
||||
background: var(--color-primary, #f04494);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.reward-amounts__currency {
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.reward-amounts__value {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
/**
|
||||
* AKSRT Enhancements — 全局 UI 增强
|
||||
*
|
||||
* 包含:骨架屏、悬停动效、暗色过渡、滚动感知
|
||||
* 包含:悬停动效、暗色过渡、滚动感知
|
||||
* 所有选择器使用 aksrt- 前缀避免冲突
|
||||
*
|
||||
* 骨架屏不在此文件:唯一契约是 .placeholder 块 + .post_ajax_loader 容器
|
||||
* (inc/helpers/posts-renderer/markup.php 的 aksrt_render_placeholder)。
|
||||
*/
|
||||
|
||||
@keyframes aksrt-spin {
|
||||
@@ -11,79 +14,7 @@
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 1. 骨架屏加载动画
|
||||
* ============================================================ */
|
||||
.aksrt-skeleton {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(121, 121, 121, 0.08) 25%,
|
||||
rgba(121, 121, 121, 0.15) 37%,
|
||||
rgba(121, 121, 121, 0.08) 63%
|
||||
);
|
||||
background-size: 400% 100%;
|
||||
animation: aksrt-skeleton-shimmer 1.5s ease infinite;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.aksrt-skeleton--image {
|
||||
height: 0;
|
||||
padding-bottom: 66.67%; /* 3:2 aspect ratio */
|
||||
}
|
||||
|
||||
.aksrt-skeleton--avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.aksrt-skeleton--title {
|
||||
height: 22px;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.aksrt-skeleton--text {
|
||||
height: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.aksrt-skeleton--text-short {
|
||||
height: 16px;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
.aksrt-skeleton--button {
|
||||
height: 36px;
|
||||
width: 100px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
@keyframes aksrt-skeleton-shimmer {
|
||||
0% { background-position: 100% 50%; }
|
||||
100% { background-position: 0 50%; }
|
||||
}
|
||||
|
||||
/* 骨架屏组合:文章卡片骨架 */
|
||||
.aksrt-skeleton-card {
|
||||
background: var(--surface-1, #fff);
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.aksrt-skeleton-card .aksrt-skeleton--image {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.aksrt-skeleton-card__body {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 2. 悬停交互动效
|
||||
* 1. 悬停交互动效
|
||||
* ============================================================ */
|
||||
|
||||
/* 卡片放大 */
|
||||
|
||||
@@ -14,6 +14,22 @@
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
/* 前台页面编辑齿轮的提示横条:参考主题共用 .abs-right(top:50% + right:.7em +
|
||||
* translateY(-50%) 的垂直居中定位)。本站 legacy-utilities 的 .abs-right 是
|
||||
* top:0/right:0,横条会贴到按钮顶角,故在此恢复参考几何。 */
|
||||
.float-right .admin-set-page .admin-set-info {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0.7em;
|
||||
transform: translateY(-50%);
|
||||
white-space: nowrap;
|
||||
line-height: 1.5;
|
||||
margin: 0;
|
||||
box-shadow: none;
|
||||
text-align: center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
body:has(.modal.in) .float-right,
|
||||
.modal-open .float-right,
|
||||
.imgbox-show .float-right,
|
||||
|
||||
@@ -1,31 +1,35 @@
|
||||
/**
|
||||
* Header account popover.
|
||||
*
|
||||
* The shared legacy navigation styles provide the popover shell. This module
|
||||
* owns AKSRT-specific account content such as check-in, wallet and VIP rows.
|
||||
* The shared legacy navigation styles provide the popover shell, and every
|
||||
* account row (avatar, VIP bar, check-in, wallet tiles, stats badges) renders
|
||||
* through the reference legacy classes. This module only owns the navigation
|
||||
* shell anchoring plus the account action-row geometry.
|
||||
*/
|
||||
|
||||
/* The desktop action cluster owns one horizontal alignment context. */
|
||||
.navbar-top .aksrt-navbar-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-height: 50px;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.navbar-top .aksrt-navbar-actions > *,
|
||||
.navbar-top .aksrt-navbar-account,
|
||||
.navbar-top .aksrt-navbar-account > li {
|
||||
/* The reference keeps the right cluster as three floated .navbar-form groups.
|
||||
* Bootstrap's base .navbar-form rule carries margin-top: 8px and the >=768px
|
||||
* override only zeroes the horizontal margins, so every right-cluster group
|
||||
* starts 8px below the top of .navbar-collapse. The 50px menu row and the logo
|
||||
* put the navbar midline at 25px, so a 34px cross-size lands a group's midline
|
||||
* at 8 + 17 = 25px. The publish/VIP chips already reach that line through their
|
||||
* own 0.4em margin stacked on the group's 8px; the account row reaches it from
|
||||
* its 34px icon tiles, and the theme group needs the same cross-size because
|
||||
* its 29px toggle is otherwise 2.5px high. Nothing here may restate a 50px
|
||||
* min-height: it would double-count the 8px and push that group's content 8px
|
||||
* below the line while inflating the navbar from 50px to 58px. */
|
||||
.navbar-top .aksrt-navbar-account {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
gap: 4px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.navbar-top .aksrt-navbar-account > li {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
padding: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
@@ -38,39 +42,6 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.navbar-top .aksrt-navbar-account {
|
||||
gap: 4px;
|
||||
min-height: 50px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.navbar-top .aksrt-navbar-account.navbar-text {
|
||||
float: none;
|
||||
}
|
||||
|
||||
.navbar-top .aksrt-navbar-actions .navbar-cta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
min-height: 29px;
|
||||
margin: 0;
|
||||
padding: .3em 1em !important;
|
||||
border-radius: var(--radius-btn, 4px);
|
||||
font-size: .8em;
|
||||
line-height: 1.44;
|
||||
}
|
||||
|
||||
.navbar-top .aksrt-navbar-actions .navbar-cta.radius {
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
.navbar-top .aksrt-navbar-actions .navbar-cta > .icon {
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.navbar-top .aksrt-nav-icon,
|
||||
.navbar-top .aksrt-nav-avatar {
|
||||
display: inline-flex !important;
|
||||
@@ -103,33 +74,34 @@
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.navbar-top .aksrt-navbar-actions .toggle-radius {
|
||||
/* The account-row chips, the theme and the language toggles all want the 29px
|
||||
* midline circle. The popover's quick-login buttons are .toggle-radius too and
|
||||
* also live inside .navbar-form, but they must keep the .social_loginbar
|
||||
* geometry from main.css: in a flex container flex-basis outranks width, so
|
||||
* without this exemption the buttons collapse to 29px and lose the 5px side
|
||||
* margins that space them apart. */
|
||||
.navbar-top .navbar-form .toggle-radius:not(.social-login-item):not(.xh-social-item) {
|
||||
flex: 0 0 29px;
|
||||
margin: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.navbar-top .aksrt-navbar-language {
|
||||
/* Match the account row's 34px cross-size so the 29px toggle shares the 25px
|
||||
* midline instead of sitting 2.5px above it. */
|
||||
.navbar-top .navbar-form.flex.ac {
|
||||
min-height: 34px;
|
||||
}
|
||||
|
||||
.navbar-top .navbar-form .aksrt-navbar-language {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Anchor the VIP preview to the full navigation row instead of relying on
|
||||
* the absolute panel's static position beside the compact CTA. */
|
||||
.navbar-top .aksrt-navbar-actions .navbar-vip-hover {
|
||||
position: relative;
|
||||
align-self: stretch;
|
||||
align-items: center;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.navbar-top .aksrt-navbar-actions .navbar-vip-hover > .sub-vip-card {
|
||||
top: 100%;
|
||||
right: 0;
|
||||
left: auto;
|
||||
width: max-content;
|
||||
max-width: calc(100vw - 24px);
|
||||
margin: 0;
|
||||
z-index: 1002;
|
||||
/* The reference keeps the publish/VIP hover wrapper a plain inline-block, so the
|
||||
* absolutely positioned .sub-vip-card takes its normal-flow static position.
|
||||
* The shared action cluster otherwise forces inline-flex on every child, which
|
||||
* would move that static position to the flex container's cross-start. */
|
||||
.navbar-top .navbar-but > .hover-show {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Keep the drop-down search panel above a fixed header (z-index: 999). */
|
||||
@@ -165,85 +137,41 @@
|
||||
}
|
||||
|
||||
.aksrt-header-userbox {
|
||||
width: 260px;
|
||||
color: var(--color-ink-2);
|
||||
line-height: 1.42857143;
|
||||
}
|
||||
|
||||
.aksrt-header-userbox .user-info {
|
||||
min-height: 44px;
|
||||
padding: 2px;
|
||||
/* public.css redeclares .muted-box after the legacy utility rules, which
|
||||
* otherwise overrides the wallet tiles' padding-6 and radius4 utilities. */
|
||||
.aksrt-header-userbox .muted-box.padding-6 {
|
||||
background-color: var(--this-bg, var(--color-line-soft));
|
||||
border-radius: 4px;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.aksrt-header-userbox .user-info > .avatar {
|
||||
flex: 0 0 44px;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
object-fit: cover;
|
||||
/* core/buttons.css restates the legacy .but block after main.css, so its
|
||||
* background / color / border / text-shadow declarations outrank .payvip-icon
|
||||
* and replace the payvip surface with whatever .jb-* skin is on the element.
|
||||
* The reference theme has no such compatibility layer, so its .payvip-icon
|
||||
* declarations are the last word in the cascade. Re-assert them here, which
|
||||
* loads after core/buttons.css, so every payvip surface (top-nav chip, user
|
||||
* card status bar and CTA, guest CTA) keeps the legacy metallic gradient. */
|
||||
.payvip-icon {
|
||||
background: linear-gradient(300deg, #4c4d51, #2a2a31 15%, #85858a 40%, #393a3c 60%, #393838 80%, #5e5f62 100%);
|
||||
color: #ece0e0;
|
||||
--this-color: #ece0e0;
|
||||
border: none;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.aksrt-header-userbox .user-right,
|
||||
.aksrt-header-userbox .user-right > .flex1 {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.aksrt-header-userbox .user-right b {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.aksrt-header-userbox .display-name {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.aksrt-header-userbox .user-right .img-icon,
|
||||
.aksrt-header-userbox .user-right .aksrt-user-level-icon,
|
||||
.aksrt-header-userbox .user-right .aksrt-identity-medal,
|
||||
.aksrt-header-userbox .user-right .aksrt-header-user-medal,
|
||||
.aksrt-header-userbox .user-right .aksrt-identity-certification-icon {
|
||||
flex: 0 0 auto;
|
||||
height: 1.1em;
|
||||
}
|
||||
|
||||
.aksrt-header-userbox .user-info > .abs-right {
|
||||
top: 50%;
|
||||
right: 2px;
|
||||
transform: translateY(-50%);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
background: var(--surface-2);
|
||||
color: var(--color-ink-3);
|
||||
}
|
||||
|
||||
.aksrt-header-userbox .aksrt-notification-badge {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: -4px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 17px;
|
||||
height: 17px;
|
||||
padding: 0 4px;
|
||||
border: 2px solid var(--surface-1);
|
||||
border-radius: 999px;
|
||||
background: #e74c3c;
|
||||
color: #fff;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
/* Keep the header check-in action flush with the VIP row's padded inner width. */
|
||||
.aksrt-header-userbox > .mt6.em09 > .but.block {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Top-nav bell badge (the card itself uses the legacy badge.top element). */
|
||||
.aksrt-nav-notification .aksrt-notification-badge {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
@@ -263,220 +191,18 @@
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.aksrt-header-wallet {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
padding: 9px 10px;
|
||||
border-radius: 8px;
|
||||
background: var(--surface-2);
|
||||
color: var(--color-ink-2);
|
||||
font-size: 12px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.aksrt-header-wallet:hover {
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.aksrt-header-wallet__item {
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.aksrt-header-wallet__item i {
|
||||
margin-right: 2px;
|
||||
color: var(--color-ink-3);
|
||||
}
|
||||
|
||||
.aksrt-header-wallet__item b {
|
||||
margin-left: 2px;
|
||||
color: var(--color-ink);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.aksrt-header-checkin {
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
width: 100% !important;
|
||||
min-height: 36px;
|
||||
margin: 0;
|
||||
padding: 8px 10px !important;
|
||||
border: 0 !important;
|
||||
border-radius: 6px !important;
|
||||
background: #fff6db !important;
|
||||
color: #8a5a00 !important;
|
||||
font-size: 13px !important;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.aksrt-header-checkin > i {
|
||||
margin: 0 !important;
|
||||
color: #b78103 !important;
|
||||
}
|
||||
|
||||
.aksrt-header-checkin:hover {
|
||||
background: #fff0c2 !important;
|
||||
color: #8a5a00 !important;
|
||||
}
|
||||
|
||||
/* 已签到态对齐参考主题:保留原配色(参考主题签到后仍为同一彩色按钮),
|
||||
* 且按钮可点击(打开签到详情弹窗);仅请求进行中的禁用态降不透明度。 */
|
||||
.aksrt-header-checkin:disabled,
|
||||
.aksrt-header-checkin:disabled:hover {
|
||||
cursor: default;
|
||||
opacity: .85;
|
||||
}
|
||||
|
||||
.aksrt-header-payvip__cta,
|
||||
.aksrt-header-payvip:not(.is-vip) .payvip-icon,
|
||||
.aksrt-header-payvip:not(.is-vip) .but {
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
gap: 6px !important;
|
||||
width: 100% !important;
|
||||
min-width: 0 !important;
|
||||
min-height: 36px;
|
||||
margin: 0 !important;
|
||||
padding: 8px 10px !important;
|
||||
border: 0 !important;
|
||||
border-radius: 6px !important;
|
||||
background: linear-gradient(90deg, #ff6b6b, #f04494) !important;
|
||||
color: #fff !important;
|
||||
font-size: 13px !important;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.aksrt-header-payvip__cta > i,
|
||||
.aksrt-header-payvip:not(.is-vip) .payvip-icon > i,
|
||||
.aksrt-header-payvip:not(.is-vip) .but > i {
|
||||
margin: 0 !important;
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
.aksrt-header-payvip__cta:hover {
|
||||
color: #fff !important;
|
||||
opacity: .92;
|
||||
}
|
||||
|
||||
.aksrt-header-payvip__active {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px !important;
|
||||
width: 100%;
|
||||
min-height: 42px;
|
||||
padding: 7px 10px !important;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.aksrt-header-payvip__level {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.aksrt-header-payvip__level > i {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.aksrt-header-payvip__exp {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: none !important;
|
||||
box-shadow: none;
|
||||
color: inherit;
|
||||
font-size: .85em;
|
||||
font-weight: 400;
|
||||
line-height: 1.3;
|
||||
opacity: .9;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Navigation links are globally capped at 50px. Account actions are taller
|
||||
* icon tiles and must opt out or their labels overlap the following row. */
|
||||
.navbar-top .aksrt-header-userbox .header-user-href {
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
* icon tiles and must opt out or their labels overlap the following row.
|
||||
* The tile geometry itself stays with the shared legacy rules
|
||||
* (.header-user-href > a and .header-user-href .toggle-radius in main.css)
|
||||
* so the action row keeps the reference proportions. */
|
||||
.navbar-top .aksrt-header-userbox .header-user-href > a,
|
||||
.aksrt-header-userbox .header-user-href > a {
|
||||
display: flex;
|
||||
flex: 1 1 0;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
max-height: none;
|
||||
padding: 10px 2px 2px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.aksrt-header-userbox .header-user-href > a:hover {
|
||||
opacity: .75;
|
||||
}
|
||||
|
||||
.aksrt-header-userbox .header-user-href .toggle-radius {
|
||||
flex: 0 0 36px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.aksrt-header-userbox .header-user-href > a > div:last-child {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
font-size: 12px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mobile-navbar .aksrt-header-userbox {
|
||||
width: 100%;
|
||||
width: auto;
|
||||
max-width: 400px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
body.dark-theme .aksrt-header-checkin {
|
||||
background: rgba(255, 193, 7, .18) !important;
|
||||
color: #f0c14b !important;
|
||||
}
|
||||
|
||||
body.dark-theme .aksrt-header-checkin > i {
|
||||
color: #f0c14b !important;
|
||||
}
|
||||
|
||||
body.dark-theme .aksrt-header-wallet {
|
||||
background: rgba(255, 255, 255, .06);
|
||||
}
|
||||
|
||||
body.dark-theme .aksrt-header-wallet__item,
|
||||
body.dark-theme .aksrt-header-wallet__item b {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
@media (max-width: 360px) {
|
||||
.aksrt-header-userbox .header-user-href {
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.aksrt-header-userbox .header-user-href .toggle-radius {
|
||||
flex-basis: 34px;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
}
|
||||
margin: auto;
|
||||
}
|
||||
@@ -483,44 +483,10 @@ body.site-layout-3 .sidebar,
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* AJAX 筛选加载状态 */
|
||||
.archive__ajax-wrap {
|
||||
position: relative;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.archive__ajax-wrap.is-loading {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.archive__ajax-loading {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 5;
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
backdrop-filter: blur(1px);
|
||||
}
|
||||
|
||||
.archive__ajax-loading.is-loading {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.archive__ajax-spinner {
|
||||
display: inline-block;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: 3px solid var(--surface-3, #e8e8e8);
|
||||
border-top-color: var(--color-primary, #f04494);
|
||||
border-radius: 50%;
|
||||
animation: aksrt-spin 0.6s linear infinite;
|
||||
}
|
||||
/* 归档面 AJAX 筛选的加载态与首页共用同一套骨架契约:
|
||||
.post_ajax_loader 容器 + .is-loading 开关(见 frontend/modal-runtime.css)。
|
||||
原先本文件另有一套独立 spinner 遮罩与 opacity 淡出,已删除 ——
|
||||
全站只有一套加载语言。 */
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.term-page-header__body {
|
||||
|
||||
@@ -106,10 +106,20 @@
|
||||
}
|
||||
|
||||
.messages-center.is-loading .messages-center__content {
|
||||
opacity: 0.55;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 加载态由消息骨架承担(与文章列表 .post_ajax_loader 同一契约),
|
||||
不再让按钮 spinner 与内容透明度抢反馈。 */
|
||||
.messages-list > .post_ajax_loader {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.messages-list > .post_ajax_loader.is-loading {
|
||||
display: block;
|
||||
animation: aksrtFadeIn 0.2s ease both;
|
||||
}
|
||||
|
||||
.messages-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -455,22 +465,36 @@ html.dark .message-detail {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.messages-center.is-loading .messages-pagination__more span::before {
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-right: 7px;
|
||||
border: 2px solid currentColor;
|
||||
border-right-color: transparent;
|
||||
border-radius: 50%;
|
||||
content: "";
|
||||
vertical-align: -2px;
|
||||
animation: messages-pagination-spin 0.7s linear infinite;
|
||||
/* 消息骨架:复用真实 .message-item 结构与 .placeholder 动画契约,
|
||||
只把内容换成灰块(参考主题 post_ajax loader 的同构思路)。 */
|
||||
.messages-list > .post_ajax_loader .message-item--placeholder {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@keyframes messages-pagination-spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
.messages-list > .post_ajax_loader .message-item--placeholder .message-item__icon {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.messages-list > .post_ajax_loader .message-item--placeholder .message-item__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.messages-list > .post_ajax_loader .message-item--placeholder .placeholder {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.messages-list > .post_ajax_loader .message-item--placeholder .placeholder.s1 {
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.messages-list > .post_ajax_loader .message-item--placeholder .message-item__actions {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.messages-list > .post_ajax_loader .placeholder {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,10 +503,6 @@ html.dark .message-detail {
|
||||
.messages-pagination__more {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.messages-center.is-loading .messages-pagination__more span::before {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 响应式 ---- */
|
||||
|
||||
@@ -257,17 +257,18 @@ body.aksrt-modal-runtime-open {
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
/* 骨架默认收起,由 pagination.js 的 showSkeleton() 加 .is-loading 展开。
|
||||
这里不依赖 .ajaxpager 祖先:首页 tab pane、归档流、评论列表的 loader
|
||||
层级各不相同,统一按类名自身控制。 */
|
||||
.post_ajax_loader,
|
||||
.post-ajax-loader {
|
||||
/* 骨架默认收起,由 JS 加 .is-loading 展开(参考主题 post_ajax 的 beforeSend
|
||||
remove + fadeIn / success .hide() 语义等价物)。
|
||||
这里不依赖 .ajaxpager 祖先:首页 tab pane、归档流、评论列表的 loader 层级各不
|
||||
相同,而且并非每个 loader 都在 bootstrap 的 .tab-pane 里(bootstrap 只负责
|
||||
隐藏 tab-pane 内的骨架),统一按类名自身控制。 */
|
||||
.post_ajax_loader {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.post_ajax_loader.is-loading,
|
||||
.post-ajax-loader.is-loading {
|
||||
.post_ajax_loader.is-loading {
|
||||
display: block;
|
||||
animation: aksrtFadeIn 0.2s ease both;
|
||||
}
|
||||
|
||||
.ajax-item.is-fading {
|
||||
@@ -551,14 +552,15 @@ body.aksrt-modal-runtime-open {
|
||||
color: var(--color-ink, #111);
|
||||
}
|
||||
|
||||
:is(.order-comment-from, .aksrt-report-modal) .form-upload .preview {
|
||||
:is(.order-comment-from, .aksrt-report-modal, .aksrt-after-sale-apply-modal) .form-upload .preview {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
:is(.order-comment-from, .aksrt-report-modal) .form-upload .preview .add,
|
||||
:is(.order-comment-from, .aksrt-report-modal) .form-upload .preview .preview-item {
|
||||
:is(.order-comment-from, .aksrt-report-modal, .aksrt-after-sale-apply-modal) .form-upload .preview .add,
|
||||
:is(.order-comment-from, .aksrt-report-modal, .aksrt-after-sale-apply-modal) .form-upload .preview .preview-item {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 10px;
|
||||
@@ -574,14 +576,14 @@ body.aksrt-modal-runtime-open {
|
||||
此前这里的 left/top:50% + translate 版本与该规则叠加导致过约束,
|
||||
加号被推到 75%/75% 只剩“L”形,故整体删除、统一走主样式包。 */
|
||||
|
||||
:is(.order-comment-from, .aksrt-report-modal) .form-upload .preview .preview-item img {
|
||||
:is(.order-comment-from, .aksrt-report-modal, .aksrt-after-sale-apply-modal) .form-upload .preview .preview-item img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
:is(.order-comment-from, .aksrt-report-modal) .form-upload .preview .preview-item .remove {
|
||||
:is(.order-comment-from, .aksrt-report-modal, .aksrt-after-sale-apply-modal) .form-upload .preview .preview-item .remove {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
@@ -596,7 +598,7 @@ body.aksrt-modal-runtime-open {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:is(.order-comment-from, .aksrt-report-modal) .form-upload .hide {
|
||||
:is(.order-comment-from, .aksrt-report-modal, .aksrt-after-sale-apply-modal) .form-upload .hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@@ -611,3 +613,313 @@ body.aksrt-modal-runtime-open {
|
||||
width: calc(100vw - 20px);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 售后弹窗(申请 / 售后详细 / 超时效提示,参考主题 modal-mini 几何 1:1)----
|
||||
三个弹窗与禁封申诉弹窗同几何:data_class='modal-mini'(宽 400px),圆角与
|
||||
底色走 --main-radius / --main-bg-color;移动端由 --bottom 底部抽屉接管。 */
|
||||
.aksrt-after-sale-apply-modal:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog,
|
||||
.aksrt-after-sale-detail-modal:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog,
|
||||
.aksrt-after-sale-expired-modal:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog,
|
||||
.aksrt-after-sale-withdraw-modal:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog {
|
||||
width: min(400px, calc(100vw - 32px));
|
||||
border-radius: var(--main-radius, 8px);
|
||||
background: var(--main-bg-color, #fff);
|
||||
}
|
||||
|
||||
.aksrt-after-sale-apply-modal .aksrt-modal-runtime__skeleton,
|
||||
.aksrt-after-sale-detail-modal .aksrt-modal-runtime__skeleton,
|
||||
.aksrt-after-sale-expired-modal .aksrt-modal-runtime__skeleton,
|
||||
.aksrt-after-sale-withdraw-modal .aksrt-modal-runtime__skeleton {
|
||||
padding: 24px 18px;
|
||||
}
|
||||
|
||||
/* 蓝色炫彩标题区(参考主题 zib_get_modal_colorful_header('jb-blue', ...)):120px
|
||||
绝对定位横幅 + 旋转彩斑,后续内容整体下移 120px,与禁封记录弹窗同构。 */
|
||||
.aksrt-after-sale-apply-modal .modal-colorful-header,
|
||||
.aksrt-after-sale-detail-modal .modal-colorful-header,
|
||||
.aksrt-after-sale-expired-modal .modal-colorful-header,
|
||||
.aksrt-after-sale-withdraw-modal .modal-colorful-header,
|
||||
.aksrt-order-detail-modal .modal-colorful-header,
|
||||
.aksrt-after-sale-apply-modal .colorful-bg,
|
||||
.aksrt-after-sale-detail-modal .colorful-bg,
|
||||
.aksrt-after-sale-expired-modal .colorful-bg,
|
||||
.aksrt-after-sale-withdraw-modal .colorful-bg,
|
||||
.aksrt-order-detail-modal .colorful-bg {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
color: var(--this-color);
|
||||
background: var(--this-bg);
|
||||
background-size: 120%;
|
||||
}
|
||||
|
||||
.aksrt-after-sale-apply-modal .modal-colorful-header,
|
||||
.aksrt-after-sale-detail-modal .modal-colorful-header,
|
||||
.aksrt-after-sale-expired-modal .modal-colorful-header,
|
||||
.aksrt-after-sale-withdraw-modal .modal-colorful-header,
|
||||
.aksrt-order-detail-modal .modal-colorful-header {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 120px;
|
||||
border-radius: var(--main-radius, 8px) var(--main-radius, 8px) 0 0;
|
||||
}
|
||||
|
||||
.aksrt-after-sale-apply-modal .modal-colorful-header + *,
|
||||
.aksrt-after-sale-detail-modal .modal-colorful-header + *,
|
||||
.aksrt-after-sale-expired-modal .modal-colorful-header + *,
|
||||
.aksrt-after-sale-withdraw-modal .modal-colorful-header + *,
|
||||
.aksrt-order-detail-modal .modal-colorful-header + * {
|
||||
margin-top: 120px;
|
||||
}
|
||||
|
||||
.aksrt-after-sale-apply-modal .modal-colorful-header > .close,
|
||||
.aksrt-after-sale-detail-modal .modal-colorful-header > .close,
|
||||
.aksrt-after-sale-expired-modal .modal-colorful-header > .close,
|
||||
.aksrt-after-sale-withdraw-modal .modal-colorful-header > .close,
|
||||
.aksrt-order-detail-modal .modal-colorful-header > .close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 9;
|
||||
color: var(--this-color);
|
||||
font-size: 18px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.aksrt-after-sale-apply-modal .colorful-bg > .colorful-make,
|
||||
.aksrt-after-sale-apply-modal .colorful-bg > .colorful-make::before,
|
||||
.aksrt-after-sale-apply-modal .colorful-bg > .colorful-make::after,
|
||||
.aksrt-after-sale-detail-modal .colorful-bg > .colorful-make,
|
||||
.aksrt-after-sale-detail-modal .colorful-bg > .colorful-make::before,
|
||||
.aksrt-after-sale-detail-modal .colorful-bg > .colorful-make::after,
|
||||
.aksrt-after-sale-expired-modal .colorful-bg > .colorful-make,
|
||||
.aksrt-after-sale-withdraw-modal .colorful-bg > .colorful-make,
|
||||
.aksrt-order-detail-modal .colorful-bg > .colorful-make,
|
||||
.aksrt-after-sale-expired-modal .colorful-bg > .colorful-make::before,
|
||||
.aksrt-after-sale-withdraw-modal .colorful-bg > .colorful-make::before,
|
||||
.aksrt-order-detail-modal .colorful-bg > .colorful-make::before,
|
||||
.aksrt-after-sale-expired-modal .colorful-bg > .colorful-make::after,
|
||||
.aksrt-after-sale-withdraw-modal .colorful-bg > .colorful-make::after,
|
||||
.aksrt-order-detail-modal .colorful-bg > .colorful-make::after {
|
||||
position: absolute;
|
||||
color: var(--this-color);
|
||||
background: var(--this-bg);
|
||||
border-radius: 100%;
|
||||
content: '';
|
||||
}
|
||||
|
||||
.aksrt-after-sale-apply-modal .colorful-bg > .colorful-make,
|
||||
.aksrt-after-sale-detail-modal .colorful-bg > .colorful-make,
|
||||
.aksrt-after-sale-expired-modal .colorful-bg > .colorful-make,
|
||||
.aksrt-after-sale-withdraw-modal .colorful-bg > .colorful-make,
|
||||
.aksrt-order-detail-modal .colorful-bg > .colorful-make {
|
||||
top: 60%;
|
||||
bottom: 0;
|
||||
width: 323px;
|
||||
height: 290px;
|
||||
animation: aksrt-ban-colorful 120s linear infinite;
|
||||
}
|
||||
|
||||
.aksrt-after-sale-apply-modal .colorful-bg > .colorful-make::before,
|
||||
.aksrt-after-sale-detail-modal .colorful-bg > .colorful-make::before,
|
||||
.aksrt-after-sale-expired-modal .colorful-bg > .colorful-make::before,
|
||||
.aksrt-after-sale-withdraw-modal .colorful-bg > .colorful-make::before,
|
||||
.aksrt-order-detail-modal .colorful-bg > .colorful-make::before {
|
||||
top: -325px;
|
||||
right: -558px;
|
||||
bottom: 0;
|
||||
width: 677px;
|
||||
height: 300px;
|
||||
opacity: 0.6;
|
||||
transform: rotate(341deg);
|
||||
}
|
||||
|
||||
.aksrt-after-sale-apply-modal .colorful-bg > .colorful-make::after,
|
||||
.aksrt-after-sale-detail-modal .colorful-bg > .colorful-make::after,
|
||||
.aksrt-after-sale-expired-modal .colorful-bg > .colorful-make::after,
|
||||
.aksrt-after-sale-withdraw-modal .colorful-bg > .colorful-make::after,
|
||||
.aksrt-order-detail-modal .colorful-bg > .colorful-make::after {
|
||||
top: -170px;
|
||||
bottom: 0;
|
||||
left: -262px;
|
||||
width: 402px;
|
||||
height: 214px;
|
||||
opacity: 0.4;
|
||||
transform: rotate(326deg);
|
||||
}
|
||||
|
||||
/* 售后原因徽章:与举报弹窗一致,可换行且间距走 mr6/mb6 工具类。 */
|
||||
.aksrt-after-sale-apply-modal .form-but-radio .badg {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* 订单摘要与售后明细行 */
|
||||
.aksrt-after-sale-summary__row,
|
||||
.aksrt-after-sale-detail__row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.aksrt-after-sale-summary__row:last-child,
|
||||
.aksrt-after-sale-detail__row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.aksrt-after-sale-summary__value,
|
||||
.aksrt-after-sale-detail__value {
|
||||
min-width: 0;
|
||||
text-align: right;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.aksrt-after-sale-detail__statusRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.aksrt-after-sale-reason-text {
|
||||
color: var(--color-ink-2, #4e5358);
|
||||
background: var(--surface-2, #eee);
|
||||
border-radius: 6px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.aksrt-after-sale-images {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aksrt-after-sale-images img {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
object-fit: cover;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--color-line, #e5e7eb);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.aksrt-after-sale-status.is-pending { color: var(--color-warning, #f59e0b); }
|
||||
.aksrt-after-sale-status.is-processing { color: var(--color-info, #3b82f6); }
|
||||
.aksrt-after-sale-status.is-approved,
|
||||
.aksrt-after-sale-status.is-completed { color: var(--color-success, #10b981); }
|
||||
.aksrt-after-sale-status.is-rejected,
|
||||
.aksrt-after-sale-status.is-cancelled { color: var(--color-error, #ef4444); }
|
||||
|
||||
/* 售后处理记录时间线:[节点][处理状态][时间] 一行,处理备注另起一行并对齐状态文字。
|
||||
售后详细弹窗与订单详情弹窗的「售后记录」共用同一套标记;竖线为条目的
|
||||
::before,末项不收口,避免出现悬空的线段。 */
|
||||
.aksrt-after-sale-log { display: grid; gap: 6px; }
|
||||
.aksrt-after-sale-log__empty { color: var(--modal-muted); font-size: 12px; }
|
||||
.aksrt-after-sale-timeline { display: grid; margin: 0; padding: 0; list-style: none; }
|
||||
.aksrt-after-sale-timeline__item { --as-accent: var(--modal-muted); position: relative; display: grid; grid-template-columns: 10px minmax(0, 1fr); gap: 10px; padding: 0 0 14px; }
|
||||
.aksrt-after-sale-timeline__item:last-child { padding-bottom: 0; }
|
||||
.aksrt-after-sale-timeline__item.is-pending { --as-accent: var(--modal-warning); }
|
||||
.aksrt-after-sale-timeline__item.is-processing { --as-accent: var(--modal-primary); }
|
||||
.aksrt-after-sale-timeline__item.is-completed { --as-accent: var(--modal-success); }
|
||||
.aksrt-after-sale-timeline__item.is-rejected,
|
||||
.aksrt-after-sale-timeline__item.is-cancelled { --as-accent: var(--modal-danger, #ef4444); }
|
||||
.aksrt-after-sale-timeline__item::before { content: ""; position: absolute; top: 14px; bottom: -2px; left: 4px; width: 2px; border-radius: 1px; background: color-mix(in srgb, var(--as-accent) 30%, var(--modal-line)); }
|
||||
.aksrt-after-sale-timeline__item:last-child::before { display: none; }
|
||||
.aksrt-after-sale-timeline__knot { position: relative; z-index: 1; width: 10px; height: 10px; margin-top: 4px; border: 2px solid var(--as-accent); border-radius: 50%; background: var(--modal-surface, #fff); }
|
||||
.aksrt-after-sale-timeline__body { display: grid; gap: 3px; min-width: 0; }
|
||||
.aksrt-after-sale-timeline__head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; }
|
||||
.aksrt-after-sale-timeline__status { color: var(--as-accent); font-size: 12px; font-weight: 700; line-height: 1.5; }
|
||||
.aksrt-after-sale-timeline__time { color: var(--modal-muted); font-size: 11px; line-height: 1.5; font-variant-numeric: tabular-nums; }
|
||||
.aksrt-after-sale-timeline__note { margin: 0; color: var(--modal-ink); font-size: 12.5px; line-height: 1.65; word-break: break-word; }
|
||||
|
||||
/* 状态行右侧单元格:状态徽章 + 「撤销售后」入口(仅待审核工单渲染)。 */
|
||||
.aksrt-after-sale-detail__statusCell { display: inline-flex; align-items: center; gap: 10px; }
|
||||
.aksrt-after-sale-withdraw { display: inline-flex; align-items: center; gap: 3px; padding: 0; border: 0; background: transparent; color: var(--modal-danger, #ef4444); font-size: 12px; font-weight: 600; line-height: 1.4; cursor: pointer; transition: color .15s ease; }
|
||||
.aksrt-after-sale-withdraw .fa { font-size: 12px; }
|
||||
.aksrt-after-sale-withdraw:hover { color: color-mix(in srgb, var(--modal-danger) 78%, var(--modal-ink)); }
|
||||
|
||||
/* 售后提示弹窗(撤销售后确认)正文。 */
|
||||
.aksrt-after-sale-withdraw__body { margin-bottom: 16px; color: var(--color-ink-2, #4e5358); line-height: 1.7; }
|
||||
|
||||
/* 售后弹窗底部按钮:全宽平铺贴底(负 margin 抵消 __content 内边距)+ 分隔线,
|
||||
子按钮去独立背景/边框/圆角(文字色区分主次),覆盖上方全局按钮网格的胶囊观感;
|
||||
仅在售后弹窗家族生效,其他 runtime 弹窗仍走全局网格样式。
|
||||
footer 自身不留垂直内边距——上下 12px 并入按钮(min-height 62 = 12+38+12),
|
||||
按钮盒即整个底部区域(hover 底色/分界线随之满幅)。 */
|
||||
.aksrt-after-sale-apply-modal .but-average.modal-buts,
|
||||
.aksrt-after-sale-detail-modal .but-average.modal-buts,
|
||||
.aksrt-after-sale-expired-modal .but-average.modal-buts,
|
||||
.aksrt-after-sale-withdraw-modal .but-average.modal-buts {
|
||||
gap: 0;
|
||||
margin: 16px -18px -16px;
|
||||
padding: 0;
|
||||
border-top: 1px solid var(--modal-line-soft, rgba(50, 50, 50, 0.06));
|
||||
background: var(--main-bg-color, #fff);
|
||||
}
|
||||
|
||||
.aksrt-after-sale-apply-modal .but-average.modal-buts > .but,
|
||||
.aksrt-after-sale-detail-modal .but-average.modal-buts > .but,
|
||||
.aksrt-after-sale-expired-modal .but-average.modal-buts > .but,
|
||||
.aksrt-after-sale-withdraw-modal .but-average.modal-buts > .but {
|
||||
min-height: 62px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
color: var(--modal-muted, #777);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
--this-bg: transparent;
|
||||
--this-border: transparent;
|
||||
--this-shadow: none;
|
||||
}
|
||||
|
||||
.aksrt-after-sale-apply-modal .but-average.modal-buts > .but.c-blue,
|
||||
.aksrt-after-sale-detail-modal .but-average.modal-buts > .but.c-blue,
|
||||
.aksrt-after-sale-expired-modal .but-average.modal-buts > .but.c-blue,
|
||||
.aksrt-after-sale-withdraw-modal .but-average.modal-buts > .but.c-blue {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: var(--modal-primary, #f04494);
|
||||
}
|
||||
|
||||
/* 相邻按钮分界线:legacy footer 自带 .but + .but 左分隔线,上方平铺规则的 border:0
|
||||
会把它抹掉,这里按家族分隔色还原(单按钮无相邻兄弟,天然无分界线)。
|
||||
需排在 .c-blue 规则之后:确认提交等主按钮同受 border:0 影响,后者同名优先级。 */
|
||||
.aksrt-after-sale-apply-modal .but-average.modal-buts > .but + .but,
|
||||
.aksrt-after-sale-detail-modal .but-average.modal-buts > .but + .but,
|
||||
.aksrt-after-sale-expired-modal .but-average.modal-buts > .but + .but,
|
||||
.aksrt-after-sale-withdraw-modal .but-average.modal-buts > .but + .but {
|
||||
border-left: 1px solid var(--modal-line-soft, rgba(50, 50, 50, 0.06));
|
||||
}
|
||||
|
||||
/* 悬停反馈(参考 Element 卡片 footer 的 hover 语言):整块区域浅灰底 + 文字转主色,
|
||||
次级按钮 hover 升为主色、主按钮略加深;平铺按钮已满幅,底色即铺满整个区域,
|
||||
基础 .but 的 0.15s 过渡自动生效,并压掉 compat 层的 hover 半透明。 */
|
||||
.aksrt-after-sale-apply-modal .but-average.modal-buts > .but:hover,
|
||||
.aksrt-after-sale-detail-modal .but-average.modal-buts > .but:hover,
|
||||
.aksrt-after-sale-expired-modal .but-average.modal-buts > .but:hover,
|
||||
.aksrt-after-sale-withdraw-modal .but-average.modal-buts > .but:hover {
|
||||
opacity: 1;
|
||||
background: color-mix(in srgb, var(--modal-ink, #333333) 5%, transparent);
|
||||
color: var(--modal-primary, #f04494);
|
||||
}
|
||||
|
||||
.aksrt-after-sale-apply-modal .but-average.modal-buts > .but.c-blue:hover,
|
||||
.aksrt-after-sale-detail-modal .but-average.modal-buts > .but.c-blue:hover,
|
||||
.aksrt-after-sale-expired-modal .but-average.modal-buts > .but.c-blue:hover,
|
||||
.aksrt-after-sale-withdraw-modal .but-average.modal-buts > .but.c-blue:hover {
|
||||
color: color-mix(in srgb, var(--modal-primary, #f04494) 82%, #000000);
|
||||
}
|
||||
|
||||
/* 单按钮 footer 占满整行(全局网格是 1fr 1fr,单按钮会只占一半)。 */
|
||||
.aksrt-after-sale-apply-modal .but-average.modal-buts > .but:only-child,
|
||||
.aksrt-after-sale-detail-modal .but-average.modal-buts > .but:only-child,
|
||||
.aksrt-after-sale-expired-modal .but-average.modal-buts > .but:only-child,
|
||||
.aksrt-after-sale-withdraw-modal .but-average.modal-buts > .but:only-child { grid-column: 1 / -1; }
|
||||
@@ -166,56 +166,6 @@ html.dark .archive-timeline__badge {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 社交登录分隔线 */
|
||||
.social-login-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 24px 0 16px;
|
||||
color: var(--color-ink-3, #94a3b8);
|
||||
font-size: 13px;
|
||||
}
|
||||
.social-login-divider::before,
|
||||
.social-login-divider::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--color-line, #e2e8f0);
|
||||
}
|
||||
.social-login-divider span {
|
||||
padding: 0 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.oauth-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.oauth-btn {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: var(--radius-btn);
|
||||
border: 1px solid var(--color-line);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-ink-3);
|
||||
transition: border-color 0.2s, color 0.2s;
|
||||
}
|
||||
.oauth-btn i {
|
||||
font-size: 22px;
|
||||
line-height: 1;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.oauth-btn:hover {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.auth-footer-link {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
|
||||
@@ -155,50 +155,6 @@
|
||||
border-color: var(--color-primary, #f04494);
|
||||
}
|
||||
|
||||
/* AJAX 加载更多 */
|
||||
.load-more {
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.load-more__btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 10px 24px;
|
||||
font-size: 13px;
|
||||
color: var(--color-ink-2, #4e5358);
|
||||
background: var(--surface-1, #fff);
|
||||
border: 1px solid var(--color-line, rgba(50, 50, 50, 0.06));
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.load-more__btn:hover {
|
||||
color: var(--color-primary, #f04494);
|
||||
border-color: var(--color-primary, #f04494);
|
||||
}
|
||||
|
||||
.load-more__btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.load-more__spinner {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid var(--surface-3, #e8e8e8);
|
||||
border-top-color: var(--color-primary, #f04494);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.6s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 移动端 */
|
||||
@media (max-width: 767px) {
|
||||
.pagination a,
|
||||
|
||||
@@ -14,6 +14,17 @@
|
||||
margin-left: 8px !important;
|
||||
}
|
||||
|
||||
/* 骨架与真实卡片同宽:.posts-row 以 -8px 抵消卡片边距,loader 被上面的
|
||||
:not() 排除在补偿外,会宽出 16px;这里补上等同的水平边距。
|
||||
补偿落在骨架内层的 .posts-item 上(与真实卡片同一元素),不落外层
|
||||
.post_ajax_loader —— 否则内层 .posts-item.card 的 margin:8px 会与外层
|
||||
补偿叠成 16px,小工具 card 骨架因此窄出 8px。 */
|
||||
.posts-row > .post_ajax_loader .posts-item,
|
||||
.posts-row > .post_ajax_loader .posts-mini {
|
||||
margin-right: 8px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.posts-item {
|
||||
margin: 15px 0;
|
||||
padding: 20px;
|
||||
@@ -447,6 +458,17 @@
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
/* 骨架与卡片同宽同轴:.widget-ajaxpager / .tab-pane 用负边距抵消卡片的水平
|
||||
边距。补偿同样落在骨架内层的 .posts-item 上(与真实卡片同一元素),
|
||||
不落外层 .post_ajax_loader —— 否则内层 .posts-item.card 自带的 margin:8px
|
||||
会与外层补偿叠成 16px,card 骨架因此窄出 8px。 */
|
||||
.widget-main-post.style-list .widget-ajaxpager > .post_ajax_loader .posts-item,
|
||||
.widget-main-post.style-card .widget-ajaxpager > .post_ajax_loader .posts-item,
|
||||
.widget-tab-post .tab-content > .tab-pane > .widget-ajaxpager > .post_ajax_loader .posts-item {
|
||||
margin-right: 8px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.posts-mini:hover img,
|
||||
.posts-item:hover .item-thumbnail img,
|
||||
.posts-item.mult-thumb .thumb-items > span > img:hover {
|
||||
@@ -517,6 +539,12 @@
|
||||
margin-left: 5px !important;
|
||||
}
|
||||
|
||||
.posts-row > .post_ajax_loader .posts-item,
|
||||
.posts-row > .post_ajax_loader .posts-mini {
|
||||
margin-right: 5px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.posts-item .item-thumbnail {
|
||||
width: 138px;
|
||||
}
|
||||
@@ -563,6 +591,13 @@
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.widget-main-post.style-list .widget-ajaxpager > .post_ajax_loader .posts-item,
|
||||
.widget-main-post.style-card .widget-ajaxpager > .post_ajax_loader .posts-item,
|
||||
.widget-tab-post .tab-content > .tab-pane > .widget-ajaxpager > .post_ajax_loader .posts-item {
|
||||
margin-right: 5px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.swiper-scroll .posts-item {
|
||||
margin-right: 10px !important;
|
||||
}
|
||||
|
||||
@@ -50,8 +50,9 @@
|
||||
color: var(--color-primary, #f04494);
|
||||
}
|
||||
|
||||
/* .widget a 的链接色不得覆盖 .but 按钮自身配色(zibll 的 zib-widget 不设置链接色) */
|
||||
.widget a.but {
|
||||
/* .widget a 的链接色不得覆盖 .but / .img-badge 自身配色(zibll 的 zib-widget 不设置链接色) */
|
||||
.widget a.but,
|
||||
.widget a.img-badge {
|
||||
color: var(--this-color, #fff);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
/* ---- 文章打赏:内容使用 AksrtModalRuntime,几何对齐参考主题 modal-mini ---- */
|
||||
.aksrt-modal-runtime.aksrt-reward-modal:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog {
|
||||
width: min(400px, calc(100vw - 32px));
|
||||
border-radius: var(--main-radius, 8px);
|
||||
background: var(--main-bg-color, #fff);
|
||||
}
|
||||
|
||||
.aksrt-modal-runtime.aksrt-reward-modal .aksrt-modal-runtime__content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.aksrt-reward-modal .modal-colorful-header + .aksrt-reward__body {
|
||||
margin-top: 0;
|
||||
padding: 135px 15px 15px;
|
||||
color: var(--main-color, var(--color-ink, #333));
|
||||
}
|
||||
|
||||
.aksrt-reward__desc {
|
||||
margin: 0 0 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.aksrt-modal-runtime.aksrt-reward-modal .aksrt-reward__amounts {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.aksrt-modal-runtime.aksrt-reward-modal .aksrt-reward__amount {
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 46px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
margin: 0;
|
||||
padding: 8px 6px;
|
||||
border: 1px solid var(--color-line-stronger, rgba(50, 50, 50, 0.12));
|
||||
border-radius: var(--radius, 8px);
|
||||
background: var(--surface-2, #f5f6f7);
|
||||
color: var(--color-ink-2, #4e5358);
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
box-shadow: none;
|
||||
transition: border-color 0.2s ease, background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.aksrt-modal-runtime.aksrt-reward-modal .aksrt-reward__amount:hover {
|
||||
border-color: var(--focus-color, var(--color-primary, #f04494));
|
||||
background: var(--color-primary-soft, rgba(240, 68, 148, 0.1));
|
||||
color: var(--focus-color, var(--color-primary, #f04494));
|
||||
opacity: 1;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.aksrt-modal-runtime.aksrt-reward-modal .aksrt-reward__amount:focus-visible {
|
||||
outline: 0;
|
||||
box-shadow: 0 0 0 3px var(--color-primary-soft, rgba(240, 68, 148, 0.2));
|
||||
}
|
||||
|
||||
.aksrt-modal-runtime.aksrt-reward-modal .aksrt-reward__amount.is-active,
|
||||
.aksrt-modal-runtime.aksrt-reward-modal .aksrt-reward__amount.is-active:hover {
|
||||
border-color: var(--focus-color, var(--color-primary, #f04494));
|
||||
background: var(--focus-color, var(--color-primary, #f04494));
|
||||
color: #fff;
|
||||
box-shadow: 0 3px 10px var(--color-primary-soft, rgba(240, 68, 148, 0.2));
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.aksrt-reward__currency {
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.aksrt-reward__value {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.aksrt-modal-runtime.aksrt-reward-modal .aksrt-reward__custom {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin: 0 0 16px;
|
||||
color: var(--color-ink-2, #4e5358);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.aksrt-reward__custom-label {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.aksrt-modal-runtime.aksrt-reward-modal .aksrt-reward__input-wrap {
|
||||
display: flex;
|
||||
width: min(190px, 62%);
|
||||
min-height: 44px;
|
||||
align-items: center;
|
||||
padding: 0 10px 0 12px;
|
||||
border: 1px solid var(--color-line-stronger, rgba(50, 50, 50, 0.12));
|
||||
border-radius: var(--radius, 8px);
|
||||
background: var(--surface-1, #fff);
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.aksrt-modal-runtime.aksrt-reward-modal .aksrt-reward__input-wrap:focus-within {
|
||||
border-color: var(--focus-color, var(--color-primary, #f04494));
|
||||
box-shadow: 0 0 0 3px var(--color-primary-soft, rgba(240, 68, 148, 0.1));
|
||||
}
|
||||
|
||||
.aksrt-reward__input-currency {
|
||||
flex: 0 0 auto;
|
||||
margin-right: 6px;
|
||||
color: var(--muted-color, var(--color-ink-3, #777));
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.aksrt-modal-runtime.aksrt-reward-modal .aksrt-reward__input {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 42px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
background: transparent;
|
||||
color: var(--color-ink, #333);
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
text-align: right;
|
||||
-moz-appearance: textfield;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.aksrt-reward__input::-webkit-inner-spin-button,
|
||||
.aksrt-reward__input::-webkit-outer-spin-button {
|
||||
margin: 0;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.aksrt-reward__input::placeholder {
|
||||
color: var(--muted-color, var(--color-ink-4, #999));
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.aksrt-reward__input:disabled,
|
||||
.aksrt-reward__amount:disabled {
|
||||
cursor: wait;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.aksrt-reward__submit {
|
||||
min-height: 42px;
|
||||
}
|
||||
|
||||
.aksrt-reward__submit:disabled {
|
||||
cursor: wait;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.aksrt-reward__message {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.aksrt-reward__captcha {
|
||||
margin: 0 0 14px;
|
||||
}
|
||||
|
||||
.aksrt-modal-runtime--bottom.aksrt-reward-modal .aksrt-modal-runtime__dialog {
|
||||
border-radius: 16px 16px 0 0;
|
||||
}
|
||||
|
||||
.aksrt-modal-runtime--bottom.aksrt-reward-modal .modal-colorful-header {
|
||||
border-radius: 16px 16px 0 0;
|
||||
}
|
||||
Vendored
-141
@@ -1,141 +0,0 @@
|
||||
/* ========== 骨架屏加载 — 对齐 legacy skeleton loading ========== */
|
||||
.skeleton {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: var(--surface-2, #eee);
|
||||
border-radius: var(--radius, 8px);
|
||||
}
|
||||
|
||||
.skeleton::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent 0%,
|
||||
rgba(255, 255, 255, 0.4) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
animation: skeleton-shimmer 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes skeleton-shimmer {
|
||||
0% { transform: translateX(-100%); }
|
||||
100% { transform: translateX(100%); }
|
||||
}
|
||||
|
||||
/* 骨架屏变体 */
|
||||
.skeleton-text {
|
||||
height: 14px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.skeleton-text--title {
|
||||
height: 20px;
|
||||
width: 60%;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.skeleton-text--short {
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
.skeleton-text--medium {
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.skeleton-avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.skeleton-avatar--sm {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.skeleton-avatar--lg {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.skeleton-thumb {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
border-radius: var(--radius, 8px);
|
||||
}
|
||||
|
||||
.skeleton-thumb--square {
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.skeleton-thumb--card {
|
||||
aspect-ratio: 16 / 10;
|
||||
border-radius: var(--radius, 8px) var(--radius, 8px) 0 0;
|
||||
}
|
||||
|
||||
/* 卡片骨架屏 */
|
||||
.skeleton-card {
|
||||
background: var(--surface-1, #fff);
|
||||
border-radius: var(--radius, 8px);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.skeleton-card__thumb {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 10;
|
||||
}
|
||||
|
||||
.skeleton-card__body {
|
||||
padding: 14px 16px;
|
||||
}
|
||||
|
||||
.skeleton-card__meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* 评论骨架屏 */
|
||||
.skeleton-comment {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 16px 0;
|
||||
border-bottom: 1px solid var(--color-line-soft, rgba(0, 0, 0, 0.03));
|
||||
}
|
||||
|
||||
.skeleton-comment__body {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.skeleton-comment__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* 通用骨架屏容器 */
|
||||
.skeleton-list .skeleton-item {
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid var(--color-line-soft, rgba(0, 0, 0, 0.03));
|
||||
}
|
||||
|
||||
.skeleton-list .skeleton-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* 减弱动画偏好 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.skeleton::after {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,27 @@
|
||||
z-index: 100055;
|
||||
}
|
||||
|
||||
/* public.css redeclares .muted-box after legacy utilities, overriding its
|
||||
background, radius, and padding utilities. Restore the reference box contract. */
|
||||
.modal[data-aksrt-vip-points-modal] .muted-box.padding-h10 {
|
||||
background-color: var(--this-bg, var(--muted-border-color));
|
||||
border-radius: var(--main-radius);
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.modal[data-aksrt-vip-points-modal] .muted-box.padding-10,
|
||||
.modal[data-aksrt-vip-points-modal] .aksrt-vip-points-desc {
|
||||
background-color: var(--this-bg, var(--muted-border-color));
|
||||
border-radius: var(--main-radius);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.modal[data-aksrt-vip-points-modal] .aksrt-vip-points-desc {
|
||||
display: block;
|
||||
margin-top: 20px;
|
||||
color: var(--muted-2-color, #999);
|
||||
}
|
||||
|
||||
.pay-vip-form-row {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
@@ -102,21 +123,85 @@
|
||||
cursor: wait;
|
||||
}
|
||||
|
||||
.vipbg-v2 {
|
||||
/* 会员弹窗骨架屏(pay-vip.js vipSkeleton()):仿真实弹窗布局——
|
||||
用户框 + 等级卡行 + 双列套餐面板。线条/色块与 shimmer 动画复用全局
|
||||
.aksrt-modal-runtime__skeleton-line/-block 与 @keyframes
|
||||
aksrt-modal-skeleton(build/public.min.css 已全局入队);
|
||||
这里只定义 VIP 专属形状(头像圆、tab 卡、双列 grid、按钮条),
|
||||
与 .pay-vip-form-row 的 640px 双列阈值保持一致。 */
|
||||
.aksrt-vip-modal-skeleton {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.aksrt-vip-modal-skeleton__user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
padding: 8px 10px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.aksrt-vip-modal-skeleton__avatar {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 50%;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.aksrt-vip-modal-skeleton__lines {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.aksrt-vip-modal-skeleton__tabs {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.aksrt-vip-modal-skeleton__tab {
|
||||
flex: 1;
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
.aksrt-vip-modal-skeleton__pane {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.aksrt-vip-modal-skeleton__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.aksrt-vip-modal-skeleton__products {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.aksrt-vip-modal-skeleton__button {
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
/* 专属形状共用骨架底色与 shimmer(值与 modal-runtime.css 的线条/块一致)。 */
|
||||
.aksrt-vip-modal-skeleton__avatar,
|
||||
.aksrt-vip-modal-skeleton__tab,
|
||||
.aksrt-vip-modal-skeleton__button {
|
||||
display: block;
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
background: linear-gradient(90deg, #ffb347, #ff6b6b);
|
||||
background: linear-gradient(90deg, rgba(148, 163, 184, 0.16), rgba(148, 163, 184, 0.3), rgba(148, 163, 184, 0.16));
|
||||
background-size: 200% 100%;
|
||||
animation: aksrt-modal-skeleton 1.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.vipbg-v2.level-2 {
|
||||
background: linear-gradient(90deg, #9b59b6, #3f51b5);
|
||||
}
|
||||
|
||||
.vip-expdate-tag {
|
||||
font-size: 12px;
|
||||
@media (min-width: 640px) {
|
||||
.aksrt-vip-modal-skeleton__pane {
|
||||
grid-template-columns: 1fr 1.2fr;
|
||||
}
|
||||
}
|
||||
+52
-14
@@ -2676,6 +2676,13 @@ select.form-control {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* .form-select::before 已经画了 FA5 箭头,隐藏原生箭头避免出现两个。 */
|
||||
.form-select select {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.form-select::before {
|
||||
font: normal normal 900 14px/1 "Font Awesome 5 Free";
|
||||
content: '\f107';
|
||||
@@ -5929,7 +5936,7 @@ body:has(.modal.in) .float-btn,
|
||||
}
|
||||
|
||||
.payvip-icon:before {
|
||||
content: '\f219';
|
||||
content: '\f3a5';
|
||||
font: normal normal 900 14px/1 "Font Awesome 5 Free";
|
||||
font-size: 3.5em;
|
||||
right: -0.2em !important;
|
||||
@@ -5937,16 +5944,6 @@ body:has(.modal.in) .float-btn,
|
||||
opacity: 0.06;
|
||||
}
|
||||
|
||||
.vipbg-v1 {
|
||||
background: linear-gradient(60deg, #f0bf81 0, #fdefd6 30%, #f2deb9 40%, #fdc277 80%, #f9e1b7 100%);
|
||||
color: #8c5728;
|
||||
--this-color: #8c5728;
|
||||
}
|
||||
|
||||
.vipbg-v1:before {
|
||||
content: '\f006';
|
||||
}
|
||||
|
||||
.vip-texticon {
|
||||
font-size: 0.85em;
|
||||
background: transparent;
|
||||
@@ -7486,6 +7483,17 @@ badge.top,
|
||||
margin-right: 8px !important;
|
||||
}
|
||||
|
||||
/* 骨架与真实卡片同宽同轴:.posts-row 用 -8px 抵消卡片边距,原 :not() 把骨架
|
||||
排除在补偿外,骨架于是比卡片左右各宽 8px。补上等同边距。
|
||||
注意补偿落在骨架**内层**的 .posts-item 上(与真实卡片同一元素、同一选择器),
|
||||
不能落在外层 .post_ajax_loader 上 —— 骨架内层本身带 .posts-item.card 的
|
||||
margin:8px,外层再补 8px 就是双重补偿,小工具 card 骨架会窄出 8px。 */
|
||||
.posts-row > .post_ajax_loader .posts-item,
|
||||
.posts-row > .post_ajax_loader .posts-mini {
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.posts-item {
|
||||
padding: 20px;
|
||||
margin: 15px 0;
|
||||
@@ -7890,6 +7898,18 @@ badge.top,
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/* 骨架与卡片同宽同轴:.widget-ajaxpager / .tab-pane 用负边距抵消卡片的水平
|
||||
边距。补偿同样落在骨架内层的 .posts-item 上(与真实卡片同一元素),
|
||||
而不是外层 .post_ajax_loader —— 否则骨架内层自带的 .posts-item.card
|
||||
margin:8px 会与外层补偿叠成 16px,让 card 骨架窄出 8px。
|
||||
style-card 的卡片由 .posts-item.card 自带 margin:8px,这里只需覆盖骨架内层。 */
|
||||
.widget-main-post.style-list .widget-ajaxpager > .post_ajax_loader .posts-item,
|
||||
.widget-main-post.style-card .widget-ajaxpager > .post_ajax_loader .posts-item,
|
||||
.widget-tab-post .tab-content > .tab-pane > .widget-ajaxpager > .post_ajax_loader .posts-item {
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.widget-tab-post.index-tab .list-inline {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
@@ -9635,11 +9655,16 @@ img.alignleft {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
/* AKSRT 适配:站点行高(--leading-normal: 1.6)与参考主题 bootstrap
|
||||
* 的 1.42857143 不同,用户卡片内的 img-badge(签到书签)钉住参考行高;
|
||||
* 字号已随 --text-base 对齐参考主题(14px),无需单独声明。 */
|
||||
/* AKSRT 适配:签到书签排版逐字对齐参考主题 bootstrap 的 body 计算值——
|
||||
* 行高 1.42857143(站点 --leading-normal 为 1.6)、字体族
|
||||
* "Helvetica Neue", Helvetica, Arial, sans-serif(站点 --font-body 为
|
||||
* 现代系统栈),并还原浏览器默认的字体平滑与渲染(站点 html 重置为
|
||||
* antialiased + optimizeLegibility);字号随 --text-base 已是参考的 14px。 */
|
||||
.user-card .card-content .img-badge {
|
||||
line-height: 1.42857143;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: auto;
|
||||
text-rendering: auto;
|
||||
}
|
||||
|
||||
.user-card name .display-name {
|
||||
@@ -11213,6 +11238,13 @@ body.error404 {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.widget-main-post.style-list .widget-ajaxpager > .post_ajax_loader .posts-item,
|
||||
.widget-main-post.style-card .widget-ajaxpager > .post_ajax_loader .posts-item,
|
||||
.widget-tab-post .tab-content > .tab-pane > .widget-ajaxpager > .post_ajax_loader .posts-item {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.posts-row {
|
||||
margin-left: -5px;
|
||||
margin-right: -5px;
|
||||
@@ -11223,6 +11255,12 @@ body.error404 {
|
||||
margin-right: 5px !important;
|
||||
}
|
||||
|
||||
.posts-row > .post_ajax_loader .posts-item,
|
||||
.posts-row > .post_ajax_loader .posts-mini {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.posts-item.card {
|
||||
width: calc(50% - 10px);
|
||||
margin: 5px;
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
body{--mian-max-width: 1200px;--posts-card-scale: 70%;--posts-list-scale: 70%;--theme-color: #f04494;--focus-color: var(--theme-color);--focus-shadow-color: rgba(253, 83, 161, .4);--key-color: #333;--main-color: #4e5358;--main-shadow: rgba(116, 116, 116, .08);--muted-color: #777;--muted-2-color: #999;--muted-3-color: #b1b1b1;--muted-4-color: #d2d2d2;--body-bg-color: #f5f6f7;--main-bg-color: #fff;--muted-bg-color: #eee;--muted-bg-2-color: #f6f7f8;--main-border-color: rgba(50, 50, 50, .06);--muted-border-color: rgba(0, 0, 0, .03);--main-radius: 8px;--mini-radius: calc(var(--main-radius) / 1.4);--blur-bg: rgba(255, 255, 255, .8);--float-btn-bg: rgba(200, 200, 200, .4);--header-bg: var(--blur-bg);--header-color: var(--main-color);--footer-bg: var(--main-bg-color);--footer-color: var(--muted-2-color)}.dark-theme{--key-color: #f8fafc;--main-color: #e5eef7;--main-shadow: rgba(24, 24, 24, .1);--muted-color: #b4b6bb;--muted-2-color: #888a8f;--muted-3-color: #636469;--muted-4-color: #43454a;--body-bg-color: #292a2d;--main-bg-color: #323335;--muted-bg-color: #2d2e31;--muted-bg-2-color: #37383a;--main-border-color: rgba(114, 114, 114, .1);--muted-border-color: rgba(184, 184, 184, .02);--blur-bg: rgba(50, 51, 53, .8);--float-btn-bg: rgba(70, 70, 70, .4);--header-bg: var(--blur-bg);--header-color: var(--main-color);--footer-bg: var(--main-bg-color);--footer-color: var(--muted-2-color)}::view-transition-old(root),::view-transition-new(root){animation:none;mix-blend-mode:normal}.dark::view-transition-old(root){z-index:1}.dark::view-transition-new(root){z-index:2147483646}::view-transition-old(root){z-index:2147483646}::view-transition-new(root){z-index:1}@supports not ((-webkit-backdrop-filter: blur(20px)) or (backdrop-filter: blur(20px))){body{--blur-bg: #ffffff}body.dark-theme{--blur-bg: #37383a}}body:has(.modal.in),body.modal-open{--blur-bg: #ffffff}body.dark-theme:has(.modal.in),body.dark-theme.modal-open{--blur-bg: #37383a}body.fps-accelerat{--blur-bg: #ffffff}body.fps-accelerat.dark-theme{--blur-bg: #37383a}body.fps-accelerat *{-webkit-backdrop-filter:unset!important;backdrop-filter:unset!important}a[href],.relates-thumb li a>.post-info,.more-posts a>span{transition:color .2s,background .3s,opacity .3s,box-shadow .3s,transform .3s}.main-shadow{transition:box-shadow .3s}.but:focus,.but:hover,.progress,.navbar-nav>li:before{transition:.3s}.navbar-top .sub-menu,.theme-popover,.hover-show .hover-show-con{opacity:0;transform:translateY(6px);transition:.4s}.navbar-top li:hover>.sub-menu,.hover-show:hover .hover-show-con,.hover-show:hover~.hover-show-con,.hover-show:hover~.sub-menu{opacity:1;transform:translateY(0)}.hover-show{position:relative;cursor:pointer;z-index:10}.hover-show .hover-show-con{position:absolute;visibility:hidden}.hover-show:hover .hover-show-con,.hover-show:hover~.hover-show-con,.hover-show:hover~.sub-menu{visibility:unset}.match-ok,.navbar-top .sub-menu li>a,.ic-close{transition:.3s}.ic-close:hover{transform:rotate(270deg);color:inherit}.social_loginbar a,.item-thumbnail img,.dosc-nav .fa{transition:.2s ease-out}.hover-zoom:hover,.hover-zoom-img:hover img,.social_loginbar .xh-social-item:hover,.social_loginbar .social-login-item:hover{transform:scale(1.05)}.hover-zoom-sm:hover,.hover-zoom-img-sm:hover img,.posts-mini:hover img,.posts-item:hover .item-thumbnail img,.posts-item.mult-thumb .thumb-items>span>img:hover{transform:scale(1.02)}.main-min-height{min-height:calc(100vh - 280px)}.main-shadow{box-shadow:0 0 10px var(--main-shadow)}.key-color{color:var(--key-color)}.focus-color{--this-color: var(--focus-color);color:var(--focus-color)}.muted-color{color:var(--muted-color)}.muted-2-color{color:var(--muted-2-color)}.muted-3-color{color:var(--muted-3-color)}.main-bg{background:var(--main-bg-color)}.placeholder{background:linear-gradient(118deg,#7979791a 25%,#79797933 37%,#7979791a 63%);opacity:1;border-radius:4px;-webkit-animation:placeholder 1.5s ease infinite!important;animation:placeholder 1.5s ease infinite!important;background-size:400% 100%!important}.placeholder.img{height:119px}.placeholder.t1{height:24px;width:80%}.placeholder.k1{height:20px;width:60%}.placeholder.k2{height:20px}.placeholder.k3{height:15px}.placeholder.s1{width:70px;height:15px;display:inline-block}@-webkit-keyframes placeholder{0%{background-position:100% 50%}to{background-position:0 50%}}@keyframes placeholder{0%{background-position:100% 50%}to{background-position:0 50%}}@-webkit-keyframes fade{0%{opacity:0}to{opacity:1}}@keyframes fade{0%{opacity:0}to{opacity:1}}.ani.shake{-webkit-animation:shake .3s ease-in!important;animation:shake .3s ease-in!important}@-webkit-keyframes shake{20%{transform:translate(2px)}40%{transform:translate(-4px)}60%{transform:translate(5px)}80%{transform:translate(3px)}to{transform:translate(0)}}@keyframes shake{20%{transform:translate(2px)}40%{transform:translate(-4px)}60%{transform:translate(5px)}80%{transform:translate(3px)}to{transform:translate(0)}}.obs-animate{opacity:0;transition:opacity .6sLine truncated
|
||||
body{--mian-max-width: 1200px;--posts-card-scale: 70%;--posts-list-scale: 70%;--theme-color: #f04494;--focus-color: var(--theme-color);--focus-shadow-color: rgba(253, 83, 161, .4);--key-color: #333;--main-color: #4e5358;--main-shadow: rgba(116, 116, 116, .08);--muted-color: #777;--muted-2-color: #999;--muted-3-color: #b1b1b1;--muted-4-color: #d2d2d2;--body-bg-color: #f5f6f7;--main-bg-color: #fff;--muted-bg-color: #eee;--muted-bg-2-color: #f6f7f8;--main-border-color: rgba(50, 50, 50, .06);--muted-border-color: rgba(0, 0, 0, .03);--main-radius: 8px;--mini-radius: calc(var(--main-radius) / 1.4);--blur-bg: rgba(255, 255, 255, .8);--float-btn-bg: rgba(200, 200, 200, .4);--header-bg: var(--blur-bg);--header-color: var(--main-color);--footer-bg: var(--main-bg-color);--footer-color: var(--muted-2-color)}.dark-theme{--key-color: #f8fafc;--main-color: #e5eef7;--main-shadow: rgba(24, 24, 24, .1);--muted-color: #b4b6bb;--muted-2-color: #888a8f;--muted-3-color: #636469;--muted-4-color: #43454a;--body-bg-color: #292a2d;--main-bg-color: #323335;--muted-bg-color: #2d2e31;--muted-bg-2-color: #37383a;--main-border-color: rgba(114, 114, 114, .1);--muted-border-color: rgba(184, 184, 184, .02);--blur-bg: rgba(50, 51, 53, .8);--float-btn-bg: rgba(70, 70, 70, .4);--header-bg: var(--blur-bg);--header-color: var(--main-color);--footer-bg: var(--main-bg-color);--footer-color: var(--muted-2-color)}::view-transition-old(root),::view-transition-new(root){animation:none;mix-blend-mode:normal}.dark::view-transition-old(root){z-index:1}.dark::view-transition-new(root){z-index:2147483646}::view-transition-old(root){z-index:2147483646}::view-transition-new(root){z-index:1}@supports not ((-webkit-backdrop-filter: blur(20px)) or (backdrop-filter: blur(20px))){body{--blur-bg: #ffffff}body.dark-theme{--blur-bg: #37383a}}body:has(.modal.in),body.modal-open{--blur-bg: #ffffff}body.dark-theme:has(.modal.in),body.dark-theme.modal-open{--blur-bg: #37383a}body.fps-accelerat{--blur-bg: #ffffff}body.fps-accelerat.dark-theme{--blur-bg: #37383a}body.fps-accelerat *{-webkit-backdrop-filter:unset!important;backdrop-filter:unset!important}a[href],.relates-thumb li a>.post-info,.more-posts a>span{transition:color .2s,background .3s,opacity .3s,box-shadow .3s,transform .3s}.main-shadow{transition:box-shadow .3s}.but:focus,.but:hover,.progress,.navbar-nav>li:before{transition:.3s}.navbar-top .sub-menu,.theme-popover,.hover-show .hover-show-con{opacity:0;transform:translateY(6px);transition:.4s}.navbar-top li:hover>.sub-menu,.hover-show:hover .hover-show-con,.hover-show:hover~.hover-show-con,.hover-show:hover~.sub-menu{opacity:1;transform:translateY(0)}.hover-show{position:relative;cursor:pointer;z-index:10}.hover-show .hover-show-con{position:absolute;visibility:hidden}.hover-show:hover .hover-show-con,.hover-show:hover~.hover-show-con,.hover-show:hover~.sub-menu{visibility:unset}.match-ok,.navbar-top .sub-menu li>a,.ic-close{transition:.3s}.ic-close:hover{transform:rotate(270deg);color:inherit}.social_loginbar a,.item-thumbnail img,.dosc-nav .fa{transition:.2s ease-out}.hover-zoom:hover,.hover-zoom-img:hover img,.social_loginbar .xh-social-item:hover,.social_loginbar .social-login-item:hover{transform:scale(1.05)}.hover-zoom-sm:hover,.hover-zoom-img-sm:hover img,.posts-mini:hover img,.posts-item:hover .item-thumbnail img,.posts-item.mult-thumb .thumb-items>span>img:hover{transform:scale(1.02)}.main-min-height{min-height:calc(100vh - 280px)}.main-shadow{box-shadow:0 0 10px var(--main-shadow)}.key-color{color:var(--key-color)}.focus-color{--this-color: var(--focus-color);color:var(--focus-color)}.muted-color{color:var(--muted-color)}.muted-2-color{color:var(--muted-2-color)}.muted-3-color{color:var(--muted-3-color)}.main-bg{background:var(--main-bg-color)}.placeholder{background:linear-gradient(118deg,#7979791a 25%,#79797933 37%,#7979791a 63%);opacity:1;border-radius:4px;-webkit-animation:placeholder 1.5s ease infinite!important;animation:placeholder 1.5s ease infinite!important;background-size:400% 100%!important}.placeholder.img{height:119px}.placeholder.t1{height:24px;width:80%}.placeholder.k1{height:20px;width:60%}.placeholder.k2{height:20px}.placeholder.k3{height:15px}.placeholder.s1{width:70px;height:15px;display:inline-block}@-webkit-keyframes placeholder{0%{background-position:100% 50%}to{background-position:0 50%}}@keyframes placeholder{0%{background-position:100% 50%}to{background-position:0 50%}}@-webkit-keyframes fade{0%{opacity:0}to{opacity:1}}@keyframes fade{0%{opacity:0}to{opacity:1}}.ani.shake{-webkit-animation:shake .3s ease-in!important;animation:shake .3s ease-in!important}@-webkit-keyframes shake{20%{transform:translate(2px)}40%{transform:translate(-4px)}60%{transform:translate(5px)}80%{transform:translate(3px)}to{transform:translate(0)}}@keyframes shake{20%{transform:translate(2px)}40%{transform:translate(-4px)}60%{transform:translate(5px)}80%{transform:translate(3px)}to{transform:translate(0)}}.obs-animate{opacity:0;transition:opacity .6sLine truncated
|
||||
@@ -34,7 +34,7 @@
|
||||
.friend-card__avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: var(--radius);
|
||||
border-radius: var(--radius-avatar, 50%);
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
background: var(--surface-2);
|
||||
|
||||
@@ -47,10 +47,10 @@
|
||||
.aksrt-orders-page__search { display: flex; gap: 8px; align-items: center; margin: 20px 0 12px; padding: 7px; border: 1px solid var(--orders-line); border-radius: 12px; background: var(--surface-1, #fff); }
|
||||
.aksrt-orders-page__search > i { margin-left: 7px; color: var(--orders-muted); font-size: 13px; }
|
||||
.aksrt-orders-page__search input { flex: 1; min-width: 0; border: 0; outline: 0; background: transparent; color: var(--orders-ink); font-size: 13px; }
|
||||
.aksrt-orders-page__search button, .aksrt-orders-page__card-actions .btn-primary, .aksrt-orders-page__card-actions .btn-outline, .aksrt-orders-page__card-actions .btn-ghost, .aksrt-orders-page__empty .btn-outline, .aksrt-order-detail__actions .btn-primary, .aksrt-order-detail__actions .btn-outline, .aksrt-order-detail__actions .btn-ghost { display: inline-flex; align-items: center; justify-content: center; min-height: 34px; padding: 0 13px; border-radius: 8px; border: 1px solid transparent; font-size: 12px; font-weight: 650; text-decoration: none; cursor: pointer; }
|
||||
.aksrt-orders-page .btn-primary, .aksrt-order-detail__actions .btn-primary { background: var(--color-primary, #3b82f6); color: var(--color-on-primary, #fff); }
|
||||
.aksrt-orders-page .btn-outline, .aksrt-order-detail__actions .btn-outline { border-color: var(--orders-line); background: var(--surface-1, #fff); color: var(--orders-ink); }
|
||||
.aksrt-orders-page .btn-ghost, .aksrt-order-detail__actions .btn-ghost { background: transparent; color: var(--orders-muted); }
|
||||
.aksrt-orders-page__search button, .aksrt-orders-page__card-actions .btn-primary, .aksrt-orders-page__card-actions .btn-outline, .aksrt-orders-page__card-actions .btn-ghost, .aksrt-orders-page__empty .btn-outline { display: inline-flex; align-items: center; justify-content: center; min-height: 34px; padding: 0 13px; border-radius: 8px; border: 1px solid transparent; font-size: 12px; font-weight: 650; text-decoration: none; cursor: pointer; }
|
||||
.aksrt-orders-page .btn-primary { background: var(--color-primary, #3b82f6); color: var(--color-on-primary, #fff); }
|
||||
.aksrt-orders-page .btn-outline { border-color: var(--orders-line); background: var(--surface-1, #fff); color: var(--orders-ink); }
|
||||
.aksrt-orders-page .btn-ghost { background: transparent; color: var(--orders-muted); }
|
||||
.aksrt-orders-page__filter-toggle { background: var(--surface-2, #f4f6f9); color: var(--orders-ink); white-space: nowrap; }
|
||||
|
||||
.aksrt-orders-page__filter-panel { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: -2px 0 14px; padding: 12px; border-radius: 10px; background: var(--surface-2, #f4f6f9); }
|
||||
@@ -108,9 +108,16 @@
|
||||
--detail-success: var(--modal-success);
|
||||
}
|
||||
|
||||
.aksrt-order-detail-modal:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog { width: min(480px, calc(100vw - 32px)); }
|
||||
.aksrt-order-detail-modal .aksrt-modal-runtime__header { padding: 15px 18px 14px; border-bottom: 1px solid var(--detail-line); }
|
||||
.aksrt-order-detail-modal .aksrt-modal-runtime__title { font-size: 16px; font-weight: 700; }
|
||||
/* 订单详情弹窗:与售后弹窗统一为 modal-mini 几何(400px);标题区改用蓝色炫彩头,运行时不再渲染默认标题栏。 */
|
||||
.aksrt-order-detail-modal:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog {
|
||||
width: min(400px, calc(100vw - 32px));
|
||||
border-radius: var(--main-radius, 8px);
|
||||
background: var(--main-bg-color, #fff);
|
||||
}
|
||||
/* 滚动发生在 dialog 本体(modal-runtime 的 overflow:auto),内容超高时右缘会露出
|
||||
滚动栏;与售后/申诉弹窗口径一致:完全隐藏滚动条(含细滚动条),滚动行为不变。 */
|
||||
.aksrt-order-detail-modal .aksrt-modal-runtime__dialog { -ms-overflow-style: none; scrollbar-width: none; }
|
||||
.aksrt-order-detail-modal .aksrt-modal-runtime__dialog::-webkit-scrollbar { display: none; }
|
||||
.aksrt-order-detail-modal .aksrt-modal-runtime__content { display: flex; flex-direction: column; gap: 18px; padding: 16px 18px 0; }
|
||||
|
||||
.aksrt-order-detail__skeleton { display: grid; gap: 12px; padding: 4px 0; }
|
||||
@@ -156,8 +163,22 @@
|
||||
.aksrt-order-detail__refresh-state p { margin: 0; }
|
||||
.aksrt-order-detail__refresh-state button { grid-column: 2; justify-self: start; }
|
||||
|
||||
.aksrt-order-detail-modal .aksrt-order-detail__actions { position: sticky; bottom: 0; z-index: 2; flex-direction: row; flex-wrap: wrap; justify-content: flex-end; gap: 8px; margin: 0 -18px; padding: 13px 18px; border-top: 1px solid var(--detail-line); background: var(--detail-surface); }
|
||||
.aksrt-order-detail-modal .aksrt-order-detail__actions > button { flex: 0 0 auto; min-height: 38px; padding: 0 16px; white-space: nowrap; }
|
||||
/* 底部操作区:按钮排布沿用主题 .but-average.modal-buts,此处仅保留吸底定位与通栏背景。
|
||||
主题 modal-buts 自带 margin-top:16px,吸底场景下用更高优先级清掉,避免内容与吸底条之间出现空隙。
|
||||
footer 不留垂直内边距(水平同理归零)——上下 13px 并入按钮(min-height 64 = 13+38+13),
|
||||
按钮盒即整个底部区域;安全区留在 footer 底部,避免按钮文字沉入 Home 指示条。 */
|
||||
.aksrt-order-detail-modal .aksrt-order-detail__actions { position: sticky; bottom: 0; z-index: 2; margin: 0 -18px; padding: 0 0 env(safe-area-inset-bottom, 0); border-top: 1px solid var(--detail-line); background: var(--detail-surface); }
|
||||
.aksrt-order-detail-modal .aksrt-order-detail__actions.but-average.modal-buts { margin-top: 0; gap: 0; }
|
||||
/* 底部按钮观感与售后弹窗家族对齐(modal-runtime 同款平铺规则):去胶囊底色/边框/圆角,
|
||||
文字色区分主次;仅「关闭」单按钮时占满整行,即售后详细「我知道了」的呈现。
|
||||
分界线与悬停底色需排在 .c-blue 规则之后:主按钮同受 border:0 影响与主色覆盖,同名优先级看先后。 */
|
||||
.aksrt-order-detail-modal .aksrt-order-detail__actions.but-average.modal-buts > .but { min-height: 64px; border: 0; border-radius: 0; box-shadow: none; background: transparent; color: var(--modal-muted, #777); font-weight: 600; cursor: pointer; --this-bg: transparent; --this-border: transparent; --this-shadow: none; }
|
||||
.aksrt-order-detail-modal .aksrt-order-detail__actions.but-average.modal-buts > .but.c-blue { background: transparent; border: 0; color: var(--modal-primary, #f04494); }
|
||||
.aksrt-order-detail-modal .aksrt-order-detail__actions.but-average.modal-buts > .but + .but { border-left: 1px solid var(--detail-line); }
|
||||
/* 悬停反馈与售后弹窗家族同款:整块区域浅灰底 + 文字转主色(次级升主色、主按钮略加深)。 */
|
||||
.aksrt-order-detail-modal .aksrt-order-detail__actions.but-average.modal-buts > .but:hover { opacity: 1; background: color-mix(in srgb, var(--modal-ink, #333333) 5%, transparent); color: var(--modal-primary, #f04494); }
|
||||
.aksrt-order-detail-modal .aksrt-order-detail__actions.but-average.modal-buts > .but.c-blue:hover { color: color-mix(in srgb, var(--modal-primary, #f04494) 82%, #000000); }
|
||||
.aksrt-order-detail-modal .aksrt-order-detail__actions.but-average.modal-buts > .but:only-child { grid-column: 1 / -1; }
|
||||
|
||||
/* Orders extensions */
|
||||
.aksrt-orders-page__extra-nav { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 12px; }
|
||||
@@ -176,5 +197,19 @@
|
||||
.aksrt-coupon-card__meta { margin-bottom: 4px; color: var(--muted-2-color, #888); font-size: 12px; }
|
||||
.aksrt-orders-page__field { margin-bottom: 12px; }
|
||||
.aksrt-orders-page__input, .aksrt-orders-page__select, .aksrt-orders-page__textarea { box-sizing: border-box; width: 100%; padding: 8px 10px; border: 1px solid var(--main-border-color, #ddd); border-radius: 6px; }
|
||||
@media (max-width: 767px) { .uc-orders-panel__head { grid-template-columns: 1fr; gap: 17px; }.aksrt-orders-page__stats { max-width: none; }.aksrt-orders-page__search { flex-wrap: wrap; }.aksrt-orders-page__search input { min-width: 150px; }.aksrt-orders-page__search .btn-primary { margin-left: auto; }.aksrt-orders-page__filter-toggle { margin-left: auto; }.aksrt-orders-page__card-rail time { width: 100%; margin-left: 0; order: 3; }.aksrt-orders-page__card-main { grid-template-columns: 48px minmax(0, 1fr); }.aksrt-orders-page__card-img, .aksrt-orders-page__card-art { width: 48px; height: 48px; }.aksrt-orders-page__card-value { grid-column: 2; text-align: left; }.aksrt-orders-page__card-actions > :not(.aksrt-orders-page__countdown) { flex: 1 1 auto; }.aksrt-orders-page__countdown { width: 100%; }.aksrt-order-detail-modal .aksrt-order-detail__actions > button { flex: 1 1 0; min-width: 0; padding: 0 10px; } }
|
||||
@media (max-width: 767px) { .uc-orders-panel__head { grid-template-columns: 1fr; gap: 17px; }.aksrt-orders-page__stats { max-width: none; }.aksrt-orders-page__search { flex-wrap: wrap; }.aksrt-orders-page__search input { min-width: 150px; }.aksrt-orders-page__search .btn-primary { margin-left: auto; }.aksrt-orders-page__filter-toggle { margin-left: auto; }.aksrt-orders-page__card-rail time { width: 100%; margin-left: 0; order: 3; }.aksrt-orders-page__card-main { grid-template-columns: 48px minmax(0, 1fr); }.aksrt-orders-page__card-img, .aksrt-orders-page__card-art { width: 48px; height: 48px; }.aksrt-orders-page__card-value { grid-column: 2; text-align: left; }.aksrt-orders-page__card-actions > :not(.aksrt-orders-page__countdown) { flex: 1 1 auto; }.aksrt-orders-page__countdown { width: 100%; }}
|
||||
html.dark .aksrt-orders-page__stat, html.dark .aksrt-orders-page__card { background: var(--surface-1, #1a1d24); } html.dark .aksrt-orders-page__card-rail, html.dark .aksrt-orders-page__filter-panel { background: var(--surface-2, #12151b); }
|
||||
|
||||
/* ---- 售后记录卡片(订单详情弹窗) ---- */
|
||||
.aksrt-order-detail__after-sales { display: grid; gap: 10px; margin: 0; padding: 0; list-style: none; }
|
||||
.aksrt-order-detail__after-sale { --as-accent: var(--detail-muted); display: grid; gap: 10px; padding: 13px 14px; border: 1px solid var(--detail-line); border-left: 3px solid var(--as-accent); border-radius: 12px; background: var(--detail-surface); }
|
||||
.aksrt-order-detail__after-sale.is-pending { --as-accent: var(--detail-warning); }
|
||||
.aksrt-order-detail__after-sale.is-processing { --as-accent: var(--detail-primary); }
|
||||
.aksrt-order-detail__after-sale.is-approved, .aksrt-order-detail__after-sale.is-completed { --as-accent: var(--detail-success); }
|
||||
.aksrt-order-detail__after-sale.is-rejected, .aksrt-order-detail__after-sale.is-cancelled { --as-accent: var(--modal-danger, #ef4444); }
|
||||
.aksrt-order-detail__after-sale-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
||||
.aksrt-order-detail__after-sale-badge { padding: 3px 11px; border-radius: 999px; background: color-mix(in srgb, var(--as-accent) 12%, transparent); color: var(--as-accent); font-size: 12px; font-weight: 700; line-height: 1.5; }
|
||||
.aksrt-order-detail__after-sale-head time { color: var(--detail-muted); font-size: 11.5px; font-variant-numeric: tabular-nums; }
|
||||
.aksrt-order-detail__after-sale-reason { margin: 0; color: var(--detail-ink); font-size: 13px; line-height: 1.65; white-space: pre-wrap; word-break: break-word; }
|
||||
.aksrt-order-detail__after-sale-field { color: var(--detail-muted); }
|
||||
.aksrt-order-detail__after-sale-foot { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; }
|
||||
@@ -1,3 +1,2 @@
|
||||
@import url("profile/layout.css");
|
||||
@import url("profile/lists.css");
|
||||
@import url("profile/modals.css");
|
||||
@@ -125,6 +125,8 @@
|
||||
flex-shrink: 0;
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
/* 用户中心头图卡保持 legacy 圆角方形:显式声明圆角,避免被 public 捆绑里
|
||||
同特异性(0,1,1)的全局头像原语 img.avatar 抢先。 */
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
|
||||
@@ -240,91 +240,93 @@
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.profile-comment-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.profile-comment-item {
|
||||
margin: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-comment-item {
|
||||
position: relative;
|
||||
padding: 16px 18px;
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--surface-1);
|
||||
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
|
||||
transition: background-color var(--dur-fast) var(--ease),
|
||||
border-color var(--dur-fast) var(--ease),
|
||||
box-shadow var(--dur-fast) var(--ease);
|
||||
}
|
||||
.profile-comment-item::before {
|
||||
content: none;
|
||||
}
|
||||
.profile-comment-item:hover {
|
||||
background: var(--surface-1);
|
||||
border-color: var(--color-line-strong, var(--color-line));
|
||||
box-shadow: 0 2px 6px rgba(15, 23, 42, 0.05);
|
||||
}
|
||||
|
||||
.profile-comment-item__quote-link {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
text-decoration: none;
|
||||
color: var(--color-ink-2);
|
||||
margin-bottom: 8px;
|
||||
margin: 0;
|
||||
padding: 20px 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
.profile-comment-item__quote-link:hover .profile-comment-item__quote-text {
|
||||
color: var(--color-ink-2);
|
||||
}
|
||||
.profile-comment-item__quote-mark {
|
||||
flex-shrink: 0;
|
||||
font-family: var(--font-display);
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
.profile-comment-item__time {
|
||||
flex: 0 0 20%;
|
||||
margin-right: 20px;
|
||||
padding-top: 6px;
|
||||
color: var(--color-ink-4);
|
||||
opacity: 0.45;
|
||||
margin-top: 2px;
|
||||
text-align: right;
|
||||
}
|
||||
.profile-comment-item__quote-text {
|
||||
flex: 1;
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-sm, 0.875rem);
|
||||
line-height: 1.65;
|
||||
color: var(--color-ink-2);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
.profile-comment-item__body {
|
||||
flex: 0 1 calc(80% - 25px);
|
||||
width: calc(80% - 25px);
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
transition: color var(--dur-fast) var(--ease);
|
||||
}
|
||||
|
||||
.profile-comment-item__meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-left: 0;
|
||||
font-size: 12px;
|
||||
color: var(--color-ink-4);
|
||||
.profile-comment-item__content-wrap {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.profile-comment-item__meta-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
.profile-comment-item__content {
|
||||
display: -webkit-box;
|
||||
max-height: 7em;
|
||||
overflow: hidden;
|
||||
color: var(--color-ink-2);
|
||||
line-height: 1.4em;
|
||||
text-decoration: none;
|
||||
white-space: normal;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 5;
|
||||
}
|
||||
.profile-comment-item__meta-item i {
|
||||
font-size: 11px;
|
||||
opacity: 0.75;
|
||||
.profile-comment-item__content:hover {
|
||||
color: var(--color-ink-2);
|
||||
}
|
||||
.profile-comment-item__meta-sep {
|
||||
opacity: 0.5;
|
||||
.profile-comment-item__pending {
|
||||
margin-right: 6px;
|
||||
}
|
||||
.profile-comment-item__source {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.profile-comment-item__parent {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.profile-comment-item__post-link {
|
||||
color: var(--color-ink-2);
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
transition: color var(--dur-fast) var(--ease);
|
||||
}
|
||||
.profile-comment-item__post-link:hover {
|
||||
color: var(--color-ink);
|
||||
.profile-comment-more {
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
.profile-comment-more button {
|
||||
padding: 5px 15px;
|
||||
border: 0;
|
||||
border-radius: 30px;
|
||||
color: var(--color-ink-3);
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
opacity: .65;
|
||||
}
|
||||
.profile-comment-more button:hover {
|
||||
background: var(--color-line);
|
||||
opacity: 1;
|
||||
}
|
||||
.profile-comment-more button:disabled {
|
||||
cursor: wait;
|
||||
}
|
||||
|
||||
/* ==================== 分页 ==================== */
|
||||
@@ -450,49 +452,15 @@
|
||||
}
|
||||
|
||||
/* ==================== 账号设置区 ==================== */
|
||||
.settings-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.settings-section__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.settings-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
background: var(--color-primary);
|
||||
color: var(--color-on-primary, #fff);
|
||||
font-size: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.settings-icon i,
|
||||
.security-action-icon i,
|
||||
.aksrt-modal__icon i,
|
||||
.aksrt-modal__close i {
|
||||
line-height: 1;
|
||||
}
|
||||
.settings-hint {
|
||||
font-size: 13px;
|
||||
color: var(--color-ink-4);
|
||||
margin: 4px 0 12px;
|
||||
}
|
||||
.settings-hint--top {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.settings-hint--bottom {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.settings-section__title {
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
.settings-empty {
|
||||
font-size: 13px;
|
||||
color: var(--color-ink-4);
|
||||
@@ -500,22 +468,7 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ---- 第三方绑定列表 ---- */
|
||||
.binding-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.binding-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 11px 14px;
|
||||
background: var(--surface-1);
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: 8px;
|
||||
transition: border-color 0.15s, background 0.15s;
|
||||
}
|
||||
/* ---- 第三方绑定列表公共行为 ---- */
|
||||
.binding-item--linkable {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
@@ -525,25 +478,6 @@
|
||||
border-color: var(--color-primary);
|
||||
background: var(--color-primary-softer);
|
||||
}
|
||||
.binding-item__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
color: var(--color-ink);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.binding-item__icon i {
|
||||
font-size: 22px;
|
||||
line-height: 1;
|
||||
}
|
||||
.binding-item__meta {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
.binding-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -556,13 +490,9 @@
|
||||
.binding-status--idle {
|
||||
color: var(--color-ink-4);
|
||||
}
|
||||
.binding-item__name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
min-width: 0;
|
||||
.badge__icon {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* ---- 徽章 ---- */
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -574,18 +504,6 @@
|
||||
line-height: 1.5;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.badge__icon {
|
||||
line-height: 1;
|
||||
}
|
||||
.badge--bound {
|
||||
background: #ecfdf5;
|
||||
color: #059669;
|
||||
}
|
||||
.badge--bind {
|
||||
background: #eff6ff;
|
||||
color: var(--color-primary);
|
||||
border: 1px solid var(--color-primary-soft);
|
||||
}
|
||||
|
||||
/* ---- 解绑按钮 ---- */
|
||||
.binding-unbind-btn {
|
||||
@@ -603,72 +521,17 @@
|
||||
background: rgba(239, 68, 68, 0.08);
|
||||
}
|
||||
|
||||
/* ---- 安全设置操作区 ---- */
|
||||
.security-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.security-action-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
background: var(--surface-1);
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: 10px;
|
||||
}
|
||||
.security-action-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.security-action-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 10px;
|
||||
background: var(--color-primary-soft);
|
||||
color: var(--color-primary);
|
||||
flex-shrink: 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
.security-action-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
.security-action-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
.security-action-desc {
|
||||
font-size: 12px;
|
||||
color: var(--color-ink-4);
|
||||
}
|
||||
|
||||
/* legacy-style user tabs: one panel outside, flat rows inside. */
|
||||
.dashboard--public .profile-list,
|
||||
.dashboard--public .profile-comment-list,
|
||||
.uc-content #panel-posts .profile-list,
|
||||
.uc-content #panel-favorites .profile-list,
|
||||
.uc-content #panel-comments .profile-comment-list {
|
||||
.uc-content #panel-favorites .profile-list {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.dashboard--public .profile-list-item,
|
||||
.dashboard--public .profile-comment-item,
|
||||
.uc-content #panel-posts .profile-list-item,
|
||||
.uc-content #panel-favorites .profile-list-item,
|
||||
.uc-content #panel-favorites .profile-list[data-list="favorites"] .profile-list-item,
|
||||
.uc-content #panel-comments .profile-comment-item {
|
||||
.uc-content #panel-favorites .profile-list[data-list="favorites"] .profile-list-item {
|
||||
padding: 16px 0;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
@@ -678,29 +541,23 @@
|
||||
}
|
||||
|
||||
.dashboard--public .profile-list-item:hover,
|
||||
.dashboard--public .profile-comment-item:hover,
|
||||
.uc-content #panel-posts .profile-list-item:hover,
|
||||
.uc-content #panel-favorites .profile-list-item:hover,
|
||||
.uc-content #panel-favorites .profile-list[data-list="favorites"] .profile-list-item:hover,
|
||||
.uc-content #panel-comments .profile-comment-item:hover {
|
||||
.uc-content #panel-favorites .profile-list[data-list="favorites"] .profile-list-item:hover {
|
||||
background: transparent;
|
||||
border-color: var(--color-line-soft, rgba(0,0,0,0.06));
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.dashboard--public .profile-list-item:first-child,
|
||||
.dashboard--public .profile-comment-item:first-child,
|
||||
.uc-content #panel-posts .profile-list-item:first-child,
|
||||
.uc-content #panel-favorites .profile-list-item:first-child,
|
||||
.uc-content #panel-comments .profile-comment-item:first-child {
|
||||
.uc-content #panel-favorites .profile-list-item:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.dashboard--public .profile-list-item:last-child,
|
||||
.dashboard--public .profile-comment-item:last-child,
|
||||
.uc-content #panel-posts .profile-list-item:last-child,
|
||||
.uc-content #panel-favorites .profile-list-item:last-child,
|
||||
.uc-content #panel-comments .profile-comment-item:last-child {
|
||||
.uc-content #panel-favorites .profile-list-item:last-child {
|
||||
padding-bottom: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
@@ -779,26 +636,24 @@
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.dashboard--public .profile-list-item,
|
||||
.dashboard--public .profile-comment-item,
|
||||
.uc-content #panel-posts .profile-list-item,
|
||||
.uc-content #panel-favorites .profile-list-item,
|
||||
.uc-content #panel-comments .profile-comment-item {
|
||||
.uc-content #panel-favorites .profile-list-item {
|
||||
padding: 14px 0;
|
||||
}
|
||||
|
||||
.dashboard--public .profile-list-item:first-child,
|
||||
.dashboard--public .profile-comment-item:first-child,
|
||||
.uc-content #panel-posts .profile-list-item:first-child,
|
||||
.uc-content #panel-favorites .profile-list-item:first-child,
|
||||
.uc-content #panel-comments .profile-comment-item:first-child {
|
||||
.uc-content #panel-favorites .profile-list-item:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.dashboard--public .profile-list-item:last-child,
|
||||
.dashboard--public .profile-comment-item:last-child,
|
||||
.uc-content #panel-posts .profile-list-item:last-child,
|
||||
.uc-content #panel-favorites .profile-list-item:last-child,
|
||||
.uc-content #panel-comments .profile-comment-item:last-child {
|
||||
.uc-content #panel-favorites .profile-list-item:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.profile-comment-item {
|
||||
padding: 10px 0;
|
||||
}
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
/* ==================== Modal 弹窗 ==================== */
|
||||
.aksrt-modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 99998;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.aksrt-modal[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
.aksrt-modal.is-open {
|
||||
display: flex;
|
||||
}
|
||||
.aksrt-modal__overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(18, 18, 18, 0.48);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
animation: modalFadeIn 0.2s ease forwards;
|
||||
}
|
||||
.aksrt-modal__box {
|
||||
position: relative;
|
||||
width: 92%;
|
||||
max-width: 440px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
background: var(--surface-1);
|
||||
border: none;
|
||||
border-radius: var(--radius-2xl, 16px);
|
||||
box-shadow: var(--shadow-drawer);
|
||||
animation: modalSlideUp 0.28s cubic-bezier(0.22, 1, 0.36, 1) forwards;
|
||||
}
|
||||
@keyframes modalFadeIn { from { opacity: 0 } to { opacity: 1 } }
|
||||
@keyframes modalSlideUp {
|
||||
from { opacity: 0; transform: translateY(24px) scale(0.96); }
|
||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||
}
|
||||
|
||||
/* Modal 头部 */
|
||||
.aksrt-modal__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 18px 20px;
|
||||
border-bottom: 1px solid var(--color-line);
|
||||
}
|
||||
.aksrt-modal__title-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.aksrt-modal__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 8px;
|
||||
background: var(--color-primary-soft);
|
||||
color: var(--color-primary);
|
||||
font-size: 16px;
|
||||
}
|
||||
.aksrt-modal__title-wrap h4 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
.aksrt-modal__close {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
color: var(--color-ink-4);
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
}
|
||||
.aksrt-modal__close:hover {
|
||||
background: var(--surface-2);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
/* Modal 表单字段 */
|
||||
.aksrt-modal__box form {
|
||||
padding: 20px;
|
||||
}
|
||||
.modal-field {
|
||||
position: relative;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.modal-field label {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--color-ink-2);
|
||||
}
|
||||
.modal-field input[type="password"],
|
||||
.modal-field input[type="email"],
|
||||
.modal-field input[type="tel"],
|
||||
.modal-field input[type="text"] {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 10px 40px 10px 14px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: var(--color-ink);
|
||||
background: var(--surface-4, var(--surface-2));
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: 8px;
|
||||
outline: none;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.modal-field input:focus {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 3px var(--color-primary-soft);
|
||||
background: var(--surface-1);
|
||||
}
|
||||
.toggle-pwd {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 31px;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-ink-4);
|
||||
font-size: 16px;
|
||||
}
|
||||
.toggle-pwd:hover {
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
/* 只读显示(当前邮箱) */
|
||||
.readonly-display {
|
||||
display: block;
|
||||
padding: 10px 14px;
|
||||
font-size: 14px;
|
||||
color: var(--color-ink-4);
|
||||
background: var(--surface-2);
|
||||
border: 1.5px solid var(--color-line);
|
||||
border-radius: 8px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* 验证码输入组 */
|
||||
.code-input-group {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: stretch;
|
||||
}
|
||||
.code-input-group input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 10px 14px !important;
|
||||
font-family: inherit !important;
|
||||
letter-spacing: 2px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.code-send-btn {
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
min-width: 110px;
|
||||
}
|
||||
.code-send-btn.is-counting {
|
||||
pointer-events: none;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
/* 密码强度条 */
|
||||
.pwd-strength {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
margin-top: 7px;
|
||||
height: 4px;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
background: var(--surface-3);
|
||||
}
|
||||
.pwd-strength__fill {
|
||||
width: 0%;
|
||||
height: 100%;
|
||||
border-radius: 2px;
|
||||
transition: width 0.3s ease, background-color 0.3s ease;
|
||||
background-color: var(--color-error);
|
||||
}
|
||||
.pwd-strength__fill.level-weak { width: 33%; background-color: var(--color-error); }
|
||||
.pwd-strength__fill.level-medium { width: 66%; background-color: var(--color-warning); }
|
||||
.pwd-strength__fill.level-strong { width: 100%; background-color: var(--color-success); }
|
||||
.pwd-strength__text {
|
||||
font-size: 11px;
|
||||
margin-top: 4px;
|
||||
font-weight: 500;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
.pwd-strength__text.level-weak { color: var(--color-error); }
|
||||
.pwd-strength__text.level-medium { color: var(--color-warning); }
|
||||
.pwd-strength__text.level-strong { color: var(--color-success); }
|
||||
|
||||
/* 人机验证容器在弹窗中的适配 */
|
||||
.aksrt-modal .aksrt-captcha-wrap {
|
||||
margin: 14px 0 8px;
|
||||
}
|
||||
.aksrt-modal .aksrt-captcha-box {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
/* Modal 底部按钮栏 */
|
||||
.aksrt-modal__footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--color-line);
|
||||
}
|
||||
|
||||
/* Modal mobile */
|
||||
@media (max-width: 640px) {
|
||||
.aksrt-modal__box {
|
||||
width: 96%;
|
||||
max-height: 92vh;
|
||||
border-radius: 14px;
|
||||
}
|
||||
.aksrt-modal__footer {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
.aksrt-modal__footer .btn,
|
||||
.aksrt-modal__footer button {
|
||||
width: 100%;
|
||||
}
|
||||
.code-input-group {
|
||||
flex-direction: column;
|
||||
}
|
||||
.code-send-btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -304,12 +304,24 @@
|
||||
body.uc-modal-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
.btn-block {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
.but.jb-blue,
|
||||
.but.b-blue-2 {
|
||||
/* Scoped to the user-center content shell. These are the credits panel's own
|
||||
* button skins, and every element they target renders inside .uc-content (the
|
||||
* panels and their modals are all emitted through templates/parts/user-center/
|
||||
* panel.php, which templates/pages/page-profile.php wraps in .uc-content).
|
||||
*
|
||||
* Left unscoped they are (0,2,0) rules that load after public.css, so they
|
||||
* out-rank the legacy .but.radius pill and flatten every .but.jb-blue on the
|
||||
* page down to 4px. The top-nav publish chip is .but.jb-blue.radius, so on the
|
||||
* user-center route it lost its 50px pill and no longer matched the homepage.
|
||||
* Site-wide .but.jb-blue callers outside the panel (login, submit, badge and
|
||||
* cashier buttons) also picked up the flat #3b82f6 skin instead of the legacy
|
||||
* gradient.
|
||||
*
|
||||
* The .btn-block restatement that used to sit here was byte-identical to the
|
||||
* one in core/buttons.css, which loads earlier on every page, so it could never
|
||||
* win and is dropped rather than scoped. */
|
||||
.uc-content .but.jb-blue,
|
||||
.uc-content .but.b-blue-2 {
|
||||
background: #3b82f6;
|
||||
color: #fff;
|
||||
border: 0;
|
||||
@@ -317,18 +329,18 @@ body.uc-modal-open {
|
||||
padding: 0.45em 1em;
|
||||
cursor: pointer;
|
||||
}
|
||||
.but.c-blue-2.hollow,
|
||||
.but.c-green.hollow {
|
||||
.uc-content .but.c-blue-2.hollow,
|
||||
.uc-content .but.c-green.hollow {
|
||||
background: transparent;
|
||||
border: 1px solid currentColor;
|
||||
border-radius: 4px;
|
||||
padding: 0.4em 0.9em;
|
||||
cursor: pointer;
|
||||
}
|
||||
.but.c-blue-2 {
|
||||
.uc-content .but.c-blue-2 {
|
||||
color: #3b82f6;
|
||||
}
|
||||
.but.c-green {
|
||||
.uc-content .but.c-green {
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
|
||||
@@ -214,9 +214,3 @@
|
||||
.uc-content .uc-revenue-summary-row--explain { grid-template-columns: 1fr; }
|
||||
.uc-content .uc-revenue-summary-row--explain .uc-reward-richtext { grid-column: 1; }
|
||||
}
|
||||
|
||||
/* 推广海报预览弹窗:舞台居中,canvas 自适应弹窗宽度。 */
|
||||
.uc-content .uc-affiliate-poster { display: grid; place-items: center; min-height: 120px; }
|
||||
.uc-content .uc-affiliate-poster img,
|
||||
.uc-content .uc-affiliate-poster canvas { display: block; max-width: 100%; height: auto; margin: 0 auto; border-radius: 12px; background: var(--surface-2, #f7f8fa); }
|
||||
.uc-content .uc-affiliate-poster p { margin: 0; text-align: center; }
|
||||
@@ -144,52 +144,153 @@
|
||||
min-width: 112px;
|
||||
}
|
||||
|
||||
/* 设置区增强 */
|
||||
.uc-content .settings-section {
|
||||
margin-bottom: 24px;
|
||||
/* 账号设置:按参考主题 account-set / box-body / oauth-bind-box 分区与排布。 */
|
||||
.uc-content #panel-settings .aksrt-account-settings {
|
||||
margin: -24px;
|
||||
overflow: hidden;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
.uc-content .settings-section__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 16px;
|
||||
.aksrt-account-settings__section {
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid var(--color-line-soft, rgba(0, 0, 0, .06));
|
||||
}
|
||||
|
||||
.uc-content .settings-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 10px;
|
||||
.aksrt-account-settings__section:last-child { border-bottom: 0; }
|
||||
|
||||
.aksrt-account-settings__heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.aksrt-account-settings__heading .title-h-left {
|
||||
margin: 0 0 8px;
|
||||
font-size: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.uc-content .settings-section__title {
|
||||
.aksrt-account-settings__heading p {
|
||||
margin: 0;
|
||||
border: 0;
|
||||
font-size: 16px;
|
||||
color: var(--muted-2-color, var(--color-ink-4, #999));
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.uc-content .settings-section__title::before {
|
||||
display: none;
|
||||
.aksrt-account-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* 安全操作项增强 */
|
||||
.uc-content .security-action-item {
|
||||
border: 1px solid var(--color-line-soft, rgba(0,0,0,0.06));
|
||||
border-radius: 10px;
|
||||
padding: 16px;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
.aksrt-account-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(116px, .85fr) minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
padding: 12px 14px;
|
||||
border-radius: var(--main-radius, 8px);
|
||||
background: var(--muted-border-color, var(--surface-2, #f5f6f7));
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.uc-content .security-action-item:hover {
|
||||
border-color: var(--color-primary-soft);
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
|
||||
.aksrt-account-row__identity {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.aksrt-account-row__icon {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
flex: 0 0 26px;
|
||||
border-radius: 50%;
|
||||
background: var(--this-bg, rgba(41, 151, 247, .12));
|
||||
color: var(--this-color, #2997f7);
|
||||
}
|
||||
|
||||
.aksrt-account-row__icon--blue { --this-bg: rgba(41, 151, 247, .12); --this-color: #2997f7; }
|
||||
.aksrt-account-row__icon--blue-2 { --this-bg: rgba(73, 174, 231, .12); --this-color: #49aee7; }
|
||||
.aksrt-account-row__icon--purple { --this-bg: rgba(136, 84, 230, .12); --this-color: #8854e6; }
|
||||
|
||||
.aksrt-account-row__copy {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: var(--color-ink, #333);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.aksrt-account-row__social-name {
|
||||
overflow: hidden;
|
||||
color: var(--color-ink, #333);
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.aksrt-account-row__value {
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
color: var(--muted-2-color, var(--color-ink-3, #777));
|
||||
font-size: 12px;
|
||||
text-align: right;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.aksrt-account-row__action {
|
||||
flex: 0 0 auto;
|
||||
margin: 0;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.aksrt-account-row--simple { grid-template-columns: minmax(0, 1fr) auto; }
|
||||
.aksrt-account-row__action--unavailable { color: var(--muted-2-color, #999); }
|
||||
|
||||
.aksrt-account-settings__section--social .binding-captcha { margin-bottom: 12px; }
|
||||
.aksrt-account-list--social .binding-item { border: 0; }
|
||||
.aksrt-account-list--social .aksrt-account-row { display: grid; grid-template-columns: 28px minmax(0, 1fr) auto auto; text-decoration: none; }
|
||||
.aksrt-account-list--social .binding-status { display: inline-flex; align-items: center; gap: 4px; color: var(--color-success, #16a34a); font-size: 11px; white-space: nowrap; }
|
||||
.aksrt-account-list--social .binding-status--idle { color: var(--muted-2-color, var(--color-ink-4, #999)); }
|
||||
.aksrt-account-list--social .binding-unbind-btn { color: var(--this-color, #e4a11b); }
|
||||
.aksrt-account-list--social .binding-item--linkable:hover { background: var(--color-primary-softer, var(--color-primary-soft, rgba(41, 151, 247, .08))); }
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.aksrt-account-list { grid-template-columns: minmax(0, 1fr); }
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.uc-content #panel-settings .aksrt-account-settings { margin: -16px; }
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.uc-content #panel-settings .aksrt-account-settings { margin: -16px; }
|
||||
.aksrt-account-settings__section { padding: 15px; }
|
||||
.aksrt-account-row { grid-template-columns: minmax(0, 1fr) auto; gap: 8px 10px; padding: 12px; }
|
||||
.aksrt-account-row__identity { grid-column: 1; }
|
||||
.aksrt-account-row__value { grid-column: 1; grid-row: 2; padding-left: 38px; text-align: left; }
|
||||
.aksrt-account-row__action { grid-column: 2; grid-row: 1 / span 2; }
|
||||
.aksrt-account-list--social .aksrt-account-row { grid-template-columns: 24px minmax(0, 1fr) auto; gap: 6px 8px; }
|
||||
.aksrt-account-list--social .binding-status { grid-column: 2; grid-row: 2; }
|
||||
.aksrt-account-list--social .aksrt-account-row__action { grid-column: 3; grid-row: 1 / span 2; }
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.uc-content #panel-settings .aksrt-account-settings { margin: -14px; }
|
||||
}
|
||||
|
||||
/* 邀请区增强 */
|
||||
@@ -502,6 +603,186 @@
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* 账号设置弹窗:使用 AksrtModalRuntime,几何和标题区对齐参考 modal-mini。 */
|
||||
.aksrt-modal-runtime.aksrt-user-settings-modal:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog {
|
||||
width: 400px;
|
||||
max-width: calc(100vw - 20px);
|
||||
max-height: calc(100vh - 20px);
|
||||
max-height: calc(100dvh - 20px);
|
||||
overflow: auto;
|
||||
border-radius: var(--main-radius, 8px);
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, .15);
|
||||
}
|
||||
|
||||
.aksrt-modal-runtime.aksrt-user-settings-modal.aksrt-modal-runtime--bottom .aksrt-modal-runtime__dialog {
|
||||
width: 400px;
|
||||
max-width: calc(100vw - 20px);
|
||||
max-height: calc(100dvh - 20px - env(safe-area-inset-bottom));
|
||||
margin: 0 auto calc(10px + env(safe-area-inset-bottom));
|
||||
border-radius: var(--main-radius, 8px) var(--main-radius, 8px) 0 0;
|
||||
box-shadow: 0 -10px 50px rgba(0, 0, 0, .15);
|
||||
}
|
||||
|
||||
.aksrt-user-settings-modal .aksrt-modal-runtime__content { padding: 0; }
|
||||
.aksrt-modal-runtime--bottom.aksrt-user-settings-modal .aksrt-settings-modal {
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
border-radius: var(--main-radius, 8px) var(--main-radius, 8px) 0 0;
|
||||
}
|
||||
|
||||
.aksrt-settings-modal {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: var(--main-radius, 8px);
|
||||
background: var(--main-bg-color, var(--surface-1, #fff));
|
||||
}
|
||||
|
||||
.aksrt-settings-modal [hidden] { display: none !important; }
|
||||
|
||||
.aksrt-settings-modal .modal-colorful-header {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
height: 120px;
|
||||
border-radius: var(--main-radius, 8px) var(--main-radius, 8px) 0 0;
|
||||
}
|
||||
|
||||
.aksrt-settings-modal .modal-colorful-header > .close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 9;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--this-color);
|
||||
font-size: 18px;
|
||||
line-height: 30px;
|
||||
opacity: .6;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.aksrt-settings-modal > form {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
margin-top: 120px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.aksrt-settings-modal__pane {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
margin-top: 120px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.aksrt-settings-modal__pane form,
|
||||
.aksrt-settings-modal .account-step form {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.aksrt-settings-modal .account-step-list {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
margin-top: 120px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.aksrt-settings-modal .account-step-panels { position: relative; z-index: 0; }
|
||||
.aksrt-settings-modal .account-step { display: none; padding: 0 15px 18px; }
|
||||
.aksrt-settings-modal .account-step.is-active { display: block; }
|
||||
.aksrt-settings-modal .account-step > p { margin: 0 0 20px; }
|
||||
.aksrt-settings-modal .account-recovery-toggle { border: 0; background: transparent; cursor: pointer; }
|
||||
|
||||
.aksrt-settings-modal__fields { padding: 0; }
|
||||
.aksrt-settings-modal .modal-line-field--code .line-form-input { padding-right: 7em; }
|
||||
.aksrt-settings-modal .modal-line-field--code .yztx.abs-right {
|
||||
top: 50%;
|
||||
right: .7em;
|
||||
z-index: 2;
|
||||
margin-top: 3px;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
.aksrt-settings-modal .modal-line-field--code .code-send-btn { display: inline-block; margin: 0; }
|
||||
.aksrt-settings-modal .toggle-pwd { position: absolute; right: 7px; bottom: 4px; width: 30px; height: 30px; padding: 0; border: 0; background: transparent; color: var(--muted-2-color, #999); cursor: pointer; }
|
||||
.aksrt-settings-modal .toggle-pwd:hover { color: var(--color-ink, #333); }
|
||||
.aksrt-settings-modal .code-send-btn { white-space: nowrap; }
|
||||
.aksrt-settings-modal .code-send-btn.is-counting { pointer-events: none; opacity: .6; }
|
||||
.aksrt-settings-modal .aksrt-captcha-wrap { margin: 12px 0 8px; }
|
||||
|
||||
/* 修改手机:脱敏中间位数字框,与前缀/后缀号码一体呈现 */
|
||||
.aksrt-settings-modal .aksrt-phone-digits {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.aksrt-settings-modal .aksrt-phone-digit__edge {
|
||||
padding: 0 2px;
|
||||
color: var(--color-ink, #333);
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
letter-spacing: 1px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.aksrt-settings-modal .aksrt-phone-digit {
|
||||
width: 44px;
|
||||
height: 50px;
|
||||
padding: 0;
|
||||
border: 1.5px solid var(--muted-3-color, #c9d1d9);
|
||||
border-radius: 10px;
|
||||
background-color: var(--main-bg-color, #f7f8fa);
|
||||
color: var(--color-ink, #333);
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
caret-color: var(--color-primary, #2a7de1);
|
||||
font-variant-numeric: tabular-nums;
|
||||
outline: none;
|
||||
transition: border-color .15s ease, background-color .15s ease, box-shadow .15s ease;
|
||||
}
|
||||
|
||||
.aksrt-settings-modal .aksrt-phone-digit:focus {
|
||||
border-color: var(--color-primary, #2a7de1);
|
||||
background-color: var(--main-bg-color, #fff);
|
||||
box-shadow: 0 0 0 3px rgba(42, 125, 225, 0.18);
|
||||
}
|
||||
|
||||
.aksrt-settings-modal .aksrt-phone-digits.has-error .aksrt-phone-digit {
|
||||
border-color: var(--color-danger, #e02020);
|
||||
background-color: rgba(224, 32, 32, 0.06);
|
||||
box-shadow: 0 0 0 3px rgba(224, 32, 32, 0.12);
|
||||
}
|
||||
|
||||
.aksrt-settings-modal .aksrt-phone-digits__error {
|
||||
margin: 0 0 12px;
|
||||
color: var(--color-danger, #e02020);
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.aksrt-modal-runtime.aksrt-user-settings-modal.aksrt-modal-runtime--bottom .aksrt-modal-runtime__dialog {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
max-height: calc(100dvh - env(safe-area-inset-bottom));
|
||||
margin: 0 0 -1px;
|
||||
border-radius: 16px 16px 0 0;
|
||||
}
|
||||
.aksrt-modal-runtime--bottom.aksrt-user-settings-modal .aksrt-settings-modal {
|
||||
border-radius: 16px 16px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ==================== 官方认证面板 ==================== */
|
||||
/* 面板内容为 Zibll 同款 colorful-bg hero 卡片(参考主题 1:1),去掉外层白卡壳 */
|
||||
@@ -519,29 +800,204 @@
|
||||
margin: 20px 0 0;
|
||||
}
|
||||
|
||||
.uc-auth-modal__dialog { width: min(620px, calc(100vw - 28px)); max-width: 620px; max-height: min(760px, calc(100vh - 40px)); overflow: auto; background: var(--surface-1, #fff); }
|
||||
.aksrt-identity-modal-shell .aksrt-modal-runtime__dialog { width: min(620px, calc(100vw - 28px)); max-width: 620px; }
|
||||
.aksrt-identity-modal-shell .uc-auth-modal__dialog { width: auto; max-width: none; max-height: none; overflow: visible; background: transparent; }
|
||||
.uc-auth-modal__header > div { display: flex; align-items: center; gap: 10px; }
|
||||
.uc-auth-modal__header > div > span { display: grid; place-items: center; width: 34px; height: 34px; border-radius: 11px; background: var(--color-primary-soft); color: var(--color-primary); }
|
||||
.uc-auth-modal__body { padding: 22px; }
|
||||
|
||||
.uc-auth-form { display: grid; gap: 17px; }
|
||||
.uc-auth-field { display: grid; gap: 7px; color: var(--color-ink-2); font-size: 13px; font-weight: 600; }
|
||||
.uc-auth-enterprise-fields { display: grid; gap: 17px; }
|
||||
.uc-auth-enterprise-fields[hidden] { display: none; }
|
||||
.uc-auth-form .form-control { width: 100%; min-height: 42px; padding: 9px 11px; border: 1px solid var(--color-line-soft, rgba(15, 23, 42, 0.1)); border-radius: 9px; background: var(--surface-1); color: var(--color-ink); font: inherit; font-weight: 400; box-sizing: border-box; }
|
||||
.aksrt-identity-modal-shell .aksrt-modal-runtime__dialog {
|
||||
width: min(600px, calc(100vw - 20px));
|
||||
max-width: 600px;
|
||||
max-height: calc(100vh - 20px);
|
||||
max-height: calc(100dvh - 20px);
|
||||
overflow: auto;
|
||||
border-radius: var(--main-radius, 8px);
|
||||
background: var(--main-bg-color, var(--surface-1, #fff));
|
||||
}
|
||||
.aksrt-identity-modal-shell .aksrt-modal-runtime__content { padding: 0; }
|
||||
.uc-auth-modal {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: 1px;
|
||||
border-radius: var(--main-radius, 8px);
|
||||
background: var(--main-bg-color, var(--surface-1, #fff));
|
||||
}
|
||||
.uc-auth-modal__header,
|
||||
.uc-auth-modal__header.colorful-bg {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 1px;
|
||||
left: 1px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 120px;
|
||||
overflow: hidden;
|
||||
border-radius: var(--main-radius, 8px) var(--main-radius, 8px) 0 0;
|
||||
background: var(--this-bg);
|
||||
background-size: 120%;
|
||||
color: var(--this-color);
|
||||
}
|
||||
.uc-auth-modal__header > .close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 9;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--this-color);
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
opacity: .6;
|
||||
cursor: pointer;
|
||||
}
|
||||
.uc-auth-modal__header > .close:hover { opacity: 1; }
|
||||
.uc-auth-modal__header .colorful-make,
|
||||
.uc-auth-modal__header .colorful-make::before,
|
||||
.uc-auth-modal__header .colorful-make::after {
|
||||
position: absolute;
|
||||
border-radius: 100%;
|
||||
background: var(--this-bg);
|
||||
color: var(--this-color);
|
||||
content: '';
|
||||
}
|
||||
.uc-auth-modal__header .colorful-make {
|
||||
top: 60%;
|
||||
bottom: 0;
|
||||
width: 323px;
|
||||
height: 290px;
|
||||
animation: uc-auth-colorful 120s linear infinite;
|
||||
}
|
||||
.uc-auth-modal__header .colorful-make::before {
|
||||
top: -325px;
|
||||
right: -558px;
|
||||
bottom: 0;
|
||||
width: 677px;
|
||||
height: 300px;
|
||||
transform: rotate(341deg);
|
||||
opacity: .6;
|
||||
}
|
||||
.uc-auth-modal__header .colorful-make::after {
|
||||
top: -170px;
|
||||
bottom: 0;
|
||||
left: -262px;
|
||||
width: 402px;
|
||||
height: 214px;
|
||||
transform: rotate(326deg);
|
||||
opacity: .4;
|
||||
}
|
||||
.uc-auth-modal__header .colorful-make + * { position: relative; }
|
||||
.uc-auth-modal__body {
|
||||
position: relative;
|
||||
margin-top: 120px;
|
||||
padding: 15px;
|
||||
background: var(--main-bg-color, var(--surface-1, #fff));
|
||||
}
|
||||
.uc-auth-form { margin: 0; }
|
||||
.uc-auth-form__scroll { max-height: 50vh; max-height: 50dvh; }
|
||||
/* 申请介绍的列表:标记随列表项内容一起绘制(悬挂缩进),
|
||||
不会被外层滚动区的左边界裁掉一半。ul/ol/li 都清原生标记,
|
||||
因为后台可能直接写裸 li(没有 ul 包裹)。 */
|
||||
.uc-auth-intro ul,
|
||||
.uc-auth-intro ol,
|
||||
.uc-auth-intro li {
|
||||
list-style: none !important;
|
||||
}
|
||||
.uc-auth-intro ul,
|
||||
.uc-auth-intro ol {
|
||||
margin: .6em 0 1em;
|
||||
padding-left: 0;
|
||||
}
|
||||
.uc-auth-intro li {
|
||||
margin-bottom: .35em;
|
||||
padding-left: 1.25em;
|
||||
text-indent: -1.25em;
|
||||
}
|
||||
.uc-auth-intro li::before {
|
||||
content: "\2022";
|
||||
display: inline-block;
|
||||
width: 1.25em;
|
||||
text-indent: 0;
|
||||
}
|
||||
.uc-auth-intro ol { counter-reset: uc-auth-intro; }
|
||||
.uc-auth-intro ol li::before {
|
||||
content: counter(uc-auth-intro) ".";
|
||||
counter-increment: uc-auth-intro;
|
||||
width: auto;
|
||||
min-width: 1.25em;
|
||||
}
|
||||
.uc-auth-intro li > ul,
|
||||
.uc-auth-intro li > ol { margin: .35em 0 0 1.25em; }
|
||||
.uc-auth-field { display: block; color: var(--muted-2-color); font-size: .9em; font-weight: 400; }
|
||||
.uc-auth-field__label { display: block; margin-bottom: 6px; }
|
||||
.uc-auth-form .form-control { width: 100%; box-sizing: border-box; }
|
||||
.uc-auth-form textarea.form-control { resize: vertical; }
|
||||
.uc-auth-consent { display: flex; align-items: flex-start; gap: 8px; color: var(--color-ink-3); font-size: 13px; font-weight: 400; line-height: 1.6; }
|
||||
.uc-auth-consent input { flex: 0 0 auto; margin-top: 3px; }
|
||||
.uc-auth-submit { display: inline-flex; align-items: center; justify-content: center; gap: 7px; min-height: 44px; }
|
||||
.uc-auth-form__message { min-height: 18px; color: var(--color-error, #dc2626); font-size: 12px; text-align: center; }
|
||||
|
||||
.uc-auth-pending { text-align: center; padding: 4px 0 18px; }
|
||||
.uc-auth-pending > span { display: grid; place-items: center; width: 58px; height: 58px; margin: 0 auto 12px; border-radius: 18px; background: rgba(245, 158, 11, 0.14); color: #a66e0b; font-size: 24px; }
|
||||
.uc-auth-pending h3 { margin: 0 0 7px; }
|
||||
.uc-auth-pending p { margin: 0; color: var(--color-ink-4); font-size: 13px; }
|
||||
.uc-auth-upload .preview .add {
|
||||
appearance: none;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.uc-auth-upload .preview-item { overflow: hidden; }
|
||||
.uc-auth-upload .preview-item img { object-fit: contain; }
|
||||
.uc-auth-preview-remove {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
background: #fd2c53;
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
.aksrt-identity-modal-shell .uc-auth-modal .uc-auth-modal__body .uc-auth-modal__footer {
|
||||
display: flex;
|
||||
grid-template-columns: none;
|
||||
gap: 0;
|
||||
margin: 0 -15px -15px;
|
||||
overflow: hidden;
|
||||
border-top: 1px solid var(--main-border-color, var(--color-line, rgba(50, 50, 50, .12)));
|
||||
border-radius: 0 0 var(--main-radius, 8px) var(--main-radius, 8px);
|
||||
}
|
||||
.aksrt-identity-modal-shell .uc-auth-modal .uc-auth-modal__body .uc-auth-modal__footer > .but {
|
||||
display: flex;
|
||||
flex: 1 1 50%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 48px;
|
||||
margin: 0 !important;
|
||||
padding: .8em;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
color: var(--muted-color, #888);
|
||||
font: inherit;
|
||||
font-weight: 400;
|
||||
cursor: pointer;
|
||||
}
|
||||
.aksrt-identity-modal-shell .uc-auth-modal .uc-auth-modal__body .uc-auth-modal__footer > .but + .but {
|
||||
border-left: 1px solid var(--main-border-color, var(--color-line, rgba(50, 50, 50, .12)));
|
||||
}
|
||||
.aksrt-identity-modal-shell .uc-auth-modal .uc-auth-modal__body .uc-auth-modal__footer > .but.c-blue {
|
||||
background: transparent;
|
||||
color: var(--this-color, #2997f7);
|
||||
}
|
||||
.aksrt-identity-modal-shell .uc-auth-modal .uc-auth-modal__body .uc-auth-modal__footer > .but:hover {
|
||||
background: var(--this-bg, rgba(136, 136, 136, .1));
|
||||
}
|
||||
.aksrt-identity-modal-shell .uc-auth-modal .uc-auth-modal__body .uc-auth-modal__footer > .but:disabled { opacity: .5; cursor: wait; }
|
||||
@keyframes uc-auth-colorful { to { transform: rotate(360deg); } }
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.uc-auth-modal__dialog { position: fixed; inset: auto 0 0; width: 100%; max-width: none; max-height: 88vh; margin: 0; border-radius: 18px 18px 0 0; }
|
||||
@media (max-width: 768px) {
|
||||
.aksrt-identity-modal-shell.aksrt-modal-runtime--bottom .aksrt-modal-runtime__dialog {
|
||||
width: calc(100% - 20px);
|
||||
max-width: none;
|
||||
max-height: calc(100dvh - 30px - env(safe-area-inset-bottom));
|
||||
margin: 0 10px calc(10px + env(safe-area-inset-bottom));
|
||||
}
|
||||
}
|
||||
@@ -622,25 +622,6 @@ body.aksrt-user-center .uc-content,
|
||||
}
|
||||
}
|
||||
|
||||
.uc-service-grid__icon small {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
right: -7px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
min-width: 17px;
|
||||
height: 17px;
|
||||
padding: 0 4px;
|
||||
border: 2px solid var(--surface-1);
|
||||
border-radius: 99px;
|
||||
background: var(--color-error, #ef4444);
|
||||
color: #fff;
|
||||
font-size: 9px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.uc-sidebar__pair {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
@@ -684,7 +665,8 @@ body.aksrt-user-center .uc-content,
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.uc-summary-card__body > i {
|
||||
.uc-summary-card__body > i,
|
||||
.uc-summary-card__body > svg {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
@@ -716,14 +698,26 @@ body.aksrt-user-center .uc-content,
|
||||
|
||||
.uc-center-card strong { font-size: 13px; }
|
||||
.uc-center-card small { margin-top: 3px; color: var(--color-ink-4); font-size: 10px; }
|
||||
.uc-center-card > i { color: var(--color-ink-4); font-size: 11px; }
|
||||
.uc-center-card > i,
|
||||
.uc-center-card > svg { color: var(--color-ink-4); font-size: 11px; }
|
||||
.uc-center-card--creation .uc-center-card__icon { color: #6650c7; background: rgba(124, 92, 220, 0.12); }
|
||||
.uc-center-card--revenue .uc-center-card__icon { color: #ce6b35; background: rgba(234, 112, 55, 0.12); }
|
||||
|
||||
.uc-service-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
/* Services / settings grids, ported from the reference theme sidebar:
|
||||
four per row, no tinted tile behind the icon. */
|
||||
.user-icon-but-box > a {
|
||||
width: calc(25% - 10px);
|
||||
margin: 5px;
|
||||
min-width: 50px;
|
||||
max-width: 100px;
|
||||
color: var(--color-ink-3);
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.user-icon-but-box > a:hover,
|
||||
.user-icon-but-box > a.is-active {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.uc-follow-management__list { display: grid; gap: 10px; margin-top: 16px; }
|
||||
@@ -735,44 +729,3 @@ body.aksrt-user-center .uc-content,
|
||||
.uc-follow-management__actions { display: flex; flex-wrap: wrap; gap: 8px; margin-left: auto; }
|
||||
@media (max-width: 560px) { .uc-follow-management__item { align-items: flex-start; flex-wrap: wrap; } .uc-follow-management__actions { width: 100%; margin-left: 60px; } }
|
||||
|
||||
.uc-service-grid__item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
min-width: 0;
|
||||
padding: 4px 2px;
|
||||
color: var(--color-ink-3);
|
||||
font-size: 11px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.uc-service-grid__icon {
|
||||
position: relative;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 14px;
|
||||
background: var(--surface-2);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.uc-service-grid__item--cart .uc-service-grid__icon { color: #dd6941; background: rgba(238, 109, 61, 0.12); }
|
||||
.uc-service-grid__item--level .uc-service-grid__icon { color: #9b6b0a; background: rgba(234, 179, 8, 0.13); }
|
||||
.uc-service-grid__item--revenue .uc-service-grid__icon { color: #b8377d; background: rgba(219, 39, 119, 0.12); }
|
||||
.uc-service-grid__item--auth .uc-service-grid__icon { color: #287bc4; background: rgba(14, 165, 233, 0.12); }
|
||||
.uc-service-grid__item--orders .uc-service-grid__icon { color: #c05621; background: rgba(234, 88, 12, 0.12); }
|
||||
.uc-service-grid__item--notice .uc-service-grid__icon { color: #d04b65; background: rgba(244, 63, 94, 0.11); }
|
||||
.uc-service-grid__item--profile .uc-service-grid__icon { color: #6550c7; background: rgba(124, 92, 220, 0.12); }
|
||||
.uc-service-grid__item--security .uc-service-grid__icon { color: #16816c; background: rgba(16, 185, 129, 0.12); }
|
||||
|
||||
.uc-service-grid__item:hover,
|
||||
.uc-service-grid__item.is-active {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.uc-service-grid__item.is-active .uc-service-grid__icon {
|
||||
box-shadow: 0 0 0 2px var(--color-primary-soft), 0 5px 14px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
@@ -88,6 +88,8 @@
|
||||
.aksrt-author-hero__avatar img {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
/* 公开主页 hero 头像保持 legacy 圆角方形:显式声明圆角,避免被 public 捆绑里
|
||||
同特异性(0,1,1)的全局头像原语 img.avatar 抢先。 */
|
||||
border-radius: var(--radius);
|
||||
border: 4px solid var(--surface-1);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
||||
@@ -390,6 +392,34 @@
|
||||
width: auto; /* 覆盖 vendored site-layout-1 的 width: calc(50% - 16px),让卡片填满网格单元 */
|
||||
margin: 0 !important; /* 抵消 vendored .posts-row > * 的 8px !important 边距 */
|
||||
}
|
||||
|
||||
/* 骨架是网格容器自己的直接子节点:不显式跨列,它就会被当成一张卡片,
|
||||
只分到一半宽度,整摞骨架全挤在单列里(翻页 / 切 tab 的占位因此变形)。
|
||||
跨满全部列,并在加载态把 loader 自身变成与真实卡片同款的双列网格,
|
||||
骨架行才会与卡片同宽、同轴、同列数。display 只随 is-loading 打开,
|
||||
未加载时容器仍是 display:none,不会被提前撑开。 */
|
||||
.aksrt-user-home .aksrt-user-home__post-list[data-view="list"] > .post_ajax_loader {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.aksrt-user-home .dashboard-panel.is-loading .aksrt-user-home__post-list[data-view="list"] > .post_ajax_loader {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
/* 骨架跟随网格走:容器 margin 已归零、卡片边距已清零,
|
||||
不能再叠加 .posts-row > .post_ajax_loader 内层的 8px 补偿;
|
||||
宽高沿用真实卡片的网格单元,所以 vendored site-layout-1 的
|
||||
calc(50% - 16px) 与剩余边距也要一并清零。 */
|
||||
.aksrt-user-home .aksrt-user-home__post-list[data-view="list"] > .post_ajax_loader .posts-item {
|
||||
margin-right: 0;
|
||||
margin-left: 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
@@ -488,6 +518,53 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.aksrt-user-home .dashboard-panel .uh-follow__list--placeholder {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.aksrt-user-home .dashboard-panel.is-loading .uh-follow__list--placeholder {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.aksrt-user-home .dashboard-panel.is-loading .uh-follow__subtabs,
|
||||
.aksrt-user-home .dashboard-panel.is-loading .uh-follow__list:not(.uh-follow__list--placeholder),
|
||||
.aksrt-user-home .dashboard-panel.is-loading .uh-pagination,
|
||||
.aksrt-user-home .dashboard-panel.is-loading .profile-empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.aksrt-user-home .dashboard-panel.is-load-error .uh-follow__list--placeholder {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.aksrt-user-home .uh-panel-load-error {
|
||||
display: none;
|
||||
margin: 12px 0;
|
||||
color: var(--color-ink-4);
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.aksrt-user-home .dashboard-panel.is-load-error .uh-panel-load-error {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.aksrt-user-home .uh-panel-load-error button {
|
||||
margin-left: 8px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
color: var(--color-primary);
|
||||
background: transparent;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.aksrt-user-home .uh-follow__placeholder {
|
||||
display: inline-flex;
|
||||
margin: 5px 8px;
|
||||
}
|
||||
|
||||
.aksrt-user-home .uh-follow__pagination {
|
||||
margin-top: 16px;
|
||||
text-align: center;
|
||||
@@ -548,7 +625,55 @@
|
||||
}
|
||||
|
||||
.aksrt-user-home .dashboard-panel.is-loading {
|
||||
opacity: .6;
|
||||
transition: opacity var(--dur-fast, .15s) var(--ease, ease);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 面板翻页的加载态由列表骨架承担(与文章列表 .post_ajax_loader 同一契约)。 */
|
||||
.aksrt-user-home .dashboard-panel .post_ajax_loader {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 加载期收起旧页内容:翻页 / 切 tab 是整页替换语义(author.js 最终就是
|
||||
panel.innerHTML = html),骨架必须独占内容区,而不是追加在旧卡片下方 ——
|
||||
否则先看到「第 1 页卡片 + 脚下一摞骨架」,等接口返回才换页。
|
||||
对齐参考主题 post_ajax() 的 beforeSend:replace 时 $item.remove() +
|
||||
find(pag).remove()。收 display 而非删节点,仍守住「不写 inline display」契约。
|
||||
|
||||
只收 .aksrt-user-home__post-list 的**直接子级** .posts-item:骨架内层同样带
|
||||
.posts-item,写成后代选择器会连骨架自己一起隐藏掉。 */
|
||||
.aksrt-user-home .dashboard-panel.is-loading .aksrt-user-home__post-list > .posts-item {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 旧翻页控件同属被替换的旧页内容,且高亮的是旧页码,加载期一并收起。
|
||||
用 :has() 限定「这个面板真有骨架」:关注 / 评论面板没有 .post_ajax_loader,
|
||||
无条件隐藏会让它们加载时只剩一片空白。 */
|
||||
.aksrt-user-home .dashboard-panel.is-loading:has(.post_ajax_loader) .profile-pagination {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.aksrt-user-home .dashboard-panel.is-loading .post_ajax_loader {
|
||||
display: block;
|
||||
animation: aksrtFadeIn 0.2s ease both;
|
||||
}
|
||||
|
||||
.aksrt-user-home .dashboard-panel.is-load-error .post_ajax_loader {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.aksrt-user-home .dashboard-panel.is-load-error .aksrt-user-home__post-list[data-view="list"] > .post_ajax_loader {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.aksrt-user-home .dashboard-panel.is-load-error .placeholder {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.aksrt-user-home .dashboard-panel .post_ajax_loader .placeholder {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Post list quick-edit dependency toggling.
|
||||
*
|
||||
* The pay quick-edit box hides fields whose dependency rules no longer match
|
||||
* the selected 付费模式 / 支付类型, mirroring the reference theme's field
|
||||
* visibility. Runs on the WP inline-edit row when it is opened or changed.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function readControl(row, fieldId) {
|
||||
var input = row.querySelector(
|
||||
'[name="aksrt_bulk_edit[pay][' + fieldId + ']"],' +
|
||||
' [name="aksrt_bulk_edit[pay][' + fieldId + '][operation]"]'
|
||||
);
|
||||
if (!input) return null;
|
||||
if (input.tagName === 'SELECT') return input.value;
|
||||
var checked = row.querySelector(
|
||||
'[name="aksrt_bulk_edit[pay][' + fieldId + ']"]:checked'
|
||||
);
|
||||
if (input.type === 'radio' && checked) return checked.value;
|
||||
return input.value;
|
||||
}
|
||||
|
||||
function compare(actual, operator, expected) {
|
||||
if (actual === null) return true;
|
||||
if (operator === '==') return actual === expected;
|
||||
if (operator === '!=') return actual !== expected;
|
||||
return true;
|
||||
}
|
||||
|
||||
function updateRow(row) {
|
||||
var boxes = row.querySelectorAll('.aksrt-bulk-edit-box[data-dependency]');
|
||||
Array.prototype.forEach.call(boxes, function (box) {
|
||||
var rules;
|
||||
try {
|
||||
rules = JSON.parse(box.getAttribute('data-dependency'));
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
if (!Array.isArray(rules)) return;
|
||||
|
||||
var visible = rules.every(function (rule) {
|
||||
return compare(readControl(row, rule[0]), rule[1], String(rule[2]));
|
||||
});
|
||||
box.style.display = visible ? '' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
function syncRows() {
|
||||
var rows = document.querySelectorAll('.inline-edit-row, #bulk-edit-row');
|
||||
Array.prototype.forEach.call(rows, function (row) {
|
||||
if (row.querySelector('[data-dependency]')) updateRow(row);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('change', function (event) {
|
||||
var target = event.target;
|
||||
if (!target || !target.name || target.name.indexOf('aksrt_bulk_edit[pay]') !== 0) return;
|
||||
var row = target.closest('.inline-edit-row, #bulk-edit-row');
|
||||
if (row) updateRow(row);
|
||||
});
|
||||
|
||||
// WP swaps the inline-edit row without a change event.
|
||||
document.addEventListener('click', function (event) {
|
||||
var target = event.target;
|
||||
if (!target || !target.closest) return;
|
||||
if (target.closest('.editinline, .bulk-action')) window.setTimeout(syncRows, 50);
|
||||
});
|
||||
|
||||
syncRows();
|
||||
})();
|
||||
+1
-1
@@ -93,7 +93,7 @@
|
||||
var url = new URL(window.location.href);
|
||||
var action = url.searchParams.get('oauth_action');
|
||||
if (['login', 'bind', 'error'].indexOf(action) === -1) return;
|
||||
var userCenterOwnsBind = action === 'bind' && /\/user(?:\/settings)?\/?$/.test(url.pathname) && document.querySelector('[data-identity-root], .settings-section');
|
||||
var userCenterOwnsBind = action === 'bind' && /\/user(?:\/settings)?\/?$/.test(url.pathname) && document.querySelector('[data-identity-root], [data-aksrt-settings-root]');
|
||||
if (userCenterOwnsBind) return;
|
||||
document.documentElement.dataset.aksrtOauthResultHandled = 'true';
|
||||
|
||||
|
||||
+83
-7
@@ -60,15 +60,44 @@
|
||||
});
|
||||
}
|
||||
|
||||
function loadPanelBody(panel, name, page, sub) {
|
||||
function appendCommentPage(panel, html) {
|
||||
var fragment = document.createElement('div');
|
||||
fragment.innerHTML = html;
|
||||
|
||||
var currentList = panel.querySelector('.profile-comment-list');
|
||||
var incomingList = fragment.querySelector('.profile-comment-list');
|
||||
if (currentList && incomingList) {
|
||||
Array.prototype.slice.call(incomingList.children).forEach(function (item) {
|
||||
currentList.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
var currentMore = panel.querySelector('.profile-comment-more');
|
||||
var incomingMore = fragment.querySelector('.profile-comment-more');
|
||||
if (currentMore) currentMore.remove();
|
||||
if (incomingMore && currentList) currentList.insertAdjacentElement('afterend', incomingMore);
|
||||
}
|
||||
|
||||
function loadPanelBody(panel, name, page, sub, appendComments, trigger) {
|
||||
var cfg = window.aksrtData || {};
|
||||
var restBase = cfg.apiUrl || cfg.restUrl || '';
|
||||
if (!userId || !restBase) return Promise.reject(new Error('rest-unavailable'));
|
||||
if (panel._aksrtUhRequest) return panel._aksrtUhRequest;
|
||||
|
||||
var followSub = sub || panel.dataset.uhSub || 'followers';
|
||||
panel.dataset.uhPage = String(page);
|
||||
panel.dataset.uhSub = followSub;
|
||||
var followSub = sub || 'followers';
|
||||
// uhPage / uhSub 是**粉丝面板**的私有状态(其它面板没有子页签)。
|
||||
// 写满所有面板会互相污染:文章面板上的 uhSub 残留会让粉丝翻页读到
|
||||
// 别人的分组,uhPage 也会让旧页码假活跃。非粉丝面板一次都不写。
|
||||
if (name === 'follow') {
|
||||
panel.dataset.uhPage = String(page);
|
||||
panel.dataset.uhSub = followSub;
|
||||
}
|
||||
if (appendComments && trigger) {
|
||||
trigger.disabled = true;
|
||||
trigger.setAttribute('aria-busy', 'true');
|
||||
trigger.textContent = '加载中…';
|
||||
}
|
||||
panel.classList.remove('is-load-error');
|
||||
panel.classList.add('is-loading');
|
||||
|
||||
var url = restBase.replace(/\/$/, '') + '/user-home/panel/' + userId + '/' + encodeURIComponent(name)
|
||||
@@ -84,10 +113,25 @@
|
||||
if (!response.ok || !payload || !payload.success || !payload.data || typeof payload.data.html !== 'string') {
|
||||
throw new Error('panel-load-failed');
|
||||
}
|
||||
panel.innerHTML = payload.data.html;
|
||||
if (appendComments && name === 'comments') {
|
||||
appendCommentPage(panel, payload.data.html);
|
||||
} else {
|
||||
panel.innerHTML = payload.data.html;
|
||||
}
|
||||
panel.dataset.panelLoaded = 'true';
|
||||
if (window.jQuery) jQuery(document).trigger('post_ajax.ed', [panel]);
|
||||
if (typeof window.auto_fun === 'function') window.auto_fun();
|
||||
});
|
||||
}).catch(function (error) {
|
||||
if (panel.dataset.panelLoaded === 'false') {
|
||||
panel.classList.add('is-load-error');
|
||||
}
|
||||
if (appendComments && trigger && trigger.isConnected) {
|
||||
trigger.disabled = false;
|
||||
trigger.removeAttribute('aria-busy');
|
||||
trigger.textContent = '加载更多';
|
||||
}
|
||||
throw error;
|
||||
}).finally(function () {
|
||||
panel._aksrtUhRequest = null;
|
||||
panel.classList.remove('is-loading');
|
||||
@@ -102,7 +146,12 @@
|
||||
|
||||
tabs.forEach(function (tab) {
|
||||
tab.addEventListener('click', function () {
|
||||
switchTo(tab.getAttribute('data-tab'));
|
||||
var name = tab.getAttribute('data-tab');
|
||||
switchTo(name);
|
||||
var panel = panels[name] && panels[name].panel;
|
||||
if (panel && panel.dataset.panelLoaded === 'false') {
|
||||
loadPanelBody(panel, name, 1, 'followers').catch(notifyLoadError);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -122,6 +171,27 @@
|
||||
return;
|
||||
}
|
||||
|
||||
var retryBtn = target.closest('[data-uh-retry]');
|
||||
if (retryBtn) {
|
||||
event.preventDefault();
|
||||
var retryPanel = retryBtn.closest('.dashboard-panel');
|
||||
if (!retryPanel) return;
|
||||
var retryName = panelNameOf(retryPanel);
|
||||
if (!retryName) return;
|
||||
loadPanelBody(retryPanel, retryName, 1, 'followers').catch(notifyLoadError);
|
||||
return;
|
||||
}
|
||||
|
||||
var commentsNext = target.closest('[data-uh-comments-next]');
|
||||
if (commentsNext) {
|
||||
event.preventDefault();
|
||||
var commentsPanel = commentsNext.closest('.dashboard-panel');
|
||||
if (!commentsPanel) return;
|
||||
var commentsPage = Math.max(2, parseInt(commentsNext.getAttribute('data-uh-comments-next') || '2', 10) || 2);
|
||||
loadPanelBody(commentsPanel, 'comments', commentsPage, 'followers', true, commentsNext).catch(notifyLoadError);
|
||||
return;
|
||||
}
|
||||
|
||||
var pageBtn = target.closest('[data-uh-page]');
|
||||
if (!pageBtn) return;
|
||||
event.preventDefault();
|
||||
@@ -130,7 +200,13 @@
|
||||
var name = panelNameOf(panel);
|
||||
if (!name) return;
|
||||
var page = Math.max(1, parseInt(pageBtn.getAttribute('data-uh-page') || '1', 10) || 1);
|
||||
loadPanelBody(panel, name, page, panel.dataset.uhSub || 'followers').catch(notifyLoadError);
|
||||
// 只认**本面板内**的子页签状态:uhSub 是粉丝面板私有的(panel-follow.php
|
||||
// 的 data-uh-sub),一旦落到别的面板上就会互相串味 —— 先点过粉丝子页签,
|
||||
// panel.dataset.uhSub 会残留在文章面板上,之后粉丝面板的翻页读到的是
|
||||
// 文章面板留下的值。只看当前面板,读不到就回落默认分组。
|
||||
var pageSub = panel.querySelector('[data-uh-sub].is-active');
|
||||
var sub = pageSub && pageSub.getAttribute('data-uh-sub') === 'following' ? 'following' : 'followers';
|
||||
loadPanelBody(panel, name, page, sub).catch(notifyLoadError);
|
||||
});
|
||||
|
||||
switchTo(defaultTab);
|
||||
|
||||
@@ -67,6 +67,18 @@
|
||||
}
|
||||
});
|
||||
|
||||
function formatCount(count) {
|
||||
// 与 PHP aksrt_format_number() 同一套缩写:≥10000 → 1.2W+,≥1000 → 1.5K。
|
||||
var safeCount = Math.max(0, parseInt(count, 10) || 0);
|
||||
if (safeCount >= 10000) {
|
||||
return Math.round((safeCount / 10000) * 10) / 10 + 'W+';
|
||||
}
|
||||
if (safeCount >= 1000) {
|
||||
return Math.round((safeCount / 1000) * 10) / 10 + 'K';
|
||||
}
|
||||
return String(safeCount);
|
||||
}
|
||||
|
||||
function handleLike(btn, commentId) {
|
||||
if (btn.dataset.busy === '1') return;
|
||||
btn.dataset.busy = '1';
|
||||
@@ -81,7 +93,7 @@
|
||||
btn.setAttribute('aria-pressed', active ? 'true' : 'false');
|
||||
btn.classList.toggle('actived', active);
|
||||
var countEl = btn.querySelector('count');
|
||||
if (countEl) countEl.textContent = String(count);
|
||||
if (countEl) countEl.textContent = formatCount(count);
|
||||
})
|
||||
.catch(function () { notyf('网络错误', 'danger'); })
|
||||
.finally(function () { btn.dataset.busy = ''; });
|
||||
|
||||
@@ -48,13 +48,13 @@
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
// 骨架开关统一走 .is-loading(modal-runtime.css),不写 inline display:
|
||||
// 内联样式优先级高于类规则,一旦残留会让后续骨架永远打不开。
|
||||
function showLoader(container) {
|
||||
if (!container) return;
|
||||
var loader = container.querySelector(".post_ajax_loader, .post-ajax-loader, [data-aksrt-ajax-loader]");
|
||||
var loader = container.querySelector(".post_ajax_loader");
|
||||
if (loader) {
|
||||
loader.classList.add("is-loading");
|
||||
loader.hidden = false;
|
||||
loader.style.display = "";
|
||||
return;
|
||||
}
|
||||
container.classList.add("is-ajax-loading");
|
||||
@@ -62,13 +62,10 @@
|
||||
|
||||
function hideLoader(container) {
|
||||
if (!container) return;
|
||||
var loader = container.querySelector(".post_ajax_loader, .post-ajax-loader, [data-aksrt-ajax-loader]");
|
||||
var loader = container.querySelector(".post_ajax_loader");
|
||||
if (loader) {
|
||||
loader.classList.remove("is-loading");
|
||||
if (loader.hasAttribute("data-aksrt-ajax-loader-hide")) {
|
||||
loader.hidden = true;
|
||||
loader.style.display = "none";
|
||||
}
|
||||
return;
|
||||
}
|
||||
container.classList.remove("is-ajax-loading");
|
||||
}
|
||||
@@ -188,8 +185,6 @@
|
||||
Array.prototype.slice.call(container.children).forEach(function (child) {
|
||||
if (
|
||||
child.classList.contains("post_ajax_loader") ||
|
||||
child.classList.contains("post-ajax-loader") ||
|
||||
child.hasAttribute("data-aksrt-ajax-loader") ||
|
||||
child.classList.contains("ajax-pag")
|
||||
) {
|
||||
return;
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
var wrap = scope.querySelector("[data-ajax-wrap]");
|
||||
if (!wrap) return;
|
||||
|
||||
var loading = wrap.querySelector(".archive__ajax-loading");
|
||||
// 归档面与首页共用同一套骨架契约:.post_ajax_loader + .is-loading,
|
||||
// 不再使用独立 spinner 遮罩 / opacity 淡出。
|
||||
var loading = wrap.querySelector(".post_ajax_loader");
|
||||
var REST_BASE = "/wp-json/aksrt/v1/archive/posts";
|
||||
var emptyText = archiveType === "tag" ? "该标签下暂无文章" : "该分类下暂无文章";
|
||||
|
||||
@@ -73,12 +75,10 @@
|
||||
|
||||
function showLoading() {
|
||||
if (loading) loading.classList.add("is-loading");
|
||||
wrap.classList.add("is-loading");
|
||||
}
|
||||
|
||||
function hideLoading() {
|
||||
if (loading) loading.classList.remove("is-loading");
|
||||
wrap.classList.remove("is-loading");
|
||||
}
|
||||
|
||||
function rebuildEmptyState() {
|
||||
@@ -102,9 +102,15 @@
|
||||
.then(function (data) {
|
||||
hideLoading();
|
||||
if (data.html !== undefined) {
|
||||
var children = Array.from(wrap.children);
|
||||
children.forEach(function (child) {
|
||||
if (child !== loading) wrap.removeChild(child);
|
||||
// 只清真实内容;骨架(.post_ajax_loader)与分页控件保留复用 ——
|
||||
// 与参考主题 post_ajax success 只 hide 不删的生命周期一致。
|
||||
var keep = function (node) {
|
||||
return node === loading ||
|
||||
node.classList.contains("post_ajax_loader") ||
|
||||
node.classList.contains("ajax-pag");
|
||||
};
|
||||
Array.from(wrap.children).forEach(function (child) {
|
||||
if (!keep(child)) wrap.removeChild(child);
|
||||
});
|
||||
if (data.html) {
|
||||
var temp = document.createElement("div");
|
||||
|
||||
@@ -149,15 +149,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
function formatStatCount(count) {
|
||||
// 与 PHP aksrt_format_number() 同一套缩写:≥10000 → 1.2W+,≥1000 → 1.5K。
|
||||
var safeCount = Math.max(0, count);
|
||||
if (safeCount >= 10000) {
|
||||
return Math.round((safeCount / 10000) * 10) / 10 + "W+";
|
||||
}
|
||||
if (safeCount >= 1000) {
|
||||
return Math.round((safeCount / 1000) * 10) / 10 + "K";
|
||||
}
|
||||
return String(safeCount);
|
||||
}
|
||||
|
||||
function getBaseStatCount(statName) {
|
||||
var statEl = document.querySelector('[data-stat-count="' + statName + '"][data-base-count]');
|
||||
return statEl ? (parseInt(statEl.dataset.baseCount, 10) || 0) : 0;
|
||||
}
|
||||
|
||||
function updateStatCounts(statName, count) {
|
||||
var safeCount = Math.max(0, count);
|
||||
var label = formatStatCount(count);
|
||||
document.querySelectorAll('[data-stat-count="' + statName + '"]').forEach(function (statEl) {
|
||||
statEl.textContent = String(safeCount);
|
||||
statEl.textContent = label;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -465,7 +465,8 @@
|
||||
if (child !== filter && child !== loader) child.remove();
|
||||
});
|
||||
if (skeleton) {
|
||||
skeleton.style.display = "block";
|
||||
// 骨架开关走 .is-loading 类(modal-runtime.css),克隆时同步清理状态。
|
||||
skeleton.classList.add("is-loading");
|
||||
skeleton.removeAttribute("hidden");
|
||||
list.appendChild(skeleton);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
var triggerText = (window.aksrtData && aksrtData.ajaxTrigger) || "加载更多";
|
||||
var nomoreText = (window.aksrtData && aksrtData.ajaxNomore) || "没有更多了";
|
||||
var loadingText = (window.aksrtData && aksrtData.ajaxLoading) || "正在加载...";
|
||||
var errorText = (window.aksrtData && aksrtData.ajaxLoadError) || "加载失败,请重试";
|
||||
|
||||
containers.forEach(function (container) {
|
||||
@@ -51,18 +50,78 @@
|
||||
target = container.parentElement.querySelector(".post-grid, .comment-list, .commentlist, .content-stream__list");
|
||||
}
|
||||
|
||||
// 文章骨架:与 runtime/main.js 的 .ajax-next 处理器、aksrt_render_placeholder()
|
||||
// 同源同款(1:1 对齐参考主题 zib_get_post_placeholder)。刻意不写 ajax-item:
|
||||
// 否则会被 inject() / find('.ajax-item') 当成真实条目删除或定位。
|
||||
var CARD_ITEM =
|
||||
'<div class="posts-item card"><div class="item-thumbnail">' +
|
||||
'<div class="radius8 item-thumbnail placeholder"></div> </div>' +
|
||||
'<div class="item-body "> <h2 class="item-excerpt placeholder t1 item-heading"></h2>' +
|
||||
' <p class="mt10 placeholder k2"></p><i class="flex jsb"><i class="placeholder s1"></i>' +
|
||||
'<i class="placeholder s1 ml10"></i></i></div></div>';
|
||||
var LIST_ITEM =
|
||||
'<div class="posts-item list flex"><div class="post-graphic">' +
|
||||
'<div class="radius8 item-thumbnail placeholder"></div> </div>' +
|
||||
'<div class="item-body flex xx flex1 jsb"> <p class="placeholder t1"></p>' +
|
||||
' <h4 class="item-excerpt placeholder k1"></h4><p class="placeholder k2"></p>' +
|
||||
'<i><i class="placeholder s1"></i><i class="placeholder s1 ml10"></i></i></div></div>';
|
||||
|
||||
// 视口阈值与参考主题 .ajax-next 处理器一致:宽 + 无侧栏 → 8,宽 + 有侧栏 → 6,
|
||||
// 窄屏 → 4;list 模式宽屏 4、窄屏 3。
|
||||
function skeletonCount(style) {
|
||||
var wide = window.innerWidth > 766;
|
||||
if (style === "card") {
|
||||
if (!wide) return 4;
|
||||
return window.innerWidth > 992 && !document.querySelector(".sidebar") ? 8 : 6;
|
||||
}
|
||||
return wide ? 4 : 3;
|
||||
}
|
||||
|
||||
// 从列表首张真实卡片判断形状;无卡片时退化为 list。
|
||||
function postSkeleton() {
|
||||
var first = target && target.querySelector
|
||||
? target.querySelector(".posts-item.card, .posts-item.list")
|
||||
: null;
|
||||
var isCard = !!first && /(^|\s)card(\s|$)/.test(first.className || "");
|
||||
var tpl = isCard ? CARD_ITEM : LIST_ITEM;
|
||||
var out = "";
|
||||
for (var i = 0, n = skeletonCount(isCard ? "card" : "list"); i < n; i++) {
|
||||
out += tpl;
|
||||
}
|
||||
return '<div class="mb20">' + out + '</div>';
|
||||
}
|
||||
|
||||
// 兜底(非文章列表面,例如评论区没有 .posts-item 时):转圈 + 文案,
|
||||
// 绝不用按钮自身文字来表达加载态。
|
||||
function fallbackLoader() {
|
||||
var text = (window.aksrtData && aksrtData.ajaxLoading) || "加载中...";
|
||||
return (
|
||||
'<div class="mb20"><div class="text-center muted-2-color mt20">' +
|
||||
'<i class="loading mr10"></i>' + text +
|
||||
'</div></div>'
|
||||
);
|
||||
}
|
||||
|
||||
function showSkeleton() {
|
||||
if (!target) return;
|
||||
// PHP 产出下划线类名 post_ajax_loader;这里同时兼容连字符写法。
|
||||
var loader = target.querySelector(".post_ajax_loader, .post-ajax-loader");
|
||||
if (loader) {
|
||||
loader.classList.add("is-loading");
|
||||
var loader = target.querySelector(".post_ajax_loader");
|
||||
if (!loader) {
|
||||
loader = document.createElement("div");
|
||||
loader.className = "post_ajax_loader";
|
||||
loader.innerHTML = postSkeleton() || fallbackLoader();
|
||||
}
|
||||
// 参考主题 beforeSend:骨架每次翻页都挪到列表末尾(分页控件之前),
|
||||
// 紧贴最后一张卡片、位于「加载更多」按钮上方。
|
||||
target.insertBefore(
|
||||
loader,
|
||||
container.parentNode === target ? container : null
|
||||
);
|
||||
loader.classList.add("is-loading");
|
||||
}
|
||||
|
||||
function hideSkeleton() {
|
||||
if (!target) return;
|
||||
var loader = target.querySelector(".post_ajax_loader, .post-ajax-loader");
|
||||
var loader = target.querySelector(".post_ajax_loader");
|
||||
if (loader) {
|
||||
loader.classList.remove("is-loading");
|
||||
}
|
||||
@@ -72,13 +131,11 @@
|
||||
container.classList.remove("ias-pending");
|
||||
if (markNomore && loadMoreBtn) {
|
||||
loadMoreBtn.innerHTML = nomoreText;
|
||||
loadMoreBtn.classList.remove("is-loading");
|
||||
loadMoreBtn.classList.add("ajax-nomore");
|
||||
var nextWrap = loadMoreBtn.closest(".next-page");
|
||||
if (nextWrap) nextWrap.classList.add("no-more");
|
||||
loadMoreBtn.style.pointerEvents = "none";
|
||||
} else if (loadMoreBtn && !loadMoreBtn.classList.contains("ajax-nomore")) {
|
||||
loadMoreBtn.classList.remove("is-loading");
|
||||
if (failed) {
|
||||
loadMoreBtn.textContent = errorText;
|
||||
} else {
|
||||
@@ -93,7 +150,6 @@
|
||||
stopInfinite(false);
|
||||
if (loadMoreBtn) {
|
||||
loadMoreBtn.textContent = message || errorText;
|
||||
loadMoreBtn.classList.remove("is-loading");
|
||||
loadMoreBtn.classList.remove("ajax-nomore");
|
||||
loadMoreBtn.style.pointerEvents = "";
|
||||
}
|
||||
@@ -107,12 +163,8 @@
|
||||
}
|
||||
|
||||
loading = true;
|
||||
// 骨架开关统一走 .is-loading(modal-runtime.css),不要直接写
|
||||
// display:flex —— 那会让卡片型骨架挤成一行而不是纵向堆叠。
|
||||
if (loadMoreBtn) {
|
||||
loadMoreBtn.classList.add("is-loading");
|
||||
loadMoreBtn.textContent = loadingText;
|
||||
}
|
||||
// 加载态的呈现完全交给列表末尾的骨架(参考主题 post_ajax beforeSend
|
||||
// 只操作 .post_ajax_loader,不触碰「加载更多」按钮自身的文字)。
|
||||
showSkeleton();
|
||||
|
||||
var action = isComment ? "aksrt_load_more_comments" : "aksrt_load_more_posts";
|
||||
@@ -199,7 +251,6 @@
|
||||
loading = false;
|
||||
hideSkeleton();
|
||||
if (loadMoreBtn && !loadMoreBtn.classList.contains("ajax-nomore") && !failed) {
|
||||
loadMoreBtn.classList.remove("is-loading");
|
||||
loadMoreBtn.innerHTML = triggerText;
|
||||
if (mode === "infinite") {
|
||||
var lazyTrigger = clickTarget.closest(".next-page") || clickTarget;
|
||||
@@ -221,6 +272,11 @@
|
||||
|
||||
if (loadMoreBtn) {
|
||||
clickTarget.addEventListener("click", function(e) {
|
||||
// 排序菜单(.ajax-option)与 tab pane 触发器(.post_ajax_trigger)归 legacy
|
||||
// runtime post_ajax() 管线;本引擎避开,否则一次点击发两个请求。
|
||||
if (e.target.closest && e.target.closest(".ajax-option, .post_ajax_trigger")) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
if (loadMoreBtn.dataset.jumpComments === "1") {
|
||||
var jumpInput = container.querySelector(".jump-input");
|
||||
|
||||
+97
-17
@@ -38,6 +38,14 @@
|
||||
return (BigInt(match[1]) * 100n) + BigInt((match[2] || '').padEnd(2, '0') || '0');
|
||||
}
|
||||
|
||||
function formatCutCount(value) {
|
||||
var number = Number(value);
|
||||
if (!Number.isFinite(number)) return String(value);
|
||||
if (number > 999999) return String(Math.round(number / 10000)) + 'W+';
|
||||
if (number > 9999) return String(Math.round(number / 1000) / 10) + 'W+';
|
||||
return String(number);
|
||||
}
|
||||
|
||||
function api(path, options) {
|
||||
return window.AksrtRequest.request(path, options || {}).then(function (response) {
|
||||
return {
|
||||
@@ -64,16 +72,18 @@
|
||||
var walletNumber = Number(wallet);
|
||||
var walletText = Number.isFinite(walletNumber) ? walletNumber.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : String(wallet);
|
||||
document.querySelectorAll('[data-aksrt-wallet-value]').forEach(function (element) {
|
||||
element.textContent = walletText;
|
||||
element.title = walletText;
|
||||
var text = element.dataset.aksrtCountFormat === 'cut-count' ? formatCutCount(wallet) : walletText;
|
||||
element.textContent = text;
|
||||
element.title = text;
|
||||
});
|
||||
}
|
||||
if (points !== undefined && points !== null && points !== '') {
|
||||
var pointsNumber = Number(points);
|
||||
var pointsText = Number.isFinite(pointsNumber) ? pointsNumber.toLocaleString() : String(points);
|
||||
document.querySelectorAll('[data-aksrt-points-value]').forEach(function (element) {
|
||||
element.textContent = pointsText;
|
||||
element.title = pointsText;
|
||||
var text = element.dataset.aksrtCountFormat === 'cut-count' ? formatCutCount(points) : pointsText;
|
||||
element.textContent = text;
|
||||
element.title = text;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -140,19 +150,62 @@
|
||||
});
|
||||
}
|
||||
|
||||
// ===== Referral poster (canvas preview modal: hero image + copy + QR + link) =====
|
||||
// ===== Referral poster =====
|
||||
// 弹窗形态 1:1 复刻参考主题 poster-share.js:Bootstrap 语义(.in 显隐 +
|
||||
// .modal-backdrop + body.modal-open + ESC),与下方充值弹窗同构,不复依赖
|
||||
// jQuery/Bootstrap 运行时。壳上带 .flex.jc,故打开态用 flex 而非 block。
|
||||
function initAffiliatePoster(root, url, poster, siteName) {
|
||||
var button = root.querySelector('[data-affiliate-poster]');
|
||||
var modal = document.querySelector('[data-aksrt-affiliate-poster-modal]');
|
||||
var stage = document.querySelector('[data-affiliate-poster-stage]');
|
||||
var download = document.querySelector('[data-affiliate-poster-download]');
|
||||
var stage = modal ? modal.querySelector('[data-affiliate-poster-stage]') : null;
|
||||
var loading = modal ? modal.querySelector('[data-affiliate-poster-loading]') : null;
|
||||
var content = modal ? modal.querySelector('.modal-content') : null;
|
||||
var download = modal ? modal.querySelector('[data-affiliate-poster-download]') : null;
|
||||
if (!button || !modal) return;
|
||||
if (!url || !poster || poster.enabled !== true) return;
|
||||
button.hidden = false;
|
||||
[].forEach.call(modal.querySelectorAll('[data-affiliate-poster-close]'), function (closer) {
|
||||
closer.addEventListener('click', function () { closeModal(modal); });
|
||||
});
|
||||
|
||||
var backdrop = null;
|
||||
var canvas = null;
|
||||
|
||||
function openPosterModal() {
|
||||
if (modal.classList.contains('in')) return;
|
||||
modal.style.display = 'flex';
|
||||
void modal.offsetWidth; // 强制 reflow,让 .fade 过渡从 scale(0.9) 起手
|
||||
modal.classList.add('in');
|
||||
modal.setAttribute('aria-hidden', 'false');
|
||||
if (!backdrop) {
|
||||
backdrop = document.createElement('div');
|
||||
backdrop.className = 'modal-backdrop fade in';
|
||||
backdrop.setAttribute('data-affiliate-poster-backdrop', '1');
|
||||
document.body.appendChild(backdrop);
|
||||
}
|
||||
document.body.classList.add('modal-open');
|
||||
}
|
||||
|
||||
function closePosterModal() {
|
||||
if (!modal.classList.contains('in')) return;
|
||||
modal.classList.remove('in');
|
||||
modal.setAttribute('aria-hidden', 'true');
|
||||
document.body.classList.remove('modal-open');
|
||||
if (backdrop && backdrop.parentNode) backdrop.parentNode.removeChild(backdrop);
|
||||
backdrop = null;
|
||||
// 等过渡结束再撤 display,复刻 Bootstrap 的 fade-out 收尾
|
||||
window.setTimeout(function () {
|
||||
if (!modal.classList.contains('in')) modal.style.display = '';
|
||||
}, 300);
|
||||
}
|
||||
|
||||
modal.addEventListener('click', function (event) {
|
||||
// 点遮罩层(.modal 自身)或关闭按钮(data-dismiss="modal")
|
||||
if (event.target === modal || (event.target.closest && event.target.closest('[data-dismiss="modal"]'))) {
|
||||
closePosterModal();
|
||||
}
|
||||
});
|
||||
document.addEventListener('keydown', function (event) {
|
||||
if (event.key === 'Escape' && modal.classList.contains('in')) closePosterModal();
|
||||
});
|
||||
|
||||
if (download) {
|
||||
download.addEventListener('click', function () {
|
||||
if (!canvas) return;
|
||||
@@ -168,20 +221,47 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
button.addEventListener('click', function () {
|
||||
if (typeof window.qrcode !== 'function') {
|
||||
if (stage) { stage.innerHTML = '<p class="muted-2-color em09">' + '二维码组件未加载。' + '</p>'; }
|
||||
openModal(modal);
|
||||
openPosterModal();
|
||||
return;
|
||||
}
|
||||
if (stage) { stage.innerHTML = '<p class="muted-2-color em09">' + '正在生成海报…' + '</p>'; }
|
||||
if (download) download.hidden = true;
|
||||
openModal(modal);
|
||||
if (stage) { stage.innerHTML = ''; stage.classList.remove('loaded'); }
|
||||
if (loading) loading.style.display = '';
|
||||
if (download) download.classList.add('hide');
|
||||
openPosterModal();
|
||||
button.disabled = true;
|
||||
buildPosterCanvas(url, poster, siteName).then(function (built) {
|
||||
canvas = built;
|
||||
if (stage) { stage.innerHTML = ''; stage.appendChild(built); }
|
||||
if (download) download.hidden = false;
|
||||
if (!stage) return;
|
||||
// 参考主题:img.loaded-img 直接进 .poster-imgbox,遮罩淡出,
|
||||
// 内容块高度从 220px 过渡到海报实际高度后清掉内联样式。
|
||||
var img = document.createElement('img');
|
||||
img.src = canvas.toDataURL('image/png');
|
||||
img.alt = '推广海报';
|
||||
img.className = 'loaded-img';
|
||||
// 参考主题按 0.7 比例预设尺寸;.poster-imgbox > img 的 max-width:360px
|
||||
// 会在窄屏把它压回弹窗宽度,height:100% 让 offsetHeight 反映真实渲染高度。
|
||||
img.width = 768 * 0.7;
|
||||
img.height = 1168 * 0.7;
|
||||
stage.appendChild(img);
|
||||
stage.classList.add('loaded');
|
||||
if (download) {
|
||||
download.href = img.src;
|
||||
download.classList.remove('hide');
|
||||
}
|
||||
if (loading) loading.style.display = 'none';
|
||||
if (content) {
|
||||
content.style.height = stage.offsetHeight + 'px';
|
||||
content.style.transition = '.2s';
|
||||
window.setTimeout(function () {
|
||||
content.style.height = '';
|
||||
content.style.overflow = '';
|
||||
content.style.transition = '';
|
||||
}, 200);
|
||||
}
|
||||
}).catch(function () {
|
||||
if (stage) { stage.innerHTML = '<p class="muted-2-color em09">' + '海报生成失败,请重试。' + '</p>'; }
|
||||
}).finally(function () {
|
||||
@@ -1193,7 +1273,7 @@
|
||||
var number = document.createElement('span');
|
||||
number.className = 'uc-withdraw-history__number muted-2-color em09';
|
||||
var numberLabel = document.createElement('span');
|
||||
numberLabel.textContent = '流水号:';
|
||||
numberLabel.textContent = '订单号:';
|
||||
number.appendChild(numberLabel);
|
||||
var code = document.createElement('code');
|
||||
code.textContent = text(item && item.withdrawal_number);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* AKSRT Frontend Page Admin Set — 前台编辑齿轮的提示横条
|
||||
*
|
||||
* 齿轮按钮左侧的「在此编辑此页面参数」横条只是首次进入的引导提示,
|
||||
* 2 秒后淡出 1 秒消失,避免常驻遮挡悬浮按钮栏(对齐参考主题 js/page-edit.js)。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function fadeHint() {
|
||||
var hint = document.querySelector('.admin-set-page .admin-set-info');
|
||||
if (!hint) return;
|
||||
|
||||
setTimeout(function () {
|
||||
hint.style.transition = 'opacity 1s';
|
||||
hint.style.opacity = '0';
|
||||
setTimeout(function () {
|
||||
hint.style.display = 'none';
|
||||
}, 1000);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', fadeHint);
|
||||
} else {
|
||||
fadeHint();
|
||||
}
|
||||
})();
|
||||
@@ -121,27 +121,100 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
var SPLIT_CHUNK_BYTES = 2 * 1048576;
|
||||
|
||||
function splitUploadConfig() {
|
||||
var split = cfg.splitUpload || {};
|
||||
var minSize = parseInt(split.minSize, 10);
|
||||
if (!isFinite(minSize) || minSize < 4) minSize = 4;
|
||||
return { enabled: !!split.enabled, minSize: minSize };
|
||||
}
|
||||
|
||||
function requestJson(url, options) {
|
||||
var init = options || {};
|
||||
init.credentials = 'same-origin';
|
||||
init.headers = { 'X-WP-Nonce': cfg.restNonce || '' };
|
||||
return fetch(url, init).then(function (response) {
|
||||
return response.json().catch(function () { return {}; }).then(function (payload) {
|
||||
if (!response.ok) {
|
||||
throw new Error((payload && (payload.message || (payload.data && payload.data.message))) || i18n.error || '文件上传失败');
|
||||
}
|
||||
return payload;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 分片上传:状态(断点续传)→ 逐片上传 → 合并走标准落库管线,响应与整传一致。
|
||||
function uploadInChunks(file, type) {
|
||||
var uploadKey = type === 'file' ? 'resource' : type;
|
||||
var base = String(cfg.restUrl || '').replace(/\/$/, '');
|
||||
if (!base) return Promise.reject(new Error(i18n.uploadUnavailable || '当前账号不能上传此类文件'));
|
||||
var postId = currentPostId();
|
||||
|
||||
function chunkUrl(action) {
|
||||
var target = base + '/front-submit/' + action + '/' + uploadKey;
|
||||
if (postId) target += (target.indexOf('?') === -1 ? '?' : '&') + 'post_id=' + encodeURIComponent(String(postId));
|
||||
return target;
|
||||
}
|
||||
|
||||
var total = Math.ceil(file.size / SPLIT_CHUNK_BYTES);
|
||||
return requestJson(chunkUrl('upload-status')).catch(function () { return { uploaded: [] }; }).then(function (status) {
|
||||
var uploaded = {};
|
||||
(Array.isArray(status.uploaded) ? status.uploaded : []).forEach(function (index) { uploaded[index] = true; });
|
||||
var chain = Promise.resolve();
|
||||
for (var index = 0; index < total; index++) {
|
||||
(function (i) {
|
||||
chain = chain.then(function () {
|
||||
if (uploaded[i]) return;
|
||||
var body = new FormData();
|
||||
body.append('file', file.slice(i * SPLIT_CHUNK_BYTES, Math.min(file.size, (i + 1) * SPLIT_CHUNK_BYTES)));
|
||||
body.append('chunk', String(i));
|
||||
body.append('chunks', String(total));
|
||||
body.append('name', file.name);
|
||||
body.append('size', String(file.size));
|
||||
return requestJson(chunkUrl('upload-chunk'), { method: 'POST', body: body });
|
||||
});
|
||||
})(index);
|
||||
}
|
||||
return chain;
|
||||
}).then(function () {
|
||||
var body = new FormData();
|
||||
body.append('chunks', String(total));
|
||||
body.append('name', file.name);
|
||||
body.append('size', String(file.size));
|
||||
return requestJson(chunkUrl('upload-merge'), { method: 'POST', body: body });
|
||||
}).then(function (payload) {
|
||||
if (!payload || !payload.success || !payload.url) {
|
||||
throw new Error((payload && (payload.message || (payload.data && payload.data.message))) || i18n.error || '文件上传失败');
|
||||
}
|
||||
return payload;
|
||||
});
|
||||
}
|
||||
|
||||
function uploadMedia(file, type) {
|
||||
var uploadKey = type === 'file' ? 'resource' : type;
|
||||
var upload = (cfg.uploads || {})[uploadKey] || null;
|
||||
if (!upload || !upload.url) return Promise.reject(new Error(i18n.uploadUnavailable || '当前账号不能上传此类文件'));
|
||||
if (!acceptsFile(file, type)) return Promise.reject(new Error(i18n.invalidFile || '请选择有效文件'));
|
||||
if (upload.maxSize && file.size > upload.maxSize) return Promise.reject(new Error(i18n.fileTooLarge || '文件大小超过限制'));
|
||||
var split = splitUploadConfig();
|
||||
if (split.enabled && typeof file.slice === 'function' && file.size > split.minSize * 1048576) {
|
||||
if (Math.ceil(file.size / SPLIT_CHUNK_BYTES) > 1) {
|
||||
return uploadInChunks(file, type);
|
||||
}
|
||||
}
|
||||
var body = new FormData();
|
||||
body.append('file', file);
|
||||
var uploadUrl = String(upload.url);
|
||||
if (currentPostId()) {
|
||||
uploadUrl += (uploadUrl.indexOf('?') === -1 ? '?' : '&') + 'post_id=' + encodeURIComponent(String(currentPostId()));
|
||||
}
|
||||
return fetch(uploadUrl, { method: 'POST', credentials: 'same-origin', headers: { 'X-WP-Nonce': cfg.restNonce || '' }, body: body })
|
||||
.then(function (response) {
|
||||
return response.json().catch(function () { return {}; }).then(function (payload) {
|
||||
if (!response.ok || !payload || !payload.success || !payload.url) {
|
||||
throw new Error((payload && (payload.message || (payload.data && payload.data.message))) || i18n.error || '文件上传失败');
|
||||
}
|
||||
return payload;
|
||||
});
|
||||
});
|
||||
return requestJson(uploadUrl, { method: 'POST', body: body }).then(function (payload) {
|
||||
if (!payload || !payload.success || !payload.url) {
|
||||
throw new Error((payload && (payload.message || (payload.data && payload.data.message))) || i18n.error || '文件上传失败');
|
||||
}
|
||||
return payload;
|
||||
});
|
||||
}
|
||||
|
||||
function imageBatchLimit() {
|
||||
@@ -578,6 +651,120 @@
|
||||
});
|
||||
}
|
||||
|
||||
function toggleBlockFormat(editor, format) {
|
||||
editor.formatter.toggle(format);
|
||||
editor.nodeChanged();
|
||||
}
|
||||
|
||||
function removeEditorBlock(editor, node) {
|
||||
if (!node || node === editor.getBody()) return;
|
||||
editor.dom.remove(node);
|
||||
editor.nodeChanged();
|
||||
}
|
||||
|
||||
function replaceButtonIconWithSvg(button, pathData) {
|
||||
var node = button && button.$el && button.$el[0];
|
||||
var icon = node ? node.querySelector('.mce-i-c-svg') : null;
|
||||
if (!icon) return;
|
||||
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('viewBox', '0 0 1024 1024');
|
||||
svg.setAttribute('style', 'width:21px;height:21px;fill:currentColor;');
|
||||
var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
path.setAttribute('d', pathData);
|
||||
svg.appendChild(path);
|
||||
icon.parentNode.replaceChild(svg, icon);
|
||||
}
|
||||
|
||||
// 经典编辑器增强浮动工具栏(后台「扩展&增强 → 系统工具」mce_float_toolbar):
|
||||
// 复刻参考主题 editextend.js 的 wptoolbar 上下文工具条。
|
||||
function registerFloatToolbar(editor) {
|
||||
editor.addButton('aksrt_h2', {
|
||||
title: 'Heading 2',
|
||||
icon: 'c-svg heading2',
|
||||
stateSelector: 'h2',
|
||||
onclick: function () { toggleBlockFormat(editor, 'h2'); },
|
||||
onPostRender: function () {
|
||||
replaceButtonIconWithSvg(this, 'M143.616 219.648v228.864h278.016V219.648h89.856V768H421.632v-242.688H143.616V768H53.76V219.648h89.856z m660.48-10.752c52.992 0 96.768 15.36 131.328 46.08 33.792 30.72 50.688 69.888 50.688 119.04 0 47.616-18.432 90.624-53.76 129.792-16.554667 17.706667-43.093333 39.082667-78.933333 64.426667l-22.613334 15.701333-12.117333 8.192c-52.309333 34.389333-85.248 64.810667-99.413333 91.178667l-2.730667 5.589333h270.336V768h-382.464c0-56.064 17.664-104.448 54.528-145.92 8.746667-10.069333 21.76-22.186667 38.912-36.352l15.786667-12.586667c5.589333-4.352 11.52-8.874667 17.834666-13.568l19.84-14.506666 21.888-15.488 11.690667-8.106667c35.328-24.576 59.904-45.312 75.264-61.44 23.808-26.88 36.096-56.064 36.096-86.784 0-29.952-8.448-52.224-23.808-66.816-16.128-14.592-39.936-21.504-71.424-21.504-33.792 0-59.136 11.52-76.032 34.56-15.36 19.541333-24.362667 48.64-27.050667 86.058667l-0.597333 11.477333h-89.856c0.768-61.44 18.432-110.592 53.76-148.224 36.096-39.936 83.712-59.904 142.848-59.904z');
|
||||
},
|
||||
});
|
||||
|
||||
editor.addButton('aksrt_h3', {
|
||||
title: 'Heading 3',
|
||||
icon: 'c-svg heading3',
|
||||
stateSelector: 'h3',
|
||||
onclick: function () { toggleBlockFormat(editor, 'h3'); },
|
||||
onPostRender: function () {
|
||||
replaceButtonIconWithSvg(this, 'M801.024 208.896c55.296 0 100.608 13.056 134.4 39.936 33.024 26.88 49.92 63.744 49.92 111.36 0 59.904-30.72 99.84-91.392 119.808 32.256 9.984 57.6 24.576 74.496 44.544 18.432 20.736 27.648 47.616 27.648 79.872 0 50.688-17.664 92.16-52.992 124.416-36.864 33.024-85.248 49.92-145.152 49.92-56.832 0-102.912-14.592-137.472-43.776-38.4-32.256-59.904-79.872-64.512-141.312h91.392c1.536 35.328 12.288 62.976 33.792 82.176 19.2 17.664 44.544 26.88 76.032 26.88 34.56 0 62.208-9.984 82.176-29.184 17.664-17.664 26.88-39.168 26.88-65.28 0-31.488-9.984-54.528-28.416-69.12-18.432-15.36-45.312-22.272-80.64-22.272h-38.4V449.28h38.4c32.256 0 56.832-6.912 73.728-20.736 16.128-13.824 24.576-34.56 24.576-61.44 0-26.88-7.68-46.848-22.272-60.672-16.128-13.824-39.936-20.736-71.424-20.736-32.256 0-56.832 7.68-74.496 23.808-18.432 16.128-29.184 40.704-32.256 73.728h-88.32c4.608-55.296 24.576-98.304 61.44-129.024 34.56-30.72 79.104-45.312 132.864-45.312z m-657.408 10.752v228.864h278.016V219.648h89.856V768H421.632v-242.688H143.616V768H53.76V219.648h89.856z');
|
||||
},
|
||||
});
|
||||
|
||||
editor.addButton('aksrt_remove', {
|
||||
tooltip: i18n.removeBlock || '删除',
|
||||
icon: 'dashicon dashicons-no-alt',
|
||||
onclick: function () {
|
||||
removeEditorBlock(editor, editor.selection.getNode());
|
||||
},
|
||||
});
|
||||
|
||||
editor.on('wptoolbar', function (event) {
|
||||
if (event.toolbar) return;
|
||||
var probe = document.createElement('div');
|
||||
probe.innerHTML = editor.getContent();
|
||||
if (probe.querySelectorAll('p,h1,h2,h3,h4,h5,h6,ul,ol,div,blockquote,pre,img,iframe,table,hr').length <= 4) return;
|
||||
|
||||
var bars = floatToolbarButtons(editor, event);
|
||||
if (bars.length && editor.wp && typeof editor.wp._createToolbar === 'function') {
|
||||
event.toolbar = editor.wp._createToolbar(bars);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 复刻参考主题 get_all_toolbar:按光标所在块与是否选中文字给出上下文按钮。
|
||||
function floatToolbarButtons(editor, event) {
|
||||
var element = event.element;
|
||||
if (!element) return [];
|
||||
var nodeName = element.nodeName;
|
||||
var innerText = element.innerText ? String(element.innerText).trim() : '';
|
||||
var hasSelection = !!editor.selection.getContent();
|
||||
var parent = element.parentElement;
|
||||
var inRoot = !!(parent && parent.id === 'tinymce');
|
||||
var bars = [];
|
||||
|
||||
if (['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'UL', 'LI', 'STRONG', 'SPAN', 'CODE'].indexOf(nodeName) !== -1) {
|
||||
if (inRoot || (nodeName === 'SPAN' && parent && parent.parentElement && parent.parentElement.id === 'tinymce')) {
|
||||
bars.push('aksrt_h2', 'aksrt_h3');
|
||||
}
|
||||
|
||||
if (hasSelection) {
|
||||
bars = ['H1', 'H2', 'H3', 'H4', 'H5'].indexOf(nodeName) === -1 ? ['bold', 'link', 'wp_code'] : [];
|
||||
bars.push('forecolor');
|
||||
if (nodeName === 'P' && editorCfg.canHide) bars.push('aksrt_hide');
|
||||
bars.push('aksrt_remove');
|
||||
return bars;
|
||||
}
|
||||
|
||||
if (innerText) {
|
||||
bars.push('bullist', 'numlist', 'aligncenter');
|
||||
if (nodeName === 'P' && inRoot) {
|
||||
bars.push('aksrt_quote');
|
||||
if (editorCfg.canHide) bars.push('aksrt_hide');
|
||||
}
|
||||
bars.push('aksrt_remove');
|
||||
} else {
|
||||
bars.push('bullist', 'aksrt_quote');
|
||||
if (editorCfg.canHide) bars.push('aksrt_hide');
|
||||
if (editorCfg.canUploadImage) bars.push('aksrt_image');
|
||||
bars.push('aksrt_video', 'aksrt_precode');
|
||||
}
|
||||
}
|
||||
|
||||
if (!bars.length && (nodeName === 'DIV' || nodeName === 'PRE') && !(element.classList && element.classList.contains('file-download-name'))) {
|
||||
bars.push('aksrt_remove');
|
||||
}
|
||||
|
||||
return bars;
|
||||
}
|
||||
|
||||
function registerEditorTools() {
|
||||
if (!window.tinymce || !window.tinymce.PluginManager || window.aksrtSubmitEditorToolsRegistered) {
|
||||
return false;
|
||||
@@ -711,6 +898,10 @@
|
||||
menu: menu,
|
||||
});
|
||||
}
|
||||
|
||||
if (editorCfg.floatToolbar) {
|
||||
registerFloatToolbar(editor);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -103,7 +103,6 @@
|
||||
this.bindFeatured();
|
||||
this.bindPay();
|
||||
this.bindLeaveWarning();
|
||||
this.bindAi();
|
||||
this.bindTagActive();
|
||||
this.loadFeaturedFromDom();
|
||||
},
|
||||
@@ -635,67 +634,6 @@
|
||||
});
|
||||
},
|
||||
|
||||
bindAi: function () {
|
||||
var ai = cfg.ai || {};
|
||||
if (!ai.available) {
|
||||
return;
|
||||
}
|
||||
qsa('.aksrt-ai-assist-btn').forEach(function (btn) {
|
||||
btn.hidden = false;
|
||||
btn.addEventListener('click', function () {
|
||||
var action = btn.getAttribute('data-ai-action');
|
||||
var content = getEditorContent();
|
||||
if (!content || content.replace(/<[^>]+>/g, '').trim().length < 10) {
|
||||
notyf(i18n.needContent || '请先填写正文内容', 'warning');
|
||||
return;
|
||||
}
|
||||
var toastId = action === 'tags'
|
||||
? 'aksrt-front-submit-ai-tags-toast'
|
||||
: 'aksrt-front-submit-ai-summary-toast';
|
||||
var old = btn.textContent;
|
||||
btn.disabled = true;
|
||||
btn.textContent = i18n.aiGenerating || '...';
|
||||
notyf(i18n.aiGenerating || '正在生成…', 'load', '', toastId);
|
||||
fetch((cfg.restUrl || '').replace(/\/$/, '') + '/ai/' + action, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-WP-Nonce': cfg.restNonce || '',
|
||||
},
|
||||
body: JSON.stringify({ content: content }),
|
||||
})
|
||||
.then(function (r) {
|
||||
return r.json();
|
||||
})
|
||||
.then(function (res) {
|
||||
var text = (res && (res.data || res).text) || (res && res.result) || '';
|
||||
if (!text) {
|
||||
throw new Error(i18n.aiFailed || 'AI failed');
|
||||
}
|
||||
if (action === 'summary') {
|
||||
var ex = qs('[name="excerpt"]');
|
||||
if (ex) {
|
||||
ex.value = text;
|
||||
}
|
||||
} else if (action === 'tags') {
|
||||
var tags = qs('[name="tags"]');
|
||||
if (tags) {
|
||||
tags.value = text;
|
||||
}
|
||||
}
|
||||
notyf(i18n.aiFilled || '已填入,可继续编辑', 'success', 5, toastId);
|
||||
})
|
||||
.catch(function (err) {
|
||||
notyf(err.message || i18n.aiFailed || 'AI 生成失败', 'danger', 5, toastId);
|
||||
})
|
||||
.finally(function () {
|
||||
btn.disabled = false;
|
||||
btn.textContent = old;
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
jQuery(document).off('auto_fun.aksrt-front-submit').on('auto_fun.aksrt-front-submit', function () { App.init(); });
|
||||
|
||||
+25
-1
@@ -70,11 +70,18 @@
|
||||
currentList.querySelectorAll('[data-notification-id]').forEach(function (item) {
|
||||
existingIds.add(item.dataset.notificationId);
|
||||
});
|
||||
// 骨架常驻列表末尾:新条目一律插到它之前,形状与参考主题一致
|
||||
// (post_ajax 的 beforeSend 把 loader 挪到最后一张卡片之后)。
|
||||
var skeleton = currentList.querySelector(':scope > .post_ajax_loader');
|
||||
var firstAdded = null;
|
||||
incomingList.querySelectorAll('.message-item').forEach(function (item) {
|
||||
if (existingIds.has(item.dataset.notificationId)) return;
|
||||
if (!firstAdded) firstAdded = item;
|
||||
currentList.appendChild(item);
|
||||
if (skeleton) {
|
||||
currentList.insertBefore(item, skeleton);
|
||||
} else {
|
||||
currentList.appendChild(item);
|
||||
}
|
||||
});
|
||||
|
||||
var currentPagination = content.querySelector('.messages-pagination');
|
||||
@@ -93,6 +100,9 @@
|
||||
var settings = options || {};
|
||||
root.classList.add('is-loading');
|
||||
content.setAttribute('aria-busy', 'true');
|
||||
// 骨架只在 AJAX 请求期间展开(.is-loading),与文章列表翻页同一契约。
|
||||
var skeleton = content.querySelector('.messages-list > .post_ajax_loader');
|
||||
if (skeleton) skeleton.classList.add('is-loading');
|
||||
|
||||
return post('aksrt_load_message_center', {
|
||||
view: requested.view,
|
||||
@@ -111,7 +121,19 @@
|
||||
if (settings.append && state.paginateType === 'ajax_lists') {
|
||||
firstAdded = appendMessagePage(result.data.content);
|
||||
} else {
|
||||
// 替换时保留骨架容器(参考主题 success 后只 hide() 不删除),
|
||||
// 让后续翻页复用同一节点而不是回到无骨架状态。
|
||||
var keep = content.querySelector('.post_ajax_loader');
|
||||
content.innerHTML = result.data.content;
|
||||
var nextSkeleton = content.querySelector('.post_ajax_loader');
|
||||
if (keep && !nextSkeleton) {
|
||||
var list = content.querySelector('.messages-list');
|
||||
if (list) {
|
||||
list.appendChild(keep);
|
||||
} else {
|
||||
content.appendChild(keep);
|
||||
}
|
||||
}
|
||||
}
|
||||
updateHeaderBadge(result.data.unread);
|
||||
|
||||
@@ -131,6 +153,8 @@
|
||||
}).finally(function () {
|
||||
root.classList.remove('is-loading');
|
||||
content.removeAttribute('aria-busy');
|
||||
var loader = content.querySelector('.messages-list > .post_ajax_loader');
|
||||
if (loader) loader.classList.remove('is-loading');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,700 @@
|
||||
/**
|
||||
* AKSRT After-Sale — 前台售后弹窗(申请 / 超时效提示 / 售后详细)
|
||||
*
|
||||
* 与「禁封/举报」链路同构(report.js / user-ban.js):
|
||||
* - [data-aksrt-after-sale-apply] → 申请售后弹窗(超时效则改为超时效提示弹窗)
|
||||
* - [data-aksrt-after-sale-detail] → 售后详细弹窗(可再次申请 / 不可再申请提示)
|
||||
* - [data-aksrt-after-sale-withdraw] → 撤销售后二次确认弹窗(仅待审核工单),确认后 DELETE 工单
|
||||
* 两个入口的 data-* 属性携带订单与售后状态载荷(同 report.js 的 data-aksrt-report),
|
||||
* 点击即同步弹窗,不做前置请求(避免首次点击无反馈)。
|
||||
* - 提交走 REST /aksrt/v1/orders/{order_number}/after-sale
|
||||
* - 撤销走 DELETE 同端点(仅待审核工单,后端置为已取消)
|
||||
* - 提交与撤销前先过人机验证(after_sale_apply / after_sale_cancel,场景未启用自动放行)
|
||||
* - 举证图片走 /orders/{order_number}/after-sale/upload-image(复用评论图片上传校验)
|
||||
*
|
||||
* 按钮形态由订单视图模型的 after_sale 状态决定(UserOrders::after_sale_state):
|
||||
* 已申请 → 售后详细;未申请且在时效内 → 申请售后;超时效 → 申请售后(点击弹超时效提示)。
|
||||
* 弹窗统一经 AksrtModalRuntime.open 直出 HTML:桌面为主题 modal-mini 几何
|
||||
* (400px)+ 蓝色炫彩标题区(modal-colorful-header colorful-bg jb-blue),
|
||||
* 移动端由 mobileBottom 底部抽屉接管。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var API_ROOT = (function () {
|
||||
var d = window.aksrtData || {};
|
||||
var raw = d.apiUrl || d.restUrl || d.rest_url || '/index.php?rest_route=/aksrt/v1';
|
||||
return String(raw).replace(/\/$/, '');
|
||||
})();
|
||||
var NONCE = (window.aksrtData && window.aksrtData.restNonce) || '';
|
||||
var MAX_IMAGES = 3;
|
||||
|
||||
var STATUS_LABELS = {
|
||||
pending: '待审核',
|
||||
processing: '处理中',
|
||||
approved: '已通过',
|
||||
rejected: '已拒绝',
|
||||
cancelled: '已取消',
|
||||
completed: '已完成',
|
||||
};
|
||||
|
||||
function settings() {
|
||||
var d = window.aksrtData && window.aksrtData.afterSale;
|
||||
return d && typeof d === 'object' ? d : {};
|
||||
}
|
||||
|
||||
function reasonOptions() {
|
||||
var list = settings().reasons;
|
||||
if (!Array.isArray(list)) return [];
|
||||
return list
|
||||
.map(function (item) {
|
||||
return {
|
||||
slug: String((item && (item.slug || item.value)) || ''),
|
||||
name: String((item && (item.name || item.label || item.t)) || ''),
|
||||
};
|
||||
})
|
||||
.filter(function (item) { return item.name !== ''; });
|
||||
}
|
||||
|
||||
function materialOptions() {
|
||||
var list = settings().materials;
|
||||
if (!Array.isArray(list)) return [];
|
||||
return list.map(function (item) { return String(item == null ? '' : item); })
|
||||
.filter(function (text) { return text !== ''; });
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value == null ? '' : value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function colorfulHeader(title, icon) {
|
||||
return '<div class="modal-colorful-header colorful-bg jb-blue">' +
|
||||
'<button type="button" class="close" data-aksrt-modal-close aria-label="关闭"><svg class="ic-close" aria-hidden="true"><use xlink:href="#icon-close"></use></svg></button>' +
|
||||
'<div class="colorful-make"></div>' +
|
||||
'<div class="text-center"><div class="em2x"><i class="fa fas ' + escapeHtml(icon) + '" aria-hidden="true"></i></div>' +
|
||||
'<div class="mt10 em12 padding-w10">' + escapeHtml(title) + '</div></div></div>';
|
||||
}
|
||||
|
||||
function orderAmountText(order) {
|
||||
var raw = String(order && order.amount != null ? order.amount : '0');
|
||||
if (order && order.is_points) {
|
||||
return escapeHtml(String(order.currency_symbol || '积分')) + ' ' + (/^\d+$/.test(raw) ? raw : '0');
|
||||
}
|
||||
return '¥' + raw;
|
||||
}
|
||||
|
||||
function orderSummaryHtml(order) {
|
||||
if (!order) return '';
|
||||
return '<div class="muted-box padding-10 mb20 em09 aksrt-after-sale-summary">' +
|
||||
'<div class="aksrt-after-sale-summary__row"><span class="muted-2-color">商品</span>' +
|
||||
'<span class="aksrt-after-sale-summary__value">' + escapeHtml(order.product_name || '订单') + '</span></div>' +
|
||||
'<div class="aksrt-after-sale-summary__row"><span class="muted-2-color">订单号</span>' +
|
||||
'<span class="aksrt-after-sale-summary__value">' + escapeHtml(order.order_number || '') + '</span></div>' +
|
||||
'<div class="aksrt-after-sale-summary__row"><span class="muted-2-color">实付</span>' +
|
||||
'<span class="aksrt-after-sale-summary__value">' + orderAmountText(order) + '</span></div>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
function buildApplyHtml(order) {
|
||||
var cfg = settings();
|
||||
var reasons = reasonOptions();
|
||||
var materials = materialOptions();
|
||||
|
||||
var html = '<div class="aksrt-after-sale-apply-modal">';
|
||||
html += colorfulHeader('申请售后', 'fa-undo-alt');
|
||||
html += '<form class="aksrt-after-sale-form" data-aksrt-after-sale-order="' + escapeHtml((order && order.order_number) || '') + '" novalidate>';
|
||||
// 滚动容器与禁封申诉弹窗一致:no-scrollbar 完全隐藏滚动条(含细滚动条)+ 50vh 内层滚动。
|
||||
html += '<div class="scroll-y no-scrollbar max-vh5">';
|
||||
html += orderSummaryHtml(order);
|
||||
|
||||
if (reasons.length) {
|
||||
html += '<div class="mb20">';
|
||||
html += '<div class="em09 muted-2-color mb6">售后原因</div>';
|
||||
html += '<div class="form-radio form-but-radio">';
|
||||
reasons.forEach(function (reason) {
|
||||
html += '<span class="mr6 mb6 badg p2-10 pointer" data-aksrt-after-sale-reason="' + escapeHtml(reason.name) + '" data-reason-slug="' + escapeHtml(reason.slug) + '">' + escapeHtml(reason.name) + '</span>';
|
||||
});
|
||||
html += '</div></div>';
|
||||
}
|
||||
|
||||
materials.forEach(function (label) {
|
||||
html += '<div class="mb10"><div class="muted-2-color mb6">' + escapeHtml(label) + '</div>' +
|
||||
'<input class="form-control" type="text" maxlength="120" data-aksrt-after-sale-material="' + escapeHtml(label) + '" placeholder="请输入' + escapeHtml(label) + '"></div>';
|
||||
});
|
||||
|
||||
var placeholder = String(cfg.reasonPlaceholder || '必填,请您详细填写申请原因');
|
||||
html += '<div class="mb10"><div class="muted-2-color mb6">申请说明</div>' +
|
||||
'<textarea class="form-control" name="reason" rows="3" maxlength="2000" required placeholder="' + escapeHtml(placeholder) + '"></textarea></div>';
|
||||
|
||||
if (cfg.imgEnabled) {
|
||||
html += '<div class="form-upload mb10">' +
|
||||
'<div class="em09 muted-2-color mb6">上传图片</div>' +
|
||||
'<div class="preview"><div class="add" data-aksrt-after-sale-add role="button" tabindex="0" aria-label="上传图片"></div></div>' +
|
||||
'<input class="hide" type="file" accept="image/gif,image/jpeg,image/jpg,image/png,image/webp" data-aksrt-after-sale-file>' +
|
||||
'<div class="muted-2-color em09 mt6">最多上传 ' + MAX_IMAGES + ' 张图片</div>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
// 申请售后说明(后台 after_sale_desc,服务端已 wp_kses_post)。
|
||||
var desc = String(cfg.desc || '');
|
||||
if (desc) {
|
||||
html += '<div class="em09 padding-h10 muted-box mb10">' + desc + '</div>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
html += '<div class="but-average modal-buts">' +
|
||||
'<button type="button" class="but" data-aksrt-modal-close>取消</button>' +
|
||||
'<button type="button" class="but c-blue" data-aksrt-after-sale-submit><i class="fa fas fa-check" aria-hidden="true"></i>确认提交</button>' +
|
||||
'</div>';
|
||||
html += '</form></div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
function detailRow(label, value) {
|
||||
if (value === '' || value == null) return '';
|
||||
return '<div class="aksrt-after-sale-detail__row"><span class="muted-2-color">' + escapeHtml(label) + '</span>' +
|
||||
'<span class="aksrt-after-sale-detail__value">' + escapeHtml(value) + '</span></div>';
|
||||
}
|
||||
|
||||
// 处理记录节点标题:用户侧提交/撤脱离不开工单状态,单独措辞;
|
||||
// 后台提交直接用目标状态,与后台列表口径一致。
|
||||
function handlingEntryTitle(entry) {
|
||||
var status = String((entry && entry.status) || '');
|
||||
if (String((entry && entry.source) || '') === 'user') {
|
||||
return status === 'cancelled' ? '用户撤销售后' : '用户提交申请';
|
||||
}
|
||||
return STATUS_LABELS[status] || status;
|
||||
}
|
||||
|
||||
// 处理记录时间线:左列绳结状态条(节点 + 状态 + 时间),右列处理备注。
|
||||
// 数据源为 latest.handling_log(工单行内 JSON,按时间正序)。
|
||||
function handlingLogHtml(log) {
|
||||
var entries = Array.isArray(log) ? log : [];
|
||||
if (!entries.length) {
|
||||
return '<div class="aksrt-after-sale-log"><div class="aksrt-after-sale-log__empty">暂无处理记录</div></div>';
|
||||
}
|
||||
var html = '<div class="aksrt-after-sale-log"><div class="muted-2-color em09 mb6">处理记录</div>';
|
||||
html += '<ol class="aksrt-after-sale-timeline">';
|
||||
entries.forEach(function (entry) {
|
||||
var status = String((entry && entry.status) || '');
|
||||
var note = String((entry && entry.note) || '');
|
||||
html += '<li class="aksrt-after-sale-timeline__item is-' + escapeHtml(status) + '">';
|
||||
html += '<span class="aksrt-after-sale-timeline__knot"></span>';
|
||||
html += '<div class="aksrt-after-sale-timeline__body">';
|
||||
// [节点][处理状态][时间] 同一行,处理备注另起一行
|
||||
html += '<div class="aksrt-after-sale-timeline__head">' +
|
||||
'<span class="aksrt-after-sale-timeline__status">' + escapeHtml(handlingEntryTitle(entry)) + '</span>' +
|
||||
'<time class="aksrt-after-sale-timeline__time">' + escapeHtml(String((entry && entry.created_at) || '')) + '</time>' +
|
||||
'</div>';
|
||||
if (note !== '') {
|
||||
html += '<p class="aksrt-after-sale-timeline__note">' + escapeHtml(note).replace(/\r\n|\r|\n/g, '<br>') + '</p>';
|
||||
}
|
||||
html += '</div>';
|
||||
html += '</li>';
|
||||
});
|
||||
html += '</ol></div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
function buildDetailHtml(order) {
|
||||
var afterSale = (order && order.after_sale) || {};
|
||||
var latest = afterSale.latest || {};
|
||||
var status = String(latest.status || '');
|
||||
|
||||
var html = '<div class="aksrt-after-sale-detail-modal">';
|
||||
html += colorfulHeader('售后详细', 'fa-clipboard-list');
|
||||
html += '<div class="scroll-y no-scrollbar max-vh5 aksrt-after-sale-detail__body">';
|
||||
html += orderSummaryHtml(order);
|
||||
|
||||
html += '<div class="aksrt-after-sale-detail__statusRow">' +
|
||||
'<span class="muted-2-color em09">售后状态</span>' +
|
||||
'<span class="aksrt-after-sale-detail__statusCell">' +
|
||||
'<span class="badg p2-10 aksrt-after-sale-status is-' + escapeHtml(status) + '">' + escapeHtml(STATUS_LABELS[status] || status) + '</span>' +
|
||||
// 仅「待审核」可撤销(后端 cancel 仅放行 pending):入口随状态徽章渲染。
|
||||
(status === 'pending'
|
||||
? '<button type="button" class="aksrt-after-sale-withdraw" data-aksrt-after-sale-withdraw="' + escapeHtml((order && order.order_number) || '') + '">撤销售后<i class="fa fas fa-angle-right" aria-hidden="true"></i></button>'
|
||||
: '') +
|
||||
'</span></div>';
|
||||
|
||||
html += '<div class="aksrt-after-sale-detail__rows">';
|
||||
html += detailRow('申请时间', String(latest.created_at || ''));
|
||||
html += '</div>';
|
||||
// 处理记录:每次后台提交一条,替代原先单行的「处理备注 / 完成时间」。
|
||||
html += handlingLogHtml(latest.handling_log);
|
||||
|
||||
// 申请表单分行展示:预置原因 / 申请资料 / 申请说明各自独立成行,
|
||||
// 与上方「申请时间」等明细行同格式;老数据无 apply_form 时兜底展示融合串。
|
||||
var applyForm = latest.apply_form && typeof latest.apply_form === 'object' ? latest.apply_form : null;
|
||||
if (applyForm) {
|
||||
html += detailRow('原因', String(applyForm.reason_label || ''));
|
||||
(Array.isArray(applyForm.materials) ? applyForm.materials : []).forEach(function (material) {
|
||||
html += detailRow(String((material && material.label) || ''), String((material && material.value) || ''));
|
||||
});
|
||||
html += detailRow('申请说明', String(applyForm.description || ''));
|
||||
} else if (latest.reason) {
|
||||
html += '<div class="mb10"><div class="muted-2-color em09 mb6">申请原因</div>' +
|
||||
'<div class="em09 aksrt-after-sale-reason-text">' + escapeHtml(latest.reason).replace(/\r\n|\r|\n/g, '<br>') + '</div></div>';
|
||||
}
|
||||
|
||||
var images = Array.isArray(latest.images) ? latest.images : [];
|
||||
if (images.length) {
|
||||
html += '<div class="mb10"><div class="muted-2-color em09 mb6">举证图片</div><div class="aksrt-after-sale-images">';
|
||||
images.forEach(function (url) {
|
||||
html += '<a href="' + escapeHtml(url) + '" target="_blank" rel="noopener noreferrer"><img src="' + escapeHtml(url) + '" alt=""></a>';
|
||||
});
|
||||
html += '</div></div>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
||||
if (afterSale.can_reapply) {
|
||||
// 可再次申请:仅保留单个主按钮(与禁封申诉弹窗单按钮同式)。
|
||||
html += '<div class="but-average modal-buts">' +
|
||||
'<button type="button" class="but c-blue" data-aksrt-after-sale-reapply data-aksrt-after-sale-payload="' + escapeHtml(JSON.stringify(order)) + '"><i class="fa fas fa-undo-alt" aria-hidden="true"></i>再次申请售后</button>' +
|
||||
'</div>';
|
||||
} else {
|
||||
// 不可再申请售后:不再展示阻断文案,仅保留关闭入口。
|
||||
html += '<div class="but-average modal-buts"><button type="button" class="but c-blue" data-aksrt-modal-close>我知道了</button></div>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
function buildExpiredHtml() {
|
||||
var text = String(settings().expiredText || '抱歉,您的订单已超过售后申请时效');
|
||||
var html = '<div class="aksrt-after-sale-expired-modal">';
|
||||
html += colorfulHeader('售后提示', 'fa-clock');
|
||||
// 超过时效提示(后台 after_sale_expired_text,服务端已 wp_kses_post,支持 HTML)。
|
||||
html += '<div class="aksrt-after-sale-expired__body em09">' + text + '</div>';
|
||||
html += '<div class="but-average modal-buts"><button type="button" class="but c-blue" data-aksrt-modal-close>我知道了</button></div>';
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
// 撤销售后二次确认弹窗:待审核工单入口点入,确认后 DELETE 售后工单。
|
||||
function buildWithdrawHtml(orderNumber) {
|
||||
var html = '<div class="aksrt-after-sale-withdraw-modal">';
|
||||
html += colorfulHeader('撤销售后', 'fa-undo-alt');
|
||||
html += '<div class="aksrt-after-sale-withdraw__body em09">撤销后本次售后申请将关闭,订单恢复为可再次申请售后的状态。确定撤销吗?</div>';
|
||||
html += '<div class="but-average modal-buts">' +
|
||||
'<button type="button" class="but" data-aksrt-modal-close>取消</button>' +
|
||||
'<button type="button" class="but c-blue" data-aksrt-after-sale-withdraw-confirm="' + escapeHtml(orderNumber) + '">确认撤销</button>' +
|
||||
'</div>';
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
function openModal(options) {
|
||||
if (!window.AksrtModalRuntime || typeof window.AksrtModalRuntime.open !== 'function') {
|
||||
notyf('售后组件加载失败,请刷新页面后重试', 'danger');
|
||||
return null;
|
||||
}
|
||||
var shell = window.AksrtModalRuntime.open({
|
||||
html: options.html,
|
||||
className: options.className,
|
||||
kind: options.kind,
|
||||
mobileBottom: true,
|
||||
});
|
||||
bindShell(shell);
|
||||
return shell;
|
||||
}
|
||||
|
||||
function openApply(order) {
|
||||
return openModal({
|
||||
html: buildApplyHtml(order),
|
||||
className: 'aksrt-after-sale-apply-modal',
|
||||
kind: 'after-sale-apply',
|
||||
});
|
||||
}
|
||||
|
||||
function openDetail(order) {
|
||||
return openModal({
|
||||
html: buildDetailHtml(order),
|
||||
className: 'aksrt-after-sale-detail-modal',
|
||||
kind: 'after-sale-detail',
|
||||
});
|
||||
}
|
||||
|
||||
function openExpired() {
|
||||
return openModal({
|
||||
html: buildExpiredHtml(),
|
||||
className: 'aksrt-after-sale-expired-modal',
|
||||
kind: 'after-sale-expired',
|
||||
});
|
||||
}
|
||||
|
||||
function openWithdraw(orderNumber) {
|
||||
if (!orderNumber) return;
|
||||
return openModal({
|
||||
html: buildWithdrawHtml(orderNumber),
|
||||
className: 'aksrt-after-sale-withdraw-modal',
|
||||
kind: 'after-sale-withdraw',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析按钮 data-* 上的售后载荷(与举报链路 data-aksrt-report 同模式):
|
||||
* 入口数据随列表一起渲染,点击即可同步弹窗,不做前置请求。
|
||||
*/
|
||||
function parsePayload(raw) {
|
||||
if (!raw) return null;
|
||||
try {
|
||||
var data = JSON.parse(raw);
|
||||
return data && typeof data === 'object' ? data : null;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 申请售后入口:超时效 → 超时效提示;已申请 → 售后详细;否则 → 申请表单。
|
||||
function openApplyFlow(payload) {
|
||||
var afterSale = payload && payload.after_sale ? payload.after_sale : null;
|
||||
if (!payload || !afterSale || !afterSale.enabled) {
|
||||
notyf('售后申请暂不可用', 'danger');
|
||||
return;
|
||||
}
|
||||
if (afterSale.expired) { openExpired(); return; }
|
||||
if (afterSale.has_application) { openDetail(payload); return; }
|
||||
openApply(payload);
|
||||
}
|
||||
|
||||
function openDetailFlow(payload) {
|
||||
var afterSale = payload && payload.after_sale ? payload.after_sale : null;
|
||||
if (!payload || !afterSale || !afterSale.enabled || !afterSale.has_application) {
|
||||
notyf('暂无售后记录', 'danger');
|
||||
return;
|
||||
}
|
||||
openDetail(payload);
|
||||
}
|
||||
|
||||
function collectImages(shell) {
|
||||
return Array.prototype.map.call(
|
||||
shell.querySelectorAll('.preview .preview-item img'),
|
||||
function (img) { return img.getAttribute('src') || ''; }
|
||||
).filter(function (url) { return url !== ''; });
|
||||
}
|
||||
|
||||
function uploadImage(shell, file) {
|
||||
var form = shell.querySelector('[data-aksrt-after-sale-order]');
|
||||
var preview = shell.querySelector('.form-upload .preview');
|
||||
var orderNumber = form ? form.getAttribute('data-aksrt-after-sale-order') : '';
|
||||
if (!preview || !orderNumber) return Promise.resolve();
|
||||
if (collectImages(shell).length >= MAX_IMAGES) {
|
||||
notyf('最多上传 ' + MAX_IMAGES + ' 张图片', 'danger');
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
var body = new FormData();
|
||||
body.append('image', file);
|
||||
|
||||
return window.fetch(API_ROOT + '/orders/' + encodeURIComponent(orderNumber) + '/after-sale/upload-image', {
|
||||
method: 'POST',
|
||||
headers: { 'X-WP-Nonce': NONCE },
|
||||
body: body,
|
||||
})
|
||||
.then(function (res) { return res.json(); })
|
||||
.then(function (result) {
|
||||
var url = result && result.success && result.data && result.data.url;
|
||||
if (!url) {
|
||||
notyf((result && result.message) || '图片上传失败', 'danger');
|
||||
return;
|
||||
}
|
||||
if (collectImages(shell).length >= MAX_IMAGES) {
|
||||
notyf('最多上传 ' + MAX_IMAGES + ' 张图片', 'danger');
|
||||
return;
|
||||
}
|
||||
var item = document.createElement('div');
|
||||
item.className = 'preview-item';
|
||||
var img = document.createElement('img');
|
||||
img.src = url;
|
||||
img.alt = '';
|
||||
var remove = document.createElement('button');
|
||||
remove.type = 'button';
|
||||
remove.className = 'remove';
|
||||
remove.setAttribute('data-aksrt-after-sale-image-remove', '');
|
||||
remove.setAttribute('aria-label', '删除图片');
|
||||
remove.innerHTML = '×';
|
||||
remove.addEventListener('click', function () { item.remove(); });
|
||||
item.appendChild(img);
|
||||
item.appendChild(remove);
|
||||
preview.insertBefore(item, preview.querySelector('.add'));
|
||||
})
|
||||
.catch(function () {
|
||||
notyf('图片上传失败,请重试', 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
function selectedReasonLabel(shell) {
|
||||
var active = shell.querySelector('[data-aksrt-after-sale-reason].active');
|
||||
return active ? String(active.getAttribute('data-aksrt-after-sale-reason') || '') : '';
|
||||
}
|
||||
|
||||
function collectMaterials(shell) {
|
||||
var materials = [];
|
||||
Array.prototype.forEach.call(shell.querySelectorAll('[data-aksrt-after-sale-material]'), function (input) {
|
||||
var value = String(input.value || '').trim();
|
||||
if (value === '') return;
|
||||
materials.push({
|
||||
label: String(input.getAttribute('data-aksrt-after-sale-material') || ''),
|
||||
value: value,
|
||||
});
|
||||
});
|
||||
return materials;
|
||||
}
|
||||
|
||||
function composeReason(shell) {
|
||||
var parts = [];
|
||||
var active = shell.querySelector('[data-aksrt-after-sale-reason].active');
|
||||
if (active) {
|
||||
var label = active.getAttribute('data-aksrt-after-sale-reason') || '';
|
||||
if (label) parts.push(label);
|
||||
}
|
||||
var materials = [];
|
||||
Array.prototype.forEach.call(shell.querySelectorAll('[data-aksrt-after-sale-material]'), function (input) {
|
||||
var value = String(input.value || '').trim();
|
||||
if (value === '') return;
|
||||
materials.push(String(input.getAttribute('data-aksrt-after-sale-material') || '') + ':' + value);
|
||||
});
|
||||
if (materials.length) parts.push(materials.join('\n'));
|
||||
var reasonInput = shell.querySelector('[name="reason"]');
|
||||
var reasonText = reasonInput ? String(reasonInput.value || '').trim() : '';
|
||||
if (reasonText) parts.push(reasonText);
|
||||
return parts.join('\n');
|
||||
}
|
||||
|
||||
function clearToast(id) {
|
||||
var toast = document.getElementById(id);
|
||||
if (toast) toast.remove();
|
||||
}
|
||||
|
||||
// 提交前的人机验证:场景未启用时无挂载点,run 直接 resolve {};
|
||||
// 用户中途取消验证时 run resolve null,此时还原按钮并中止提交。
|
||||
function runCaptcha(scene) {
|
||||
if (!window.AKSRTCaptcha || typeof window.AKSRTCaptcha.run !== 'function') {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
return window.AKSRTCaptcha.run(scene).then(function (payload) {
|
||||
return payload === null ? Promise.reject({ cancelled: true }) : (payload || {});
|
||||
});
|
||||
}
|
||||
|
||||
function submitApply(shell) {
|
||||
var form = shell.querySelector('[data-aksrt-after-sale-order]');
|
||||
var orderNumber = form ? form.getAttribute('data-aksrt-after-sale-order') : '';
|
||||
var reasonInput = shell.querySelector('[name="reason"]');
|
||||
var reasonText = reasonInput ? String(reasonInput.value || '').trim() : '';
|
||||
if (!reasonText) {
|
||||
notyf('请填写申请说明', 'danger');
|
||||
if (reasonInput) reasonInput.focus();
|
||||
return;
|
||||
}
|
||||
// 预置原因被后台清空时不强制选择;有配置则必须选中一项(与举报弹窗一致)。
|
||||
if (shell.querySelector('[data-aksrt-after-sale-reason]') && !shell.querySelector('[data-aksrt-after-sale-reason].active')) {
|
||||
notyf('请选择售后原因', 'danger');
|
||||
return;
|
||||
}
|
||||
|
||||
var submitBtn = shell.querySelector('[data-aksrt-after-sale-submit]');
|
||||
if (submitBtn) submitBtn.disabled = true;
|
||||
notyf('正在提交售后申请…', 'load', 3000, 'aksrt-after-sale-submit');
|
||||
|
||||
var restoreButton = function () {
|
||||
if (submitBtn) submitBtn.disabled = false;
|
||||
};
|
||||
var sendApply = function (captchaPayload) {
|
||||
window.fetch(API_ROOT + '/orders/' + encodeURIComponent(orderNumber) + '/after-sale', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-WP-Nonce': NONCE,
|
||||
},
|
||||
body: JSON.stringify(Object.assign({
|
||||
reason: composeReason(shell),
|
||||
reason_label: selectedReasonLabel(shell),
|
||||
materials: collectMaterials(shell),
|
||||
description: reasonText,
|
||||
images: collectImages(shell),
|
||||
}, captchaPayload)),
|
||||
})
|
||||
.then(function (res) { return res.json(); })
|
||||
.then(function (result) {
|
||||
if (result && result.success) {
|
||||
window.AksrtModalRuntime.close('submit');
|
||||
notyf('售后申请已提交', 'success');
|
||||
if (window.AKSRTOrderCenter && typeof window.AKSRTOrderCenter.refresh === 'function') {
|
||||
window.AKSRTOrderCenter.refresh(orderNumber);
|
||||
}
|
||||
} else {
|
||||
restoreButton();
|
||||
notyf((result && result.message) || '售后申请提交失败', 'danger');
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
restoreButton();
|
||||
notyf('网络错误,请重试', 'danger');
|
||||
})
|
||||
.then(function () { clearToast('aksrt-after-sale-submit'); });
|
||||
};
|
||||
|
||||
runCaptcha('after_sale_apply').then(sendApply).catch(function (error) {
|
||||
restoreButton();
|
||||
clearToast('aksrt-after-sale-submit');
|
||||
if (error && error.cancelled) return;
|
||||
notyf('人机验证失败,请重试', 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
// 撤销售后:待审核工单置为已取消(DELETE 售后工单),成功后刷新订单列表与统计。
|
||||
function submitWithdraw(shell, button) {
|
||||
var orderNumber = button.getAttribute('data-aksrt-after-sale-withdraw-confirm') || '';
|
||||
if (!orderNumber || button.disabled) return;
|
||||
button.disabled = true;
|
||||
notyf('正在撤销售后申请…', 'load', 3000, 'aksrt-after-sale-withdraw');
|
||||
|
||||
var restoreButton = function () {
|
||||
button.disabled = false;
|
||||
};
|
||||
var sendWithdraw = function (captchaPayload) {
|
||||
window.fetch(API_ROOT + '/orders/' + encodeURIComponent(orderNumber) + '/after-sale', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-WP-Nonce': NONCE,
|
||||
},
|
||||
body: JSON.stringify(captchaPayload || {}),
|
||||
})
|
||||
.then(function (res) { return res.json(); })
|
||||
.then(function (result) {
|
||||
if (result && result.success) {
|
||||
window.AksrtModalRuntime.close('withdraw');
|
||||
notyf('售后申请已撤销', 'success');
|
||||
if (window.AKSRTOrderCenter && typeof window.AKSRTOrderCenter.refresh === 'function') {
|
||||
window.AKSRTOrderCenter.refresh(orderNumber);
|
||||
}
|
||||
} else {
|
||||
restoreButton();
|
||||
notyf((result && result.message) || '撤销失败,请重试', 'danger');
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
restoreButton();
|
||||
notyf('网络错误,请重试', 'danger');
|
||||
})
|
||||
.then(function () { clearToast('aksrt-after-sale-withdraw'); });
|
||||
};
|
||||
|
||||
runCaptcha('after_sale_cancel').then(sendWithdraw).catch(function (error) {
|
||||
restoreButton();
|
||||
clearToast('aksrt-after-sale-withdraw');
|
||||
if (error && error.cancelled) return;
|
||||
notyf('人机验证失败,请重试', 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
function bindShell(shell) {
|
||||
if (!shell) return;
|
||||
|
||||
shell.addEventListener('click', function (event) {
|
||||
// 原因徽章:单选 active(与举报弹窗同语义,重复点击已选徽章保持选中)。
|
||||
var chip = event.target.closest('[data-aksrt-after-sale-reason]');
|
||||
if (chip) {
|
||||
event.preventDefault();
|
||||
Array.prototype.forEach.call(shell.querySelectorAll('[data-aksrt-after-sale-reason].active'), function (el) {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
chip.classList.add('active');
|
||||
return;
|
||||
}
|
||||
|
||||
var addBtn = event.target.closest('[data-aksrt-after-sale-add]');
|
||||
if (addBtn) {
|
||||
event.preventDefault();
|
||||
var fileInput = shell.querySelector('[data-aksrt-after-sale-file]');
|
||||
if (fileInput) fileInput.click();
|
||||
return;
|
||||
}
|
||||
|
||||
var removeBtn = event.target.closest('[data-aksrt-after-sale-image-remove]');
|
||||
if (removeBtn) {
|
||||
event.preventDefault();
|
||||
var item = removeBtn.closest('.preview-item');
|
||||
if (item) item.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
var submitBtn = event.target.closest('[data-aksrt-after-sale-submit]');
|
||||
if (submitBtn) {
|
||||
event.preventDefault();
|
||||
submitApply(shell);
|
||||
return;
|
||||
}
|
||||
|
||||
// 再次申请售后:载荷已随按钮渲染。can_reapply 必然伴随 has_application=true,
|
||||
// 直接走 openApplyFlow 会被 has_application 分流回「售后详细」打转;
|
||||
// 清除标记落回申请表单分支(expired 超时效拦截仍然生效)。
|
||||
var reapplyBtn = event.target.closest('[data-aksrt-after-sale-reapply]');
|
||||
if (reapplyBtn) {
|
||||
event.preventDefault();
|
||||
var payload = parsePayload(reapplyBtn.getAttribute('data-aksrt-after-sale-payload'));
|
||||
if (payload && payload.after_sale) payload.after_sale.has_application = false;
|
||||
window.AksrtModalRuntime.close('replace');
|
||||
openApplyFlow(payload);
|
||||
return;
|
||||
}
|
||||
|
||||
// 撤销售后:入口在售后详细弹窗状态行(仅待审核),点击先弹二次确认。
|
||||
var withdrawBtn = event.target.closest('[data-aksrt-after-sale-withdraw]');
|
||||
if (withdrawBtn) {
|
||||
event.preventDefault();
|
||||
openWithdraw(withdrawBtn.getAttribute('data-aksrt-after-sale-withdraw') || '');
|
||||
return;
|
||||
}
|
||||
|
||||
var withdrawConfirm = event.target.closest('[data-aksrt-after-sale-withdraw-confirm]');
|
||||
if (withdrawConfirm) {
|
||||
event.preventDefault();
|
||||
submitWithdraw(shell, withdrawConfirm);
|
||||
}
|
||||
});
|
||||
|
||||
shell.addEventListener('change', function (event) {
|
||||
var fileInput = event.target.closest('[data-aksrt-after-sale-file]');
|
||||
if (!fileInput || !fileInput.files || !fileInput.files.length) return;
|
||||
Array.prototype.forEach.call(fileInput.files, function (file) {
|
||||
uploadImage(shell, file);
|
||||
});
|
||||
fileInput.value = '';
|
||||
});
|
||||
}
|
||||
|
||||
function initAfterSaleButtons() {
|
||||
if (document.documentElement.dataset.aksrtAfterSaleInitialized === '1') return;
|
||||
document.documentElement.dataset.aksrtAfterSaleInitialized = '1';
|
||||
document.addEventListener('click', function (event) {
|
||||
var apply = event.target.closest('[data-aksrt-after-sale-apply]');
|
||||
if (apply) {
|
||||
event.preventDefault();
|
||||
openApplyFlow(parsePayload(apply.getAttribute('data-aksrt-after-sale-apply')));
|
||||
return;
|
||||
}
|
||||
var detail = event.target.closest('[data-aksrt-after-sale-detail]');
|
||||
if (detail) {
|
||||
event.preventDefault();
|
||||
openDetailFlow(parsePayload(detail.getAttribute('data-aksrt-after-sale-detail')));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
jQuery(document).off('auto_fun.aksrt-after-sale').on('auto_fun.aksrt-after-sale', initAfterSaleButtons);
|
||||
// 委托监听不依赖 DOM 扫描,脚本加载时立即挂上;auto_fun 钩子保留作幂等重扫。
|
||||
initAfterSaleButtons();
|
||||
})();
|
||||
+126
-12
@@ -7,6 +7,7 @@
|
||||
"use strict";
|
||||
|
||||
var instances = new WeakMap();
|
||||
var liveStates = [];
|
||||
|
||||
function request(path, body, method, operationKey) {
|
||||
var options = { method: method || "POST" };
|
||||
@@ -74,7 +75,6 @@
|
||||
var map = {
|
||||
pending: { icon: "fa-clock", desc: "订单已生成,请在有效期内完成支付" },
|
||||
paid: { icon: "fa-check-circle", desc: "支付已完成,相关权益已发放至账户" },
|
||||
refunding: { icon: "fa-hourglass-half", desc: "退款申请已受理,正在处理中" },
|
||||
refunded: { icon: "fa-undo-alt", desc: "退款已完成,款项将原路退回" },
|
||||
closed: { icon: "fa-times-circle", desc: "订单已关闭,如有需要可重新下单" },
|
||||
};
|
||||
@@ -106,6 +106,14 @@
|
||||
return "<div><dt>" + escapeHtml(label) + "</dt><dd>" + cell + "</dd></div>";
|
||||
}
|
||||
|
||||
function colorfulHeader(title, icon) {
|
||||
return '<div class="modal-colorful-header colorful-bg jb-blue">' +
|
||||
'<button type="button" class="close" data-aksrt-modal-close aria-label="关闭"><svg class="ic-close" aria-hidden="true"><use xlink:href="#icon-close"></use></svg></button>' +
|
||||
'<div class="colorful-make"></div>' +
|
||||
'<div class="text-center"><div class="em2x"><i class="fa fas ' + escapeHtml(icon) + '" aria-hidden="true"></i></div>' +
|
||||
'<div class="mt10 em12 padding-w10">' + escapeHtml(title) + '</div></div></div>';
|
||||
}
|
||||
|
||||
function renderDetail(state, order, session) {
|
||||
var allowedActions = order.allowed_actions || {};
|
||||
var reconciliation = order.reconciliation || {};
|
||||
@@ -116,6 +124,8 @@
|
||||
var meta = statusMeta(status);
|
||||
var timeLeft = Number(order.time_left || 0);
|
||||
var html = "";
|
||||
// 炫彩标题区:与售后弹窗统一(modal-mini + jb-blue),运行时不再渲染默认标题栏。
|
||||
html += colorfulHeader("订单详情", "fa-receipt");
|
||||
|
||||
// 状态横幅:整块配色只由 --hero-accent 驱动,换状态只改一处变量。
|
||||
html += '<section class="aksrt-order-detail__hero is-' + escapeHtml(status) + '">';
|
||||
@@ -145,15 +155,68 @@
|
||||
html += '<h4 class="aksrt-order-detail__group-title">交易信息</h4>';
|
||||
html += '<dl class="aksrt-order-detail__list">';
|
||||
[
|
||||
["流水号", order.order_number],
|
||||
["订单号", order.order_number],
|
||||
["支付方式", paymentMethodText(order)],
|
||||
["创建时间", order.created_at],
|
||||
["支付时间", order.paid_at],
|
||||
].forEach(function (row) {
|
||||
html += detailRow(row[0], row[1], row[0] === "流水号");
|
||||
html += detailRow(row[0], row[1], row[0] === "订单号");
|
||||
});
|
||||
html += "</dl></section>";
|
||||
|
||||
var afterSales = Array.isArray(order.after_sales) ? order.after_sales : [];
|
||||
if (afterSales.length) {
|
||||
html += '<section class="aksrt-order-detail__group"><h4 class="aksrt-order-detail__group-title">售后记录</h4><ul class="aksrt-order-detail__after-sales">';
|
||||
afterSales.forEach(function (item, index) {
|
||||
var labels = { pending: "待审核", processing: "处理中", approved: "已通过", rejected: "已拒绝", cancelled: "已取消", completed: "已完成" };
|
||||
var asStatus = String(item.status || "");
|
||||
var asLabel = labels[asStatus] || asStatus;
|
||||
html += '<li class="aksrt-order-detail__after-sale is-' + escapeHtml(asStatus) + '">';
|
||||
html += '<div class="aksrt-order-detail__after-sale-head"><span class="aksrt-order-detail__after-sale-badge is-' + escapeHtml(asStatus) + '">' + escapeHtml(asLabel) + "</span>";
|
||||
if (item.created_at) html += "<time>" + escapeHtml(item.created_at) + "</time>";
|
||||
html += "</div>";
|
||||
// 原因 / 申请理由:新数据走 apply_form 结构化字段;老数据无 apply_form 时兜底展示融合串。
|
||||
var applyForm = item.apply_form && typeof item.apply_form === "object" ? item.apply_form : null;
|
||||
if (applyForm) {
|
||||
var reasonLabel = String(applyForm.reason_label || "");
|
||||
var applyDesc = String(applyForm.description || "");
|
||||
if (reasonLabel !== "") html += '<p class="aksrt-order-detail__after-sale-reason"><span class="aksrt-order-detail__after-sale-field">原因:</span>' + escapeHtml(reasonLabel) + "</p>";
|
||||
if (applyDesc !== "") html += '<p class="aksrt-order-detail__after-sale-reason"><span class="aksrt-order-detail__after-sale-field">申请理由:</span>' + escapeHtml(applyDesc).replace(/\r\n|\r|\n/g, "<br>") + "</p>";
|
||||
} else if (item.reason) {
|
||||
html += '<p class="aksrt-order-detail__after-sale-reason">' + escapeHtml(item.reason).replace(/\r\n|\r|\n/g, "<br>") + "</p>";
|
||||
}
|
||||
// 查看详细:逐记录构造售后详细弹窗载荷(after_sale.latest 指向本条记录);
|
||||
// 再次申请仅对最新一条且订单级可再申请时开放(after_sales 按 id 倒序,首条即最新)。
|
||||
var recordPayload = escapeHtml(JSON.stringify({
|
||||
order_number: order.order_number || "",
|
||||
product_name: order.product_name || "",
|
||||
amount: order.amount || "0",
|
||||
is_points: !!order.is_points,
|
||||
currency_symbol: order.currency_symbol || "",
|
||||
status_label: order.status_label || "",
|
||||
after_sale: {
|
||||
enabled: true,
|
||||
has_application: true,
|
||||
can_reapply: index === 0 && !!((order.after_sale || {}).can_reapply),
|
||||
latest: {
|
||||
status: asStatus,
|
||||
reason: String(item.reason || ""),
|
||||
apply_form: applyForm,
|
||||
images: Array.isArray(item.images) ? item.images : [],
|
||||
refund_amount: String(item.refund_amount || "0"),
|
||||
created_at: String(item.created_at || ""),
|
||||
handling_log: Array.isArray(item.handling_log) ? item.handling_log : [],
|
||||
},
|
||||
},
|
||||
}));
|
||||
html += '<div class="aksrt-order-detail__after-sale-foot">';
|
||||
html += '<button class="btn-ghost" type="button" data-aksrt-after-sale-detail="' + recordPayload + '">查看详细</button>';
|
||||
html += "</div>";
|
||||
html += "</li>";
|
||||
});
|
||||
html += "</ul></section>";
|
||||
}
|
||||
|
||||
if (reconciliation.state === "refresh_available") {
|
||||
html += '<div class="aksrt-order-detail__refresh-state" role="status">';
|
||||
html += '<i class="fa fas fa-info-circle" aria-hidden="true"></i>';
|
||||
@@ -162,10 +225,17 @@
|
||||
html += "</div>";
|
||||
}
|
||||
|
||||
html += '<footer class="aksrt-modal__footer aksrt-order-detail__actions">';
|
||||
html += '<button class="btn-outline" type="button" data-aksrt-modal-close>关闭</button>';
|
||||
if (allowedActions.can_cancel) html += '<button class="btn-ghost" type="button" data-aksrt-order-cancel="' + escapeHtml(order.order_number) + '">关闭订单</button>';
|
||||
if (allowedActions.can_pay) html += '<button class="btn-primary" type="button" data-aksrt-order-pay="' + escapeHtml(order.order_number) + '" data-aksrt-order-method="' + escapeHtml(paymentMethod.value || "") + '">继续支付</button>';
|
||||
// 底部按钮与售后详细弹窗同式:but-average modal-buts;「关闭」与「我知道了」
|
||||
// 同款(but c-blue),平铺观感由 orders.css 的 footer 按钮规则统一呈现。
|
||||
html += '<footer class="aksrt-order-detail__actions but-average modal-buts">';
|
||||
html += '<button type="button" class="but c-blue" data-aksrt-modal-close>关闭</button>';
|
||||
if (allowedActions.can_cancel) html += '<button type="button" class="but" data-aksrt-order-cancel="' + escapeHtml(order.order_number) + '">关闭订单</button>';
|
||||
if (allowedActions.can_pay) html += '<button type="button" class="but c-blue" data-aksrt-order-pay="' + escapeHtml(order.order_number) + '" data-aksrt-order-method="' + escapeHtml(paymentMethod.value || "") + '">继续支付</button>';
|
||||
// 售后入口:未申请时底部保留「申请售后」;已有申请时入口收拢到售后记录卡片的「查看详细」。
|
||||
var detailAfterSale = order.after_sale || {};
|
||||
if (detailAfterSale.enabled && !detailAfterSale.has_application) {
|
||||
html += '<button type="button" class="but c-blue" data-aksrt-after-sale-apply="' + afterSalePayload(order) + '">申请售后</button>';
|
||||
}
|
||||
html += "</footer>";
|
||||
|
||||
session.content.innerHTML = html;
|
||||
@@ -179,7 +249,6 @@
|
||||
method: refresh ? "POST" : "GET",
|
||||
headers: actionKey ? { "Idempotency-Key": state.operationKeys[actionKey] } : {},
|
||||
kind: "orders",
|
||||
title: "订单详情",
|
||||
className: "aksrt-order-detail-modal",
|
||||
responseType: "json",
|
||||
cache: !refresh,
|
||||
@@ -241,9 +310,40 @@
|
||||
|
||||
function actionHtml(order) {
|
||||
var allowedActions = order.allowed_actions || {};
|
||||
var afterSale = order.after_sale || {};
|
||||
var html = '<button class="btn-outline" type="button" data-aksrt-order-detail="' + escapeHtml(order.order_number) + '">订单详情</button>';
|
||||
if (allowedActions.can_cancel) html += '<button class="btn-ghost" type="button" data-aksrt-order-cancel="' + escapeHtml(order.order_number) + '">关闭订单</button>';
|
||||
if (allowedActions.can_pay) html += '<button class="btn-primary" type="button" data-aksrt-order-pay="' + escapeHtml(order.order_number) + '" data-aksrt-order-method="' + escapeHtml((order.payment_method || {}).value || "") + '">继续支付</button>';
|
||||
// 售后入口:申请售后 / 售后详细走 order-after-sale.js 的独立弹窗。
|
||||
if (afterSale.enabled) html += afterSaleButtons(order);
|
||||
return html;
|
||||
}
|
||||
|
||||
// 售后按钮(列表卡片与订单详情弹窗共用):已申请 → 售后详细,否则 → 申请售后。
|
||||
// 载荷与 PHP 模板同构(order-after-sale.js 同步消费),点击即弹窗、无前置请求。
|
||||
function afterSalePayload(order) {
|
||||
var afterSale = order.after_sale || {};
|
||||
return escapeHtml(JSON.stringify({
|
||||
order_number: order.order_number || "",
|
||||
product_name: order.product_name || "",
|
||||
amount: order.amount || "0",
|
||||
is_points: !!order.is_points,
|
||||
currency_symbol: order.currency_symbol || "",
|
||||
status_label: order.status_label || "",
|
||||
after_sale: afterSale,
|
||||
}));
|
||||
}
|
||||
|
||||
function afterSaleButtons(order) {
|
||||
var afterSale = order.after_sale || {};
|
||||
var html = "";
|
||||
if (!afterSale.enabled) return html;
|
||||
var payload = afterSalePayload(order);
|
||||
if (afterSale.has_application) {
|
||||
html += '<button class="btn-outline" type="button" data-aksrt-after-sale-detail="' + payload + '">售后详细</button>';
|
||||
return html;
|
||||
}
|
||||
html += '<button class="btn-outline" type="button" data-aksrt-after-sale-apply="' + payload + '">申请售后</button>';
|
||||
return html;
|
||||
}
|
||||
|
||||
@@ -256,7 +356,7 @@
|
||||
: "";
|
||||
return '<article class="aksrt-orders-page__card" data-aksrt-order-card="' + escapeHtml(order.order_number) + '" data-order-status="' + escapeHtml(order.status_group || "") + '">' +
|
||||
'<div class="aksrt-orders-page__card-rail"><span>' + escapeHtml(order.product_type_label || "") + '</span><time>' + escapeHtml(order.created_at || "") + '</time><span class="aksrt-orders-page__card-status is-' + escapeHtml(order.status_group || "") + '">' + escapeHtml(order.status_label || "") + "</span></div>" +
|
||||
'<div class="aksrt-orders-page__card-main">' + image + '<div class="aksrt-orders-page__card-copy"><h3>' + escapeHtml(order.product_name || "订单") + "</h3><p>流水号 " + escapeHtml(order.order_number) + "</p></div><div class=\"aksrt-orders-page__card-value\"><strong>" + formatMoney(order) + "</strong><span>实付</span></div></div>" +
|
||||
'<div class="aksrt-orders-page__card-main">' + image + '<div class="aksrt-orders-page__card-copy"><h3>' + escapeHtml(order.product_name || "订单") + "</h3><p>订单号 " + escapeHtml(order.order_number) + "</p></div><div class=\"aksrt-orders-page__card-value\"><strong>" + formatMoney(order) + "</strong><span>实付</span></div></div>" +
|
||||
'<footer class="aksrt-orders-page__card-actions">' + countdown + actionHtml(order) + "</footer></article>";
|
||||
}
|
||||
|
||||
@@ -265,7 +365,10 @@
|
||||
if (!root) return;
|
||||
root.innerHTML = '<div class="aksrt-orders-page__stat"><span>全部</span><strong>' + Number(stats.total || 0) + "</strong></div>" +
|
||||
'<div class="aksrt-orders-page__stat"><span>待支付</span><strong class="is-pending">' + Number(stats.pending || 0) + "</strong></div>" +
|
||||
'<div class="aksrt-orders-page__stat"><span>已支付</span><strong class="is-paid">' + Number(stats.paid || 0) + "</strong></div>";
|
||||
'<div class="aksrt-orders-page__stat"><span>已支付</span><strong class="is-paid">' + Number(stats.paid || 0) + "</strong></div>" +
|
||||
'<div class="aksrt-orders-page__stat"><span>已退款</span><strong>' + Number(stats.refunded || 0) + "</strong></div>" +
|
||||
'<div class="aksrt-orders-page__stat"><span>已关闭</span><strong>' + Number(stats.closed || 0) + "</strong></div>" +
|
||||
'<div class="aksrt-orders-page__stat"><span>售后待处理</span><strong>' + Number(stats.after_sale_pending || 0) + "</strong></div>";
|
||||
var pendingCount = state.root.querySelector('[data-aksrt-order-filter="status"][data-aksrt-order-filter-value="pending"] .aksrt-orders-page__filter-count');
|
||||
if (pendingCount) {
|
||||
pendingCount.textContent = String(Number(stats.pending || 0));
|
||||
@@ -428,7 +531,7 @@
|
||||
if (!value) return;
|
||||
var icon = button.querySelector("i");
|
||||
var settle = function (ok) {
|
||||
notyf(ok ? "流水号已复制" : "复制失败,请手动选择复制", ok ? "success" : "danger");
|
||||
notyf(ok ? "订单号已复制" : "复制失败,请手动选择复制", ok ? "success" : "danger");
|
||||
if (!ok) return;
|
||||
button.classList.add("is-copied");
|
||||
if (icon) icon.className = "fa fas fa-check";
|
||||
@@ -533,6 +636,7 @@
|
||||
state.detailReplacementSession = null;
|
||||
};
|
||||
state.onDocumentClick = function (event) {
|
||||
// 取消/撤销售后已移入售后详细弹窗(order-after-sale.js 的「撤销售后」入口)。
|
||||
if (!state.detailSession || window.AksrtModalRuntime.getActive() !== state.detailSession.shell) return;
|
||||
var copy = closest(event.target, "[data-aksrt-order-copy]");
|
||||
if (copy) {
|
||||
@@ -565,6 +669,7 @@
|
||||
document.addEventListener("click", state.onDocumentClick);
|
||||
state.countdownTimer = window.setInterval(function () { updateCountdowns(state); }, 1000);
|
||||
instances.set(root, state);
|
||||
liveStates.push(state);
|
||||
}
|
||||
|
||||
function rootsIn(root) {
|
||||
@@ -590,9 +695,18 @@
|
||||
document.removeEventListener("click", state.onDocumentClick);
|
||||
window.clearInterval(state.countdownTimer);
|
||||
instances.delete(item);
|
||||
liveStates = liveStates.filter(function (entry) { return entry !== state; });
|
||||
});
|
||||
}
|
||||
|
||||
window.AKSRTOrderCenter = { init: init, destroy: destroy };
|
||||
// refresh 供售后弹窗(order-after-sale.js)提交/取消后刷新列表与统计。
|
||||
function refresh(orderNumber) {
|
||||
invalidateOrder(orderNumber);
|
||||
liveStates.forEach(function (state) {
|
||||
refreshView(state);
|
||||
});
|
||||
}
|
||||
|
||||
window.AKSRTOrderCenter = { init: init, destroy: destroy, refresh: refresh };
|
||||
jQuery(document).off("auto_fun.aksrt-order-center").on("auto_fun.aksrt-order-center", function () { init(document); });
|
||||
}());
|
||||
+110
-23
@@ -17,6 +17,14 @@
|
||||
return el;
|
||||
}
|
||||
|
||||
// Server-side schema sanitization limits these configured rich-text fields to post-safe HTML.
|
||||
function htmlNode(tag, className, value) {
|
||||
var el = document.createElement(tag);
|
||||
if (className) el.className = className;
|
||||
if (value !== undefined && value !== null) el.innerHTML = text(value);
|
||||
return el;
|
||||
}
|
||||
|
||||
function imageNode(src, className) {
|
||||
if (!src) return null;
|
||||
var image = document.createElement("img");
|
||||
@@ -61,7 +69,7 @@
|
||||
if (levelInfo && levelInfo.img_icon) {
|
||||
var img = node("img", "img-icon");
|
||||
img.src = text(levelInfo.img_icon);
|
||||
img.alt = text(levelInfo.name || "");
|
||||
img.alt = text(levelInfo.name_text || levelInfo.name || "");
|
||||
img.decoding = "async";
|
||||
return img;
|
||||
}
|
||||
@@ -115,7 +123,8 @@
|
||||
// Zibll 套餐卡:zib-widget vip-product relative text-center product-box
|
||||
// (右上 tag + 价格行 + 时长 badge,无标题行)。
|
||||
var product = node("div", "zib-widget vip-product relative text-center product-box");
|
||||
if (item.discount_tag) append(product, node("div", "abs-right vip-tag badg jb-yellow", item.discount_tag));
|
||||
// 标签颜色取后台「标签颜色」配置,未配置时回退参考主题默认的 jb-yellow。
|
||||
if (item.discount_tag) append(product, htmlNode("div", "abs-right vip-tag badg " + (item.tag_class || "jb-yellow"), item.discount_tag));
|
||||
append(product, priceRow(mark, item.price_label || "", item.show_price_label || ""));
|
||||
if (item.days_label) append(product, node("div", "muted-2-color c-blue em09 p2-10 badg radius", item.days_label));
|
||||
append(label, product);
|
||||
@@ -179,7 +188,7 @@
|
||||
box.setAttribute("data-condition", "==");
|
||||
box.setAttribute("data-value", "card_pass");
|
||||
if (defaultMethod !== "card_pass") box.style.display = "none";
|
||||
if (cardPass.desc) append(box, node("div", "muted-box muted-2-color padding-10 mb10 em09", cardPass.desc));
|
||||
if (cardPass.desc) append(box, htmlNode("div", "muted-box muted-2-color padding-10 mb10 em09", cardPass.desc));
|
||||
append(box, node("div", "muted-2-color em09 mb6", cardPass.only_password ? "请输入卡密" : "请输入卡号和密码"));
|
||||
if (!cardPass.only_password) {
|
||||
var cardInput = node("input", "form-control mb6");
|
||||
@@ -384,12 +393,7 @@
|
||||
var avatarItem = node("li");
|
||||
var avatarWrap = node("div", "avatar-img");
|
||||
if (member.avatar_url) {
|
||||
var avatar = node("img");
|
||||
avatar.src = text(member.avatar_url);
|
||||
avatar.alt = "";
|
||||
avatar.width = 42;
|
||||
avatar.height = 42;
|
||||
append(avatarWrap, avatar);
|
||||
append(avatarWrap, imageNode(text(member.avatar_url), "avatar"));
|
||||
}
|
||||
append(avatarItem, avatarWrap);
|
||||
append(userBox, avatarItem);
|
||||
@@ -403,7 +407,7 @@
|
||||
} else if (member.state === "expired") {
|
||||
append(userInfo, node("div", "muted-2-color em09", "已过期"));
|
||||
} else if (catalog.desc) {
|
||||
append(userInfo, node("div", "muted-2-color em09", catalog.desc));
|
||||
append(userInfo, htmlNode("div", "muted-2-color em09", catalog.desc));
|
||||
}
|
||||
append(userBox, userInfo);
|
||||
append(modal, userBox);
|
||||
@@ -439,8 +443,8 @@
|
||||
append(miniIcon, vipIcon(levelInfo));
|
||||
append(miniIconbox, miniIcon);
|
||||
var miniInfo = node("div", "v-info");
|
||||
append(miniInfo, node("div", "v-name", actionLabel(action) + (levelInfo.name || group.name || "VIP" + level)));
|
||||
append(miniInfo, node("div", "v-desc em09", levelInfo.desc || ""));
|
||||
append(miniInfo, htmlNode("div", "v-name", actionLabel(action) + (levelInfo.name || group.name || "VIP" + level)));
|
||||
append(miniInfo, htmlNode("div", "v-desc em09", levelInfo.desc || ""));
|
||||
append(miniIconbox, miniInfo);
|
||||
append(miniCard, miniIconbox);
|
||||
append(tab, miniCard);
|
||||
@@ -466,15 +470,12 @@
|
||||
append(vicon, vipIcon(levelInfo));
|
||||
append(iconbox, vicon);
|
||||
var vinfo = node("div", "v-info");
|
||||
append(vinfo, node("div", "v-name", levelInfo.name || group.name || "VIP" + level));
|
||||
append(vinfo, node("div", "v-desc em09", levelInfo.desc || ""));
|
||||
append(vinfo, htmlNode("div", "v-name", levelInfo.name || group.name || "VIP" + level));
|
||||
append(vinfo, htmlNode("div", "v-desc em09", levelInfo.desc || ""));
|
||||
append(iconbox, vinfo);
|
||||
append(info, iconbox);
|
||||
if (levelInfo.equity) {
|
||||
var equity = document.createElement("ul");
|
||||
equity.className = "v-equity";
|
||||
equity.innerHTML = text(levelInfo.equity);
|
||||
append(info, equity);
|
||||
append(info, htmlNode("div", "", levelInfo.equity));
|
||||
}
|
||||
var infoBa = node("div", "abs-center vip-baicon");
|
||||
append(infoBa, vipIcon(levelInfo));
|
||||
@@ -497,7 +498,7 @@
|
||||
+ '<i class="fa fas fa-angle-right em12 ml10" aria-hidden="true"></i>';
|
||||
append(products, pointsLink);
|
||||
}
|
||||
if (catalog.more) append(products, node("div", "muted-2-color mb10 em09 muted-box padding-10 mini-radius", catalog.more));
|
||||
if (catalog.more) append(products, htmlNode("div", "muted-2-color mb10 em09 muted-box padding-10 mini-radius", catalog.more));
|
||||
renderMethods(products, methods, cardPass, wallet);
|
||||
var actionName = actionKey(action);
|
||||
var actionAllowed = allowed[actionName] === true;
|
||||
@@ -524,8 +525,6 @@
|
||||
append(modal, tabs);
|
||||
append(modal, content);
|
||||
|
||||
if (catalog.help_text) append(modal, node("div", "muted-2-color em09 mb6", catalog.help_text));
|
||||
|
||||
// 卡密渠道初始显隐(选中 card_pass 时隐藏套餐、显示卡密输入盒)。
|
||||
modal.querySelectorAll(".aksrt-vip-pane").forEach(function (pane) {
|
||||
applyVipMethodVisibility(pane);
|
||||
@@ -703,6 +702,46 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 骨架屏:仿弹窗真实布局(用户框 + 等级卡 + 双列套餐面板)。
|
||||
// 线条/色块复用全局骨架类(build/public.min.css),VIP 专属形状见 vip-modal.css。
|
||||
function vipSkeleton() {
|
||||
var skeleton = node("div", "aksrt-vip-modal-skeleton");
|
||||
skeleton.setAttribute("aria-live", "polite");
|
||||
skeleton.setAttribute("aria-busy", "true");
|
||||
|
||||
// 用户框:头像圆 + 两行文字。
|
||||
var user = node("div", "aksrt-vip-modal-skeleton__user");
|
||||
append(user, node("span", "aksrt-vip-modal-skeleton__avatar"));
|
||||
var userLines = node("span", "aksrt-vip-modal-skeleton__lines");
|
||||
append(userLines, node("span", "aksrt-modal-runtime__skeleton-line is-wide"));
|
||||
append(userLines, node("span", "aksrt-modal-runtime__skeleton-line is-short"));
|
||||
append(user, userLines);
|
||||
append(skeleton, user);
|
||||
|
||||
// 等级卡行(vip-cardminis):两张 tab 占位。
|
||||
var tabs = node("div", "aksrt-vip-modal-skeleton__tabs");
|
||||
append(tabs, node("span", "aksrt-vip-modal-skeleton__tab"));
|
||||
append(tabs, node("span", "aksrt-vip-modal-skeleton__tab"));
|
||||
append(skeleton, tabs);
|
||||
|
||||
// 双列面板:左权益信息 + 右套餐区(套餐卡、支付方式行、提交按钮条)。
|
||||
var pane = node("div", "aksrt-vip-modal-skeleton__pane");
|
||||
var info = node("div", "aksrt-vip-modal-skeleton__info");
|
||||
append(info, node("span", "aksrt-modal-runtime__skeleton-line is-wide"));
|
||||
append(info, node("span", "aksrt-modal-runtime__skeleton-line"));
|
||||
append(info, node("span", "aksrt-modal-runtime__skeleton-line is-short"));
|
||||
append(pane, info);
|
||||
var products = node("div", "aksrt-vip-modal-skeleton__products");
|
||||
append(products, node("span", "aksrt-modal-runtime__skeleton-block"));
|
||||
append(products, node("span", "aksrt-modal-runtime__skeleton-block"));
|
||||
append(products, node("span", "aksrt-modal-runtime__skeleton-line is-short"));
|
||||
append(products, node("span", "aksrt-vip-modal-skeleton__button"));
|
||||
append(pane, products);
|
||||
append(skeleton, pane);
|
||||
|
||||
return skeleton.outerHTML;
|
||||
}
|
||||
|
||||
function vipPay(trigger) {
|
||||
var level = trigger
|
||||
? parseInt(trigger.getAttribute("vip-level") || trigger.getAttribute("data-vip-level") || "1", 10) || 1
|
||||
@@ -711,11 +750,11 @@
|
||||
url: "/pay/vip/modal",
|
||||
query: { vip_level: level },
|
||||
className: "aksrt-vip-modal-shell",
|
||||
skeleton: '<div class="aksrt-vip-modal-panel"><div class="aksrt-vip-modal-content" data-aksrt-vip-modal-body><div class="text-center padding-20 muted-2-color">加载中…</div></div></div>',
|
||||
skeleton: vipSkeleton(),
|
||||
telemetry: "vip",
|
||||
kind: "vip",
|
||||
responseType: "json",
|
||||
cacheTtl: 15000,
|
||||
cacheTtl: 60000,
|
||||
cacheTags: ["vip"],
|
||||
renderData: function (data, session) {
|
||||
renderModalView(session.content, data);
|
||||
@@ -733,6 +772,53 @@
|
||||
});
|
||||
}
|
||||
|
||||
var prefetchedLevels = Object.create(null);
|
||||
|
||||
// 与 openRemote 同一 cacheKey(method + AksrtRequest.url),命中同一份缓存/flight。
|
||||
function vipModalCacheKey(level) {
|
||||
return "GET:" + window.AksrtRequest.url("/pay/vip/modal", { vip_level: level });
|
||||
}
|
||||
|
||||
// 意图预热:悬停/聚焦开通入口时按等级预热弹窗数据(不打开弹窗,失败静默)。
|
||||
// 点击打开时若预热请求在途,cache.acquire 的 flight 去重会自动合并。
|
||||
function prefetchVipModal(trigger) {
|
||||
if (!trigger || !window.AksrtModalCache || !window.AksrtRequest) return;
|
||||
var level = parseInt(trigger.getAttribute("vip-level") || trigger.getAttribute("data-vip-level") || "1", 10) || 1;
|
||||
if (level < 1 || level > 2) level = 1;
|
||||
if (prefetchedLevels[level]) return;
|
||||
prefetchedLevels[level] = true;
|
||||
var cache = window.AksrtModalCache;
|
||||
if (cache.get && cache.get(vipModalCacheKey(level)).hit) return;
|
||||
try {
|
||||
cache.acquire(vipModalCacheKey(level), function () {
|
||||
return window.AksrtRequest.request("/pay/vip/modal", {
|
||||
method: "GET",
|
||||
query: { vip_level: level },
|
||||
});
|
||||
}, {
|
||||
ttl: 60000,
|
||||
tags: ["vip"],
|
||||
});
|
||||
} catch (error) {
|
||||
// 预热失败不影响点击后的正常打开路径。
|
||||
}
|
||||
}
|
||||
|
||||
function vipTriggerFrom(event) {
|
||||
var target = event.target;
|
||||
if (!target || !target.closest) return null;
|
||||
return target.closest(".pay-vip, a.pay-vip, .float-btn.pay-vip, [data-aksrt-pay-vip]");
|
||||
}
|
||||
|
||||
function bindPrefetch() {
|
||||
// pointerenter 不冒泡但走捕获阶段;focusin 冒泡,两者互补覆盖鼠标与键盘。
|
||||
["pointerenter", "focusin"].forEach(function (type) {
|
||||
document.addEventListener(type, function (event) {
|
||||
prefetchVipModal(vipTriggerFrom(event));
|
||||
}, true);
|
||||
});
|
||||
}
|
||||
|
||||
function onDocClick(event) {
|
||||
var trigger = event.target.closest(".pay-vip, a.pay-vip, .float-btn.pay-vip, [data-aksrt-pay-vip]");
|
||||
if (!trigger) return;
|
||||
@@ -773,6 +859,7 @@
|
||||
if (window.__aksrtPayVipBound) return;
|
||||
window.__aksrtPayVipBound = true;
|
||||
document.addEventListener("click", onDocClick, true);
|
||||
bindPrefetch();
|
||||
bindPointsModal();
|
||||
bindPointsEntry();
|
||||
}
|
||||
|
||||
+219
-95
@@ -1,127 +1,251 @@
|
||||
/**
|
||||
* On-site wallet tipping modal.
|
||||
* On-site wallet tipping content for the shared modal runtime.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
;(function () {
|
||||
;(function (window, document) {
|
||||
'use strict';
|
||||
|
||||
var cfg = window.aksrtData || {};
|
||||
var titleSequence = 0;
|
||||
var toastId = 'aksrt-reward-toast';
|
||||
|
||||
function initReward() {
|
||||
var activeTrigger = null;
|
||||
function escapeHtml(value) {
|
||||
return String(value == null ? '' : value).replace(/[&<>"']/g, function (character) {
|
||||
return {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
}[character];
|
||||
});
|
||||
}
|
||||
|
||||
function operationKey(modal) {
|
||||
if (!modal.dataset.aksrtOperationKey) {
|
||||
modal.dataset.aksrtOperationKey = window.AksrtRequest.createIdempotencyKey();
|
||||
}
|
||||
return modal.dataset.aksrtOperationKey;
|
||||
function notify(message, type, time) {
|
||||
if (typeof window.notyf === 'function') {
|
||||
window.notyf(escapeHtml(message), type || 'info', time, toastId);
|
||||
}
|
||||
}
|
||||
|
||||
function rewardModal(target) {
|
||||
return target.closest('.aksrt-modal-runtime.aksrt-reward-modal');
|
||||
}
|
||||
|
||||
function rewardTemplate(modal) {
|
||||
return modal ? document.getElementById(modal.dataset.templateId || '') : null;
|
||||
}
|
||||
|
||||
function operationKey(template) {
|
||||
if (!template.dataset.aksrtOperationKey) {
|
||||
template.dataset.aksrtOperationKey = window.AksrtRequest.createIdempotencyKey();
|
||||
}
|
||||
return template.dataset.aksrtOperationKey;
|
||||
}
|
||||
|
||||
function runCaptcha(modal) {
|
||||
var mount = modal.querySelector('[data-aksrt-captcha-config][data-scene="tip_user"]');
|
||||
if (!mount) return Promise.resolve({});
|
||||
if (!window.AKSRTCaptcha || typeof window.AKSRTCaptcha.run !== 'function') {
|
||||
return Promise.reject(new Error('人机验证正在加载,请稍后重试'));
|
||||
}
|
||||
|
||||
function closeAll() {
|
||||
document.querySelectorAll('.reward-modal.is-open').forEach(function (modal) {
|
||||
modal.classList.remove('is-open');
|
||||
modal.setAttribute('aria-hidden', 'true');
|
||||
return window.AKSRTCaptcha.run('tip_user', modal).then(function (payload) {
|
||||
if (!payload) return null;
|
||||
if (!payload.aksrt_captcha_proof) throw new Error('人机验证凭证未就绪,请重试');
|
||||
return payload;
|
||||
});
|
||||
}
|
||||
|
||||
function setSubmitting(modal, submit, submitting, succeeded) {
|
||||
submit.disabled = submitting || succeeded;
|
||||
submit.textContent = submitting ? '验证中…' : '余额赞赏';
|
||||
modal.querySelectorAll('[data-reward-amount], [data-aksrt-reward-custom]').forEach(function (control) {
|
||||
control.disabled = submitting || succeeded;
|
||||
});
|
||||
}
|
||||
|
||||
function openReward(trigger) {
|
||||
var runtime = window.AksrtModalRuntime;
|
||||
if (!runtime || !window.AksrtRequest) {
|
||||
if (trigger.dataset.aksrtRewardWaiting !== '1') {
|
||||
trigger.dataset.aksrtRewardWaiting = '1';
|
||||
document.addEventListener('aksrt:modal-runtime:ready', function () {
|
||||
delete trigger.dataset.aksrtRewardWaiting;
|
||||
if (document.contains(trigger)) openReward(trigger);
|
||||
}, { once: true });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var templateId = trigger.getAttribute('data-template-id') || '';
|
||||
var template = document.getElementById(templateId);
|
||||
if (!template || !template.content) return;
|
||||
|
||||
var shell = runtime.open({
|
||||
html: template.innerHTML,
|
||||
className: 'aksrt-reward-modal modal-mini full-sm',
|
||||
mobileBottom: true,
|
||||
kind: 'generic',
|
||||
});
|
||||
shell.dataset.templateId = template.id;
|
||||
shell.dataset.userId = trigger.getAttribute('data-user-id') || template.dataset.userId || '';
|
||||
shell.dataset.postId = trigger.getAttribute('data-post-id') || template.dataset.postId || '0';
|
||||
var title = shell.querySelector('.aksrt-reward__title');
|
||||
if (title) {
|
||||
title.id = 'aksrt-reward-title-' + (++titleSequence);
|
||||
shell.setAttribute('aria-labelledby', title.id);
|
||||
}
|
||||
|
||||
var initialFocus = shell.querySelector('[data-reward-amount].is-active, [data-aksrt-reward-custom]');
|
||||
if (initialFocus) initialFocus.focus();
|
||||
}
|
||||
|
||||
document.addEventListener('click', function (event) {
|
||||
var opener = event.target.closest('[data-aksrt-reward-open]');
|
||||
if (opener) {
|
||||
event.preventDefault();
|
||||
openReward(opener);
|
||||
return;
|
||||
}
|
||||
|
||||
var amountButton = event.target.closest('[data-reward-amount]');
|
||||
if (amountButton) {
|
||||
var amountModal = rewardModal(amountButton);
|
||||
if (!amountModal) return;
|
||||
var amountTemplate = rewardTemplate(amountModal);
|
||||
if (amountTemplate) delete amountTemplate.dataset.aksrtOperationKey;
|
||||
amountModal.querySelectorAll('[data-reward-amount]').forEach(function (button) {
|
||||
var isActive = button === amountButton;
|
||||
button.classList.toggle('is-active', isActive);
|
||||
button.setAttribute('aria-pressed', isActive ? 'true' : 'false');
|
||||
});
|
||||
document.body.style.overflow = '';
|
||||
if (activeTrigger) activeTrigger.focus();
|
||||
activeTrigger = null;
|
||||
var customInput = amountModal.querySelector('[data-aksrt-reward-custom]');
|
||||
if (customInput) customInput.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
function open(trigger) {
|
||||
var userId = trigger.getAttribute('data-user-id') || '';
|
||||
var modal = document.getElementById('reward-modal-' + userId);
|
||||
if (!modal) return;
|
||||
closeAll();
|
||||
activeTrigger = trigger;
|
||||
modal.dataset.postId = trigger.getAttribute('data-post-id') || modal.dataset.postId || '0';
|
||||
modal.classList.add('is-open');
|
||||
modal.setAttribute('aria-hidden', 'false');
|
||||
document.body.style.overflow = 'hidden';
|
||||
var first = modal.querySelector('[data-reward-amount], [data-aksrt-reward-custom]');
|
||||
if (first) first.focus();
|
||||
var submit = event.target.closest('[data-aksrt-reward-submit]');
|
||||
if (!submit || submit.disabled) return;
|
||||
|
||||
var modal = rewardModal(submit);
|
||||
var template = rewardTemplate(modal);
|
||||
var runtime = window.AksrtModalRuntime;
|
||||
if (!modal || !template || !runtime) return;
|
||||
|
||||
var message = modal.querySelector('[data-aksrt-reward-message]');
|
||||
var custom = modal.querySelector('[data-aksrt-reward-custom]');
|
||||
var selected = modal.querySelector('[data-reward-amount].is-active');
|
||||
var amount = custom && custom.value ? custom.value : (selected ? selected.dataset.rewardAmount : '');
|
||||
if (!amount) {
|
||||
if (message) message.textContent = '请选择或输入赞赏金额';
|
||||
notify('请选择或输入赞赏金额', 'warning');
|
||||
return;
|
||||
}
|
||||
if (custom && custom.value && !custom.checkValidity()) {
|
||||
custom.reportValidity();
|
||||
return;
|
||||
}
|
||||
var key;
|
||||
try {
|
||||
key = operationKey(template);
|
||||
} catch (error) {
|
||||
if (message) message.textContent = error.message || '无法创建安全请求,请稍后重试';
|
||||
notify(error.message || '无法创建安全请求,请稍后重试', 'danger');
|
||||
return;
|
||||
}
|
||||
|
||||
document.addEventListener('click', function (event) {
|
||||
var opener = event.target.closest('[data-aksrt-reward-open]');
|
||||
if (opener) {
|
||||
event.preventDefault();
|
||||
open(opener);
|
||||
return;
|
||||
}
|
||||
setSubmitting(modal, submit, true, false);
|
||||
if (message) message.textContent = '正在进行人机验证…';
|
||||
|
||||
if (event.target.closest('[data-aksrt-reward-close]')) {
|
||||
event.preventDefault();
|
||||
closeAll();
|
||||
return;
|
||||
}
|
||||
var body = new URLSearchParams();
|
||||
body.set('action', 'aksrt_submit_tip');
|
||||
body.set('nonce', cfg.nonce || '');
|
||||
body.set('recipient_id', modal.dataset.userId || '');
|
||||
body.set('post_id', modal.dataset.postId || '0');
|
||||
body.set('amount', amount);
|
||||
var succeeded = false;
|
||||
|
||||
var amountButton = event.target.closest('[data-reward-amount]');
|
||||
if (amountButton) {
|
||||
var modal = amountButton.closest('.reward-modal');
|
||||
delete modal.dataset.aksrtOperationKey;
|
||||
modal.querySelectorAll('[data-reward-amount]').forEach(function (button) {
|
||||
button.classList.toggle('is-active', button === amountButton);
|
||||
});
|
||||
var custom = modal.querySelector('[data-aksrt-reward-custom]');
|
||||
if (custom) custom.value = '';
|
||||
return;
|
||||
runCaptcha(modal).then(function (captchaPayload) {
|
||||
if (!captchaPayload) {
|
||||
if (window.AKSRTCaptcha && typeof window.AKSRTCaptcha.reset === 'function') {
|
||||
window.AKSRTCaptcha.reset('tip_user', modal);
|
||||
}
|
||||
return { cancelled: true };
|
||||
}
|
||||
|
||||
var submit = event.target.closest('[data-aksrt-reward-submit]');
|
||||
if (!submit || submit.disabled) return;
|
||||
var tipModal = submit.closest('.reward-modal');
|
||||
var message = tipModal.querySelector('[data-aksrt-reward-message]');
|
||||
var customInput = tipModal.querySelector('[data-aksrt-reward-custom]');
|
||||
var selected = tipModal.querySelector('[data-reward-amount].is-active');
|
||||
var amount = customInput && customInput.value ? customInput.value : (selected ? selected.dataset.rewardAmount : '');
|
||||
if (customInput && customInput.dataset.aksrtIdempotencyBound !== '1') {
|
||||
customInput.dataset.aksrtIdempotencyBound = '1';
|
||||
customInput.addEventListener('input', function () { delete tipModal.dataset.aksrtOperationKey; });
|
||||
if (runtime.getActive() !== modal) {
|
||||
if (window.AKSRTCaptcha && typeof window.AKSRTCaptcha.reset === 'function') {
|
||||
window.AKSRTCaptcha.reset('tip_user', modal);
|
||||
}
|
||||
return { cancelled: true };
|
||||
}
|
||||
|
||||
submit.disabled = true;
|
||||
Object.keys(captchaPayload).forEach(function (name) {
|
||||
var value = captchaPayload[name];
|
||||
if (value != null && typeof value !== 'object') body.set(name, String(value));
|
||||
});
|
||||
submit.textContent = '赞赏中…';
|
||||
if (message) message.textContent = '';
|
||||
|
||||
var body = new URLSearchParams();
|
||||
body.set('action', 'aksrt_submit_tip');
|
||||
body.set('nonce', cfg.nonce || '');
|
||||
body.set('recipient_id', tipModal.dataset.userId || '');
|
||||
body.set('post_id', tipModal.dataset.postId || '0');
|
||||
body.set('amount', amount);
|
||||
|
||||
fetch(cfg.ajaxUrl || '/wp-admin/admin-ajax.php', {
|
||||
if (message) message.textContent = '正在提交赞赏…';
|
||||
notify('正在提交赞赏…', 'load', '');
|
||||
return window.fetch(cfg.ajaxUrl || '/wp-admin/admin-ajax.php', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
'Idempotency-Key': operationKey(tipModal)
|
||||
'Idempotency-Key': key,
|
||||
},
|
||||
body: body.toString()
|
||||
body: body.toString(),
|
||||
}).then(function (response) {
|
||||
return response.json();
|
||||
}).then(function (response) {
|
||||
var text = response && response.data && response.data.message ? response.data.message : '赞赏失败';
|
||||
if (response && response.success) {
|
||||
delete tipModal.dataset.aksrtOperationKey;
|
||||
if (message) message.textContent = text;
|
||||
setTimeout(closeAll, 900);
|
||||
} else if (message) {
|
||||
message.textContent = text;
|
||||
}
|
||||
}).catch(function () {
|
||||
if (message) message.textContent = '网络错误,请稍后重试';
|
||||
}).finally(function () {
|
||||
submit.disabled = false;
|
||||
submit.textContent = '余额赞赏';
|
||||
});
|
||||
}).then(function (response) {
|
||||
if (runtime.getActive() !== modal) return;
|
||||
if (response && response.cancelled) {
|
||||
if (message) message.textContent = '请完成人机验证后再试';
|
||||
notify('请完成人机验证后再试', 'warning', 5);
|
||||
return;
|
||||
}
|
||||
var text = response && response.data && response.data.message ? response.data.message : '赞赏失败';
|
||||
if (response && response.success) {
|
||||
succeeded = true;
|
||||
delete template.dataset.aksrtOperationKey;
|
||||
if (message) message.textContent = text;
|
||||
notify(text, 'success', 5);
|
||||
window.setTimeout(function () {
|
||||
if (runtime.getActive() === modal) runtime.close('success');
|
||||
}, 900);
|
||||
} else {
|
||||
if (message) message.textContent = text;
|
||||
notify(text, 'danger', 5);
|
||||
if (window.AKSRTCaptcha && typeof window.AKSRTCaptcha.reset === 'function') {
|
||||
window.AKSRTCaptcha.reset('tip_user', modal);
|
||||
}
|
||||
}
|
||||
}).catch(function (error) {
|
||||
if (runtime.getActive() !== modal) return;
|
||||
var errorMessage = error && error.message ? error.message : '网络错误,请稍后重试';
|
||||
if (message) message.textContent = errorMessage;
|
||||
notify(errorMessage, 'danger', 5);
|
||||
if (window.AKSRTCaptcha && typeof window.AKSRTCaptcha.reset === 'function') {
|
||||
window.AKSRTCaptcha.reset('tip_user', modal);
|
||||
}
|
||||
}).finally(function () {
|
||||
setSubmitting(modal, submit, false, succeeded);
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function (event) {
|
||||
if (event.key === 'Escape') closeAll();
|
||||
document.addEventListener('input', function (event) {
|
||||
if (!event.target.matches('[data-aksrt-reward-custom]')) return;
|
||||
var modal = rewardModal(event.target);
|
||||
var template = rewardTemplate(modal);
|
||||
if (template) delete template.dataset.aksrtOperationKey;
|
||||
if (!modal) return;
|
||||
|
||||
var hasCustomAmount = event.target.value !== '';
|
||||
var firstAmount = modal.querySelector('[data-reward-amount]');
|
||||
modal.querySelectorAll('[data-reward-amount]').forEach(function (button) {
|
||||
var isActive = hasCustomAmount ? false : button === firstAmount;
|
||||
button.classList.toggle('is-active', isActive);
|
||||
button.setAttribute('aria-pressed', isActive ? 'true' : 'false');
|
||||
});
|
||||
}
|
||||
|
||||
jQuery(document).off('auto_fun.aksrt-reward').on('auto_fun.aksrt-reward', initReward);
|
||||
})();
|
||||
});
|
||||
})(window, document);
|
||||
@@ -1,364 +1,235 @@
|
||||
(function (window, document) {
|
||||
'use strict';
|
||||
|
||||
function parseConfig(root) {
|
||||
function configFor(root) {
|
||||
try { return JSON.parse(root.getAttribute('data-identity-config') || '{}'); } catch (_) { return {}; }
|
||||
}
|
||||
|
||||
function uuid() {
|
||||
if (window.crypto && crypto.randomUUID) return crypto.randomUUID();
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
var r = Math.random() * 16 | 0;
|
||||
return (c === 'x' ? r : (r & 3 | 8)).toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
function request(path, config, options) {
|
||||
options = Object.assign({}, options || {});
|
||||
options.credentials = 'same-origin';
|
||||
options.headers = Object.assign({ 'X-WP-Nonce': config.nonce || '' }, options.headers || {});
|
||||
return window.AksrtRequest.request(path, options).then(function (response) {
|
||||
return response.data !== undefined ? response.data : response.json;
|
||||
});
|
||||
function parseResponse(response) {
|
||||
var payload = response && (response.data !== undefined ? response.data : response.json);
|
||||
return payload && payload.data !== undefined ? payload.data : payload;
|
||||
}
|
||||
|
||||
function errorMessage(error, fallback) {
|
||||
if (error && error.json) {
|
||||
return error.json.message || (error.json.data && error.json.data.message) || fallback;
|
||||
}
|
||||
if (error && error.json) return error.json.message || fallback;
|
||||
return error && error.message ? error.message : fallback;
|
||||
}
|
||||
|
||||
function loadScript(src) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var existing = document.querySelector('script[src="' + src + '"]');
|
||||
if (existing) {
|
||||
if (existing.dataset.loaded === '1') return resolve();
|
||||
existing.addEventListener('load', resolve, { once: true });
|
||||
existing.addEventListener('error', reject, { once: true });
|
||||
return;
|
||||
}
|
||||
var script = document.createElement('script');
|
||||
script.src = src;
|
||||
script.async = true;
|
||||
script.onload = function () { script.dataset.loaded = '1'; resolve(); };
|
||||
script.onerror = reject;
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
function aliyunMetaInfo(config) {
|
||||
if (config.provider !== 'aliyun') return Promise.resolve('');
|
||||
return loadScript('https://o.alicdn.com/yd-cloudauth/cloudauth-cdn/jsvm_all.js').then(function () {
|
||||
if (typeof window.getMetaInfo !== 'function') throw new Error('阿里云 MetaInfo 组件未加载成功');
|
||||
var meta = window.getMetaInfo();
|
||||
return typeof meta === 'string' ? meta : JSON.stringify(meta || {});
|
||||
});
|
||||
function toast(message, type, time, id) {
|
||||
if (message && typeof window.notyf === 'function') window.notyf(message, type || 'success', time, id);
|
||||
}
|
||||
|
||||
function init(root) {
|
||||
if (!root || root.dataset.identityBound === '1') return;
|
||||
root.dataset.identityBound = '1';
|
||||
|
||||
var config = parseConfig(root);
|
||||
var config = configFor(root);
|
||||
var template = root.querySelector('[data-auth-modal-template]');
|
||||
var runtime = window.AksrtModalRuntime;
|
||||
if (!template) return;
|
||||
|
||||
var activeShell = null;
|
||||
var currentUuid = config.currentUuid || '';
|
||||
var currentStage = config.currentStage || '';
|
||||
var pollTimer = null;
|
||||
var pollBackoff = 0;
|
||||
var currentController = null;
|
||||
var refreshController = null;
|
||||
var refreshTimeout = null;
|
||||
var refreshInFlight = false;
|
||||
var openSequence = 0;
|
||||
var pendingCreateKey = '';
|
||||
var pendingCreateSignature = '';
|
||||
var pendingRefreshKey = '';
|
||||
var pendingManualKey = '';
|
||||
var pendingManualSignature = '';
|
||||
|
||||
function isOpen(shell) {
|
||||
return !!shell && shell.classList.contains('is-open') && document.contains(shell);
|
||||
}
|
||||
|
||||
function stopPolling() {
|
||||
if (pollTimer) window.clearTimeout(pollTimer);
|
||||
pollTimer = null;
|
||||
}
|
||||
|
||||
function abortCurrent() {
|
||||
if (currentController) {
|
||||
try { currentController.abort(); } catch (_) {}
|
||||
}
|
||||
currentController = null;
|
||||
}
|
||||
|
||||
function abortRefresh() {
|
||||
if (refreshTimeout) window.clearTimeout(refreshTimeout);
|
||||
refreshTimeout = null;
|
||||
if (refreshController) {
|
||||
try { refreshController.abort(); } catch (_) {}
|
||||
}
|
||||
refreshController = null;
|
||||
refreshInFlight = false;
|
||||
}
|
||||
|
||||
function setMessage(shell, message) {
|
||||
var node = shell && shell.querySelector('[data-auth-status-message]');
|
||||
if (node) node.textContent = message || '';
|
||||
}
|
||||
|
||||
function updateSummary(data) {
|
||||
if (!data || data.status !== 'verified') return;
|
||||
var trigger = root.querySelector('[data-auth-modal-open]');
|
||||
if (trigger) {
|
||||
trigger.disabled = true;
|
||||
trigger.textContent = '已认证';
|
||||
}
|
||||
runtime.invalidate(['identity']);
|
||||
document.dispatchEvent(new CustomEvent('aksrt:identity:changed', { detail: { status: data.status } }));
|
||||
}
|
||||
|
||||
function schedulePoll(delay) {
|
||||
stopPolling();
|
||||
if (!currentUuid || !isOpen(activeShell) || document.visibilityState !== 'visible') return;
|
||||
var base = Math.max(5, Number(delay || config.pollInterval || 5));
|
||||
var seconds = Math.min(60, base * Math.pow(2, Math.min(4, pollBackoff)));
|
||||
pollTimer = window.setTimeout(function () {
|
||||
pollTimer = null;
|
||||
refresh(false);
|
||||
}, seconds * 1000);
|
||||
}
|
||||
|
||||
function render(data, shell) {
|
||||
if (!shell || shell !== activeShell || !isOpen(shell)) return;
|
||||
data = data || {};
|
||||
var previousStage = currentStage;
|
||||
currentUuid = data.uuid || currentUuid;
|
||||
currentStage = data.stage || currentStage;
|
||||
if (data.stage && data.stage !== previousStage) runtime.invalidate(['identity']);
|
||||
var lock = ['created', 'provider_pending', 'provider_processing', 'manual_pending'].indexOf(currentStage) !== -1;
|
||||
var retryable = data.next_action === 'manual_or_retry';
|
||||
var statusBox = shell.querySelector('[data-auth-status]');
|
||||
var createForm = shell.querySelector('[data-auth-create]');
|
||||
var stageNode = shell.querySelector('[data-auth-stage]');
|
||||
var messageNode = shell.querySelector('[data-auth-status-message]');
|
||||
var continueLink = shell.querySelector('[data-auth-continue]');
|
||||
var manualButton = shell.querySelector('[data-auth-manual-open]');
|
||||
|
||||
if (statusBox) statusBox.hidden = !data.uuid && !retryable;
|
||||
if (createForm) createForm.hidden = lock || data.status === 'verified';
|
||||
if (stageNode) stageNode.textContent = data.stage || data.status || '准备认证';
|
||||
if (messageNode) messageNode.textContent = data.message || '认证结果以站点服务端主动查询为准。';
|
||||
if (continueLink) {
|
||||
var canContinue = data.next_action === 'poll' && data.redirect_url;
|
||||
continueLink.hidden = !canContinue;
|
||||
if (canContinue) continueLink.href = data.redirect_url;
|
||||
}
|
||||
if (manualButton) manualButton.hidden = !retryable;
|
||||
|
||||
if (data.status === 'verified' || data.next_action !== 'poll') {
|
||||
pollBackoff = 0;
|
||||
stopPolling();
|
||||
} else if (isOpen(shell) && document.visibilityState === 'visible') {
|
||||
schedulePoll(data.poll_interval || config.pollInterval);
|
||||
}
|
||||
updateSummary(data);
|
||||
}
|
||||
|
||||
function refresh(explicit) {
|
||||
if (!currentUuid || !isOpen(activeShell) || document.visibilityState !== 'visible' || refreshInFlight) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
stopPolling();
|
||||
abortRefresh();
|
||||
refreshInFlight = true;
|
||||
refreshController = window.AbortController ? new window.AbortController() : null;
|
||||
var timedOut = false;
|
||||
var shell = activeShell;
|
||||
var timeoutMs = Math.max(5000, Number(config.refreshTimeout || 15000));
|
||||
if (!pendingRefreshKey) pendingRefreshKey = uuid();
|
||||
refreshTimeout = window.setTimeout(function () {
|
||||
timedOut = true;
|
||||
if (refreshController) {
|
||||
try { refreshController.abort(); } catch (_) {}
|
||||
}
|
||||
}, timeoutMs);
|
||||
|
||||
return request(config.restBase + '/' + encodeURIComponent(currentUuid) + '/refresh', config, {
|
||||
method: 'POST',
|
||||
headers: { 'Idempotency-Key': pendingRefreshKey },
|
||||
signal: refreshController ? refreshController.signal : undefined,
|
||||
}).then(function (data) {
|
||||
pendingRefreshKey = '';
|
||||
if (shell !== activeShell || !isOpen(shell)) return data;
|
||||
pollBackoff = explicit ? 0 : Math.min(4, pollBackoff + 1);
|
||||
render(data, shell);
|
||||
return data;
|
||||
}).catch(function (error) {
|
||||
var aborted = error && (error.name === 'AbortError' || error.code === 'ABORT_ERR');
|
||||
if (shell !== activeShell || !isOpen(shell) || (aborted && !timedOut)) return null;
|
||||
setMessage(shell, timedOut ? '云端刷新超时,请稍后重试。' : errorMessage(error, '刷新失败,请稍后重试。'));
|
||||
pollBackoff = Math.min(4, pollBackoff + 1);
|
||||
schedulePoll(config.pollInterval || 5);
|
||||
return null;
|
||||
}).finally(function () {
|
||||
if (refreshTimeout) window.clearTimeout(refreshTimeout);
|
||||
refreshTimeout = null;
|
||||
refreshController = null;
|
||||
refreshInFlight = false;
|
||||
});
|
||||
}
|
||||
|
||||
function loadCurrent(shell, sequence) {
|
||||
abortCurrent();
|
||||
currentController = window.AbortController ? new window.AbortController() : null;
|
||||
var controller = currentController;
|
||||
return request(config.restBase + '/current', config, {
|
||||
method: 'GET',
|
||||
signal: controller ? controller.signal : undefined,
|
||||
}).then(function (snapshot) {
|
||||
if (sequence !== openSequence || shell !== activeShell || !isOpen(shell)) return;
|
||||
var data = snapshot && (snapshot.current || snapshot.active || snapshot.latest);
|
||||
if (!data) {
|
||||
currentUuid = '';
|
||||
currentStage = '';
|
||||
var statusBox = shell.querySelector('[data-auth-status]');
|
||||
var createForm = shell.querySelector('[data-auth-create]');
|
||||
if (statusBox) statusBox.hidden = true;
|
||||
if (createForm) createForm.hidden = false;
|
||||
stopPolling();
|
||||
return;
|
||||
}
|
||||
render(data, shell);
|
||||
}).catch(function (error) {
|
||||
var aborted = error && (error.name === 'AbortError' || error.code === 'ABORT_ERR');
|
||||
if (aborted || sequence !== openSequence || shell !== activeShell || !isOpen(shell)) return;
|
||||
setMessage(shell, errorMessage(error, '状态加载失败,请稍后重试。'));
|
||||
}).finally(function () {
|
||||
if (currentController === controller) currentController = null;
|
||||
});
|
||||
}
|
||||
|
||||
function bindForm(shell) {
|
||||
var createForm = shell.querySelector('[data-auth-create]');
|
||||
var manualForm = shell.querySelector('[data-auth-manual]');
|
||||
var statusBox = shell.querySelector('[data-auth-status]');
|
||||
var refreshButton = shell.querySelector('[data-auth-refresh]');
|
||||
var manualButton = shell.querySelector('[data-auth-manual-open]');
|
||||
var typeSelect = createForm && createForm.querySelector('[name="type"]');
|
||||
var enterprise = createForm && createForm.querySelector('[data-enterprise-fields]');
|
||||
var personLabel = createForm && createForm.querySelector('[data-auth-person-label]');
|
||||
var idLabel = createForm && createForm.querySelector('[data-auth-id-label]');
|
||||
|
||||
function syncEnterprise() {
|
||||
var enabled = typeSelect && typeSelect.value === 'enterprise';
|
||||
if (enterprise) enterprise.hidden = !enabled;
|
||||
if (enterprise) enterprise.querySelectorAll('input').forEach(function (input) { input.required = enabled; });
|
||||
if (personLabel) personLabel.textContent = enabled ? '法定代表人姓名' : '真实姓名';
|
||||
if (idLabel) idLabel.textContent = enabled ? '法定代表人身份证号' : '身份证号码';
|
||||
}
|
||||
|
||||
if (typeSelect) {
|
||||
typeSelect.addEventListener('change', syncEnterprise);
|
||||
syncEnterprise();
|
||||
}
|
||||
if (refreshButton) refreshButton.addEventListener('click', function () { refresh(true); });
|
||||
if (manualButton) manualButton.addEventListener('click', function () { if (manualForm) manualForm.hidden = false; });
|
||||
|
||||
if (createForm) createForm.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
var button = createForm.querySelector('[type="submit"]');
|
||||
var message = createForm.querySelector('[data-auth-message]');
|
||||
var form = new FormData(createForm);
|
||||
var payload = {};
|
||||
form.forEach(function (value, key) { payload[key] = value; });
|
||||
payload.consent = payload.consent === '1';
|
||||
var signature = JSON.stringify(payload);
|
||||
if (!pendingCreateKey || pendingCreateSignature !== signature) {
|
||||
pendingCreateKey = uuid();
|
||||
pendingCreateSignature = signature;
|
||||
}
|
||||
if (button) button.disabled = true;
|
||||
if (message) message.textContent = '';
|
||||
aliyunMetaInfo(config).then(function (metaInfo) {
|
||||
if (metaInfo) payload.meta_info = metaInfo;
|
||||
return request(config.restBase, config, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'Idempotency-Key': pendingCreateKey },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}).then(function (data) {
|
||||
pendingCreateKey = '';
|
||||
pendingCreateSignature = '';
|
||||
render(data, shell);
|
||||
}).catch(function (error) {
|
||||
if (message) message.textContent = errorMessage(error, '创建认证会话失败,请重试。');
|
||||
}).finally(function () {
|
||||
if (button) button.disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
if (manualForm) manualForm.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
if (!currentUuid) return;
|
||||
var files = manualForm.querySelector('[name="evidence[]"]');
|
||||
if (files && files.files.length > 3) {
|
||||
setMessage(shell, '证明材料最多上传 3 张');
|
||||
return;
|
||||
}
|
||||
var button = manualForm.querySelector('[type="submit"]');
|
||||
var body = new FormData();
|
||||
Array.prototype.forEach.call((files && files.files) || [], function (file) { body.append('evidence[]', file); });
|
||||
var signature = Array.prototype.map.call((files && files.files) || [], function (file) {
|
||||
return [file.name, file.size, file.lastModified].join(':');
|
||||
}).join('|');
|
||||
if (!pendingManualKey || pendingManualSignature !== signature) {
|
||||
pendingManualKey = uuid();
|
||||
pendingManualSignature = signature;
|
||||
}
|
||||
if (button) button.disabled = true;
|
||||
request(config.restBase + '/' + encodeURIComponent(currentUuid) + '/manual', config, {
|
||||
method: 'POST',
|
||||
headers: { 'Idempotency-Key': pendingManualKey },
|
||||
body: body,
|
||||
}).then(function (data) {
|
||||
pendingManualKey = '';
|
||||
pendingManualSignature = '';
|
||||
manualForm.hidden = true;
|
||||
render(data, shell);
|
||||
}).catch(function (error) {
|
||||
setMessage(shell, errorMessage(error, '人工审核提交失败,请重试。'));
|
||||
}).finally(function () {
|
||||
if (button) button.disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
if (statusBox && statusBox.hidden && createForm) createForm.hidden = false;
|
||||
}
|
||||
if (!template || !runtime || !window.AksrtRequest) return;
|
||||
|
||||
function open() {
|
||||
stopPolling();
|
||||
abortCurrent();
|
||||
abortRefresh();
|
||||
openSequence += 1;
|
||||
var sequence = openSequence;
|
||||
var shell = runtime.open({
|
||||
runtime.open({
|
||||
html: template.innerHTML,
|
||||
height: 320,
|
||||
kind: 'identity',
|
||||
className: 'aksrt-identity-modal-shell',
|
||||
mobileBottom: true,
|
||||
onContent: function (_, session) {
|
||||
activeShell = session.shell;
|
||||
bindForm(session.shell);
|
||||
var shell = session && session.shell;
|
||||
var form = shell && shell.querySelector('[data-auth-create]');
|
||||
var input = form && form.querySelector('[data-auth-evidence]');
|
||||
var preview = form && form.querySelector('[data-auth-preview]');
|
||||
if (!form || !input || !preview) return;
|
||||
shell.setAttribute('aria-labelledby', 'uc-auth-modal-title');
|
||||
|
||||
var selectedFiles = [];
|
||||
function renderPreview() {
|
||||
preview.replaceChildren();
|
||||
selectedFiles.forEach(function (entry, index) {
|
||||
var item = document.createElement('div');
|
||||
item.className = 'preview-item';
|
||||
var image = document.createElement('img');
|
||||
image.className = 'fit-cover';
|
||||
image.src = entry.url;
|
||||
image.alt = entry.file.name;
|
||||
var remove = document.createElement('button');
|
||||
remove.type = 'button';
|
||||
remove.className = 'uc-auth-preview-remove';
|
||||
remove.dataset.authRemove = String(index);
|
||||
remove.setAttribute('aria-label', '移除 ' + entry.file.name);
|
||||
remove.textContent = '×';
|
||||
item.appendChild(image);
|
||||
item.appendChild(remove);
|
||||
preview.appendChild(item);
|
||||
});
|
||||
|
||||
if (selectedFiles.length < 3) {
|
||||
var add = document.createElement('button');
|
||||
add.type = 'button';
|
||||
add.className = 'add';
|
||||
add.dataset.authFilePicker = '';
|
||||
add.setAttribute('aria-label', '选择证明材料图片');
|
||||
preview.appendChild(add);
|
||||
}
|
||||
}
|
||||
|
||||
function releasePreviewUrls() {
|
||||
selectedFiles.forEach(function (entry) { window.URL.revokeObjectURL(entry.url); });
|
||||
selectedFiles = [];
|
||||
}
|
||||
|
||||
function onModalClose(event) {
|
||||
if (!event.detail || event.detail.shell !== shell) return;
|
||||
document.removeEventListener('aksrt:modal:close', onModalClose);
|
||||
releasePreviewUrls();
|
||||
}
|
||||
|
||||
document.addEventListener('aksrt:modal:close', onModalClose);
|
||||
preview.addEventListener('click', function (event) {
|
||||
var remove = event.target.closest('[data-auth-remove]');
|
||||
if (remove) {
|
||||
var index = Number(remove.dataset.authRemove);
|
||||
var entry = selectedFiles[index];
|
||||
if (entry) window.URL.revokeObjectURL(entry.url);
|
||||
selectedFiles.splice(index, 1);
|
||||
renderPreview();
|
||||
return;
|
||||
}
|
||||
if (event.target.closest('[data-auth-file-picker]')) input.click();
|
||||
});
|
||||
input.addEventListener('change', function () {
|
||||
var files = Array.prototype.slice.call(input.files || []);
|
||||
input.value = '';
|
||||
if (!files.length) return;
|
||||
|
||||
var unsupported = files.some(function (file) { return !/^image\/(gif|jpe?g|png|webp)$/i.test(file.type); });
|
||||
if (unsupported) {
|
||||
toast('证明材料仅支持 JPG、PNG、GIF 或 WebP 图片', 'danger');
|
||||
return;
|
||||
}
|
||||
if (files.some(function (file) { return file.size > 2 * 1024 * 1024; })) {
|
||||
toast('单张证明材料不能超过 2MB', 'danger');
|
||||
return;
|
||||
}
|
||||
|
||||
var uniqueFiles = files.filter(function (file, index) {
|
||||
var key = [file.name, file.size, file.lastModified].join(':');
|
||||
return !selectedFiles.some(function (entry) { return entry.key === key; }) &&
|
||||
files.findIndex(function (candidate) {
|
||||
return [candidate.name, candidate.size, candidate.lastModified].join(':') === key;
|
||||
}) === index;
|
||||
});
|
||||
if (selectedFiles.length + uniqueFiles.length > 3) {
|
||||
toast('证明材料最多上传 3 张', 'danger');
|
||||
return;
|
||||
}
|
||||
|
||||
uniqueFiles.forEach(function (file) {
|
||||
selectedFiles.push({
|
||||
file: file,
|
||||
key: [file.name, file.size, file.lastModified].join(':'),
|
||||
url: window.URL.createObjectURL(file),
|
||||
});
|
||||
});
|
||||
renderPreview();
|
||||
});
|
||||
|
||||
var header = shell.querySelector('.uc-auth-modal__header');
|
||||
if (header) {
|
||||
var touchStartY = null;
|
||||
header.addEventListener('touchstart', function (event) {
|
||||
touchStartY = event.touches.length ? event.touches[0].clientY : null;
|
||||
}, { passive: true });
|
||||
header.addEventListener('touchend', function (event) {
|
||||
var touchEndY = event.changedTouches.length ? event.changedTouches[0].clientY : null;
|
||||
if (touchStartY !== null && touchEndY !== null && touchEndY - touchStartY > 60 &&
|
||||
runtime.getActive && runtime.getActive() === shell) {
|
||||
runtime.close('swipe');
|
||||
}
|
||||
touchStartY = null;
|
||||
}, { passive: true });
|
||||
}
|
||||
|
||||
form.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
var invalidField = form.querySelector(':invalid');
|
||||
if (!form.checkValidity()) {
|
||||
toast((invalidField && invalidField.validationMessage) || '请检查申请信息是否填写完整', 'danger');
|
||||
if (invalidField && typeof invalidField.focus === 'function') invalidField.focus();
|
||||
return;
|
||||
}
|
||||
var button = form.querySelector('[type="submit"]');
|
||||
var formData = new FormData(form);
|
||||
selectedFiles.forEach(function (entry) {
|
||||
formData.append('evidence[]', entry.file, entry.file.name);
|
||||
});
|
||||
var submitLabel = button ? button.innerHTML : '';
|
||||
if (button) {
|
||||
button.disabled = true;
|
||||
button.innerHTML = '<i class="fa fas fa-spinner fa-spin mr6" aria-hidden="true"></i>正在提交…';
|
||||
}
|
||||
toast('正在提交申请…', 'load', '', 'aksrt_identity_submit');
|
||||
window.AksrtRequest.request(config.restBase, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'X-WP-Nonce': config.nonce || '' },
|
||||
body: formData,
|
||||
}).then(function (response) {
|
||||
var result = parseResponse(response);
|
||||
if (!result || result.success === false) throw new Error((result && result.message) || '申请提交失败,请稍后重试');
|
||||
toast('申请已提交,请耐心等待管理员审核。', 'success', '', 'aksrt_identity_submit');
|
||||
var summary = root.querySelector('[data-auth-summary]');
|
||||
if (summary) {
|
||||
summary.replaceChildren();
|
||||
var pending = document.createElement('div');
|
||||
pending.className = 'c-red mb10';
|
||||
pending.textContent = '您的申请正在审核中,请耐心等待';
|
||||
var name = document.createElement('div');
|
||||
name.textContent = result.name || '';
|
||||
summary.appendChild(pending);
|
||||
summary.appendChild(name);
|
||||
if (result.description) {
|
||||
var description = document.createElement('div');
|
||||
description.className = 'muted-2-color mt10';
|
||||
description.textContent = result.description;
|
||||
summary.appendChild(description);
|
||||
}
|
||||
if (result.message) {
|
||||
var applicationMessage = document.createElement('div');
|
||||
applicationMessage.className = 'muted-2-color mt10';
|
||||
applicationMessage.textContent = '申请说明:' + result.message;
|
||||
summary.appendChild(applicationMessage);
|
||||
}
|
||||
(Array.isArray(result.evidence) ? result.evidence : []).forEach(function (item) {
|
||||
if (!item || !item.url || !item.thumbnail_url) return;
|
||||
var link = document.createElement('a');
|
||||
link.href = item.url;
|
||||
link.target = '_blank';
|
||||
link.rel = 'noopener noreferrer';
|
||||
var image = document.createElement('img');
|
||||
image.src = item.thumbnail_url;
|
||||
image.alt = '身份认证证明材料';
|
||||
image.width = 64;
|
||||
image.height = 64;
|
||||
image.style.objectFit = 'cover';
|
||||
image.style.borderRadius = '4px';
|
||||
link.appendChild(image);
|
||||
summary.appendChild(link);
|
||||
});
|
||||
}
|
||||
if (runtime.getActive && runtime.getActive() === shell) runtime.close('submit');
|
||||
}).catch(function (error) {
|
||||
toast(errorMessage(error, '申请提交失败,请稍后重试'), 'danger', '', 'aksrt_identity_submit');
|
||||
}).finally(function () {
|
||||
if (button) {
|
||||
button.disabled = false;
|
||||
button.innerHTML = submitLabel;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
activeShell = shell;
|
||||
loadCurrent(shell, sequence);
|
||||
}
|
||||
|
||||
root.querySelectorAll('[data-auth-modal-open]').forEach(function (button) {
|
||||
@@ -367,27 +238,6 @@
|
||||
open();
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('visibilitychange', function () {
|
||||
if (!isOpen(activeShell)) return;
|
||||
if (document.visibilityState !== 'visible') {
|
||||
stopPolling();
|
||||
abortCurrent();
|
||||
abortRefresh();
|
||||
return;
|
||||
}
|
||||
if (currentUuid && ['provider_pending', 'provider_processing'].indexOf(currentStage) !== -1) {
|
||||
schedulePoll(config.pollInterval || 5);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('aksrt:modal:close', function (event) {
|
||||
if (!activeShell || !event.detail || event.detail.shell !== activeShell) return;
|
||||
stopPolling();
|
||||
abortCurrent();
|
||||
abortRefresh();
|
||||
activeShell = null;
|
||||
});
|
||||
}
|
||||
|
||||
window.AKSRTUserCenterAuth = { init: init };
|
||||
|
||||
+471
-313
@@ -40,6 +40,11 @@
|
||||
if (window.AKSRTCaptcha) window.AKSRTCaptcha.reset(scene, scope);
|
||||
}
|
||||
|
||||
function setSettingsSubmitLabel(button, text) {
|
||||
var label = button && button.querySelector('[data-aksrt-settings-submit-label]');
|
||||
if (label) label.textContent = text;
|
||||
}
|
||||
|
||||
/* ==================== Dashboard 导航 ==================== */
|
||||
function initDashboardNavigation() {
|
||||
if (document.documentElement.dataset.aksrtUserCenterNavigationInitialized === 'true') return;
|
||||
@@ -344,7 +349,7 @@
|
||||
}
|
||||
|
||||
/* ==================== 个人信息保存 ==================== */
|
||||
|
||||
|
||||
|
||||
function initProfileForm(root) {
|
||||
root = root || document;
|
||||
@@ -709,236 +714,493 @@
|
||||
if (!ajaxUrl || !settingsNonce) return;
|
||||
root = root || document;
|
||||
|
||||
function openModal(id) {
|
||||
var m = document.getElementById(id);
|
||||
if (!m) return;
|
||||
m.hidden = false;
|
||||
m.classList.add('is-open');
|
||||
document.body.style.overflow = 'hidden';
|
||||
var firstInput = m.querySelector('input:not([type=hidden])');
|
||||
if (firstInput) setTimeout(function() { firstInput.focus(); }, 200);
|
||||
function openSettingsModal(kind) {
|
||||
var runtime = window.AksrtModalRuntime;
|
||||
var template = document.querySelector('template[data-aksrt-settings-modal-template="' + kind + '"]');
|
||||
if (!runtime || typeof runtime.open !== 'function' || !template) return;
|
||||
runtime.open({
|
||||
html: template.innerHTML,
|
||||
kind: 'account-settings',
|
||||
className: 'aksrt-user-settings-modal modal-mini full-sm',
|
||||
mobileBottom: true,
|
||||
onContent: function (_, session) {
|
||||
var shell = session && session.shell;
|
||||
if (!shell) return;
|
||||
var dialog = shell.querySelector('[data-account-dialog]');
|
||||
var title = dialog && dialog.querySelector('[data-account-password-title], .modal-colorful-header .text-center > div:last-child');
|
||||
if (title && title.textContent.trim()) shell.setAttribute('aria-label', title.textContent.trim());
|
||||
bindDialog(shell);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function closeModal(m) {
|
||||
if (typeof m === 'string') m = document.getElementById(m);
|
||||
if (!m) return;
|
||||
m.classList.remove('is-open');
|
||||
m.hidden = true;
|
||||
document.body.style.overflow = '';
|
||||
var f = m.querySelector('form');
|
||||
var sceneMap = { 'pwd-modal': 'change_password', 'email-modal': 'change_email', 'phone-modal': 'bind_phone' };
|
||||
var scene = m.id && sceneMap[m.id] ? sceneMap[m.id] : '';
|
||||
if (f) {
|
||||
f.reset();
|
||||
var bar = document.getElementById('pwd-strength-bar');
|
||||
var txt = document.getElementById('pwd-strength-text');
|
||||
if (bar && bar.firstChild) { bar.style.width = '0'; bar.firstChild.className = 'pwd-strength__fill'; }
|
||||
if (txt) { txt.textContent = ''; txt.className = 'pwd-strength__text'; }
|
||||
function setStep(dialog, key) {
|
||||
dialog.querySelectorAll('[data-account-step]').forEach(function (panel) {
|
||||
panel.classList.toggle('is-active', panel.getAttribute('data-account-step') === key);
|
||||
});
|
||||
var panels = Array.prototype.slice.call(dialog.querySelectorAll('[data-account-step]'));
|
||||
var index = Math.max(0, panels.findIndex(function (panel) { return panel.getAttribute('data-account-step') === key; }));
|
||||
dialog.querySelectorAll('.account-step-list li').forEach(function (item, itemIndex) {
|
||||
item.classList.toggle('active', itemIndex === index);
|
||||
item.classList.toggle('done', itemIndex < index);
|
||||
});
|
||||
}
|
||||
|
||||
function resetCountdown(button) {
|
||||
if (!button) return;
|
||||
if (button.__aksrtCountdownTimer) window.clearInterval(button.__aksrtCountdownTimer);
|
||||
button.__aksrtCountdownTimer = null;
|
||||
button.disabled = false;
|
||||
button.classList.remove('is-counting');
|
||||
button.textContent = button.getAttribute('data-idle-label') || '发送验证码';
|
||||
}
|
||||
|
||||
function startCountdown(button, seconds) {
|
||||
var idleText = button.textContent;
|
||||
var left = Number(seconds || 60);
|
||||
if (button.__aksrtCountdownTimer) window.clearInterval(button.__aksrtCountdownTimer);
|
||||
button.disabled = true;
|
||||
button.classList.add('is-counting');
|
||||
button.setAttribute('data-idle-label', idleText);
|
||||
button.textContent = left + 's';
|
||||
button.__aksrtCountdownTimer = window.setInterval(function () {
|
||||
left -= 1;
|
||||
if (left <= 0) { resetCountdown(button); return; }
|
||||
button.textContent = left + 's';
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function settingsRestRoot() {
|
||||
return String((window.aksrtData && (window.aksrtData.restUrl || window.aksrtData.apiUrl)) || cfg.restUrl || '/wp-json/aksrt/v1').replace(/\/$/, '');
|
||||
}
|
||||
|
||||
function settingsRestNonce() {
|
||||
return (window.aksrtData && (window.aksrtData.restNonce || window.aksrtData.nonce)) || cfg.restNonce || '';
|
||||
}
|
||||
|
||||
function formHasRequiredValues(form) {
|
||||
var required = form.querySelectorAll('[required]');
|
||||
for (var i = 0; i < required.length; i++) {
|
||||
if (required[i].type === 'checkbox' && !required[i].checked) {
|
||||
required[i].focus();
|
||||
notyf(i18n.agreeTerms || '请先同意相关协议', 'warning');
|
||||
return false;
|
||||
}
|
||||
if (required[i].type !== 'checkbox' && !String(required[i].value || '').trim()) {
|
||||
required[i].focus();
|
||||
notyf(i18n.fillRequired || '请填写必填项', 'warning');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (scene) resetCaptcha(scene, f || m);
|
||||
return true;
|
||||
}
|
||||
|
||||
root.querySelectorAll('.security-trigger').forEach(function(btn) {
|
||||
if (btn.dataset.aksrtSettingsInitialized === 'true') return;
|
||||
btn.dataset.aksrtSettingsInitialized = 'true';
|
||||
btn.addEventListener('click', function() { openModal(this.getAttribute('data-modal')); });
|
||||
});
|
||||
root.querySelectorAll('[data-close-modal], [data-modal-close]').forEach(function(el) {
|
||||
if (el.dataset.aksrtSettingsInitialized === 'true') return;
|
||||
el.dataset.aksrtSettingsInitialized = 'true';
|
||||
el.addEventListener('click', function() { closeModal(this.closest('.aksrt-modal')); });
|
||||
});
|
||||
if (document.documentElement.dataset.aksrtSettingsKeyboardInitialized !== 'true') {
|
||||
document.documentElement.dataset.aksrtSettingsKeyboardInitialized = 'true';
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
var openM = document.querySelector('.aksrt-modal.is-open');
|
||||
if (openM) closeModal(openM);
|
||||
function sendEmailCode(button, form, scene) {
|
||||
if (!form || button.disabled) return;
|
||||
if (scene === 'change_email' && !String((form.querySelector('[name="new_email"]') || {}).value || '').trim()) {
|
||||
notyf('请输入邮箱', 'warning');
|
||||
return;
|
||||
}
|
||||
runCaptcha('change_email', form).then(function (captchaPayload) {
|
||||
if (captchaPayload === null) return null;
|
||||
var data = new URLSearchParams();
|
||||
data.set('action', 'aksrt_send_account_email_code');
|
||||
data.set('_settings_nonce', settingsNonce);
|
||||
data.set('scene', scene);
|
||||
new FormData(form).forEach(function (value, key) { if (value !== '' && !data.has(key)) data.set(key, value); });
|
||||
Object.keys(captchaPayload || {}).forEach(function (key) { data.set(key, captchaPayload[key]); });
|
||||
button.disabled = true;
|
||||
return fetch(ajaxUrl, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: data.toString() });
|
||||
}).then(function (response) { return response ? response.json() : null; }).then(function (payload) {
|
||||
if (!payload) return;
|
||||
if (!payload.success) throw new Error((payload.data && payload.data.message) || '发送失败');
|
||||
notyf((payload.data && payload.data.message) || '验证码已发送', 'success');
|
||||
startCountdown(button, payload.data && payload.data.countdown);
|
||||
}).catch(function (error) {
|
||||
resetCountdown(button);
|
||||
notyf(error.message || '发送失败', 'danger');
|
||||
}).finally(function () { resetCaptcha('change_email', form); });
|
||||
}
|
||||
|
||||
function sendPhoneCode(button, form, stage) {
|
||||
if (!form || button.disabled) return;
|
||||
var payload = { stage: stage };
|
||||
if (stage === 'verify_old_phone') {
|
||||
var dialog = form.closest('[data-account-dialog="phone"]');
|
||||
var boxes = dialog ? phoneDigitBoxes(dialog) : [];
|
||||
if (boxes.length) {
|
||||
var hiddenLength = parseInt(dialog.querySelector('[data-aksrt-phone-digits]').getAttribute('data-hidden-length'), 10) || boxes.length;
|
||||
var middle = phoneDigitsValue(dialog);
|
||||
if (middle.length !== hiddenLength) { notyf('请先补全手机号缺失的数字', 'warning'); return; }
|
||||
payload.middle = middle;
|
||||
}
|
||||
} else {
|
||||
var phone = String((form.querySelector('[name="phone"]') || {}).value || '').trim();
|
||||
if (!phone) { notyf('请输入手机号', 'warning'); return; }
|
||||
payload.phone = phone;
|
||||
}
|
||||
runCaptcha('bind_phone', form).then(function (captchaPayload) {
|
||||
if (captchaPayload === null) return null;
|
||||
var restRoot = settingsRestRoot();
|
||||
var nonce = settingsRestNonce();
|
||||
Object.keys(captchaPayload || {}).forEach(function (key) { payload[key] = captchaPayload[key]; });
|
||||
var request = fetch(restRoot + '/user/phone-code', {
|
||||
method: 'POST', credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
button.disabled = true;
|
||||
return request;
|
||||
}).then(function (response) { return response ? response.json().then(function (payload) { return { ok: response.ok, payload: payload }; }) : null; }).then(function (result) {
|
||||
if (!result) return;
|
||||
if (!result.ok || !result.payload.success) throw new Error(result.payload.message || '发送失败');
|
||||
notyf(result.payload.message || '验证码已发送', 'success');
|
||||
startCountdown(button, result.payload.data && result.payload.data.countdown);
|
||||
}).catch(function (error) {
|
||||
resetCountdown(button);
|
||||
notyf(error.message || '发送失败', 'danger');
|
||||
}).finally(function () { resetCaptcha('bind_phone', form); });
|
||||
}
|
||||
|
||||
function postAjaxForm(form, action, scene, success) {
|
||||
if (!formHasRequiredValues(form)) return;
|
||||
var button = form.querySelector('[type="submit"]');
|
||||
var label = button && button.querySelector('[data-aksrt-settings-submit-label]');
|
||||
var original = label ? label.textContent : '';
|
||||
if (button) { button.disabled = true; setSettingsSubmitLabel(button, i18n.submitting || '提交中...'); }
|
||||
runCaptcha(scene, form).then(function (captchaPayload) {
|
||||
if (captchaPayload === null) {
|
||||
if (button) { button.disabled = false; setSettingsSubmitLabel(button, original); }
|
||||
return null;
|
||||
}
|
||||
var data = new FormData(form);
|
||||
data.set('action', action);
|
||||
data.set('_settings_nonce', settingsNonce);
|
||||
Object.keys(captchaPayload || {}).forEach(function (key) { data.set(key, captchaPayload[key]); });
|
||||
return fetch(ajaxUrl, { method: 'POST', credentials: 'same-origin', body: data });
|
||||
}).then(function (response) { return response ? response.json() : null; }).then(function (payload) {
|
||||
if (!payload) return;
|
||||
if (button) { button.disabled = false; setSettingsSubmitLabel(button, original); }
|
||||
if (!payload.success) throw new Error((payload.data && payload.data.message) || '操作失败');
|
||||
notyf((payload.data && payload.data.message) || '操作成功', 'success');
|
||||
if (typeof success === 'function') success(payload);
|
||||
}).catch(function (error) {
|
||||
if (button) { button.disabled = false; setSettingsSubmitLabel(button, original); }
|
||||
notyf(error.message || '操作失败', 'danger');
|
||||
}).finally(function () { resetCaptcha(scene, form); });
|
||||
}
|
||||
|
||||
function postPhoneForm(form, endpoint, success) {
|
||||
if (!formHasRequiredValues(form)) return;
|
||||
var button = form.querySelector('[type="submit"]');
|
||||
var label = button && button.querySelector('[data-aksrt-settings-submit-label]');
|
||||
var original = label ? label.textContent : '';
|
||||
runCaptcha('bind_phone', form).then(function (captchaPayload) {
|
||||
if (captchaPayload === null) return null;
|
||||
var restRoot = settingsRestRoot();
|
||||
var nonce = settingsRestNonce();
|
||||
if (button) { button.disabled = true; setSettingsSubmitLabel(button, i18n.submitting || '提交中...'); }
|
||||
var payload = {};
|
||||
new FormData(form).forEach(function (value, key) { payload[key] = value; });
|
||||
Object.keys(captchaPayload || {}).forEach(function (key) { payload[key] = captchaPayload[key]; });
|
||||
return fetch(restRoot + endpoint, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce }, body: JSON.stringify(payload) });
|
||||
}).then(function (response) { return response ? response.json().then(function (payload) { return { ok: response.ok, payload: payload }; }) : null; }).then(function (result) {
|
||||
if (!result) return;
|
||||
if (!result.ok || !result.payload.success) throw new Error(result.payload.message || '操作失败');
|
||||
if (button) { button.disabled = false; setSettingsSubmitLabel(button, original); }
|
||||
notyf(result.payload.message || '操作成功', 'success');
|
||||
if (typeof success === 'function') success(result.payload);
|
||||
}).catch(function (error) {
|
||||
if (button) { button.disabled = false; setSettingsSubmitLabel(button, original); }
|
||||
notyf(error.message || '操作失败', 'danger');
|
||||
}).finally(function () { resetCaptcha('bind_phone', form); });
|
||||
}
|
||||
|
||||
/* ---- 修改手机:脱敏中间位数字框(格数随号码长度变化) ---- */
|
||||
function phoneDigitBoxes(dialog) {
|
||||
return Array.prototype.slice.call(dialog.querySelectorAll('[data-aksrt-phone-digits] .aksrt-phone-digit'));
|
||||
}
|
||||
|
||||
function phoneDigitsValue(dialog) {
|
||||
return phoneDigitBoxes(dialog).map(function (input) { return String(input.value || '').replace(/\D/g, ''); }).join('');
|
||||
}
|
||||
|
||||
function setPhoneDigitsError(dialog, message) {
|
||||
var group = dialog.querySelector('[data-aksrt-phone-digits]');
|
||||
var error = dialog.querySelector('[data-aksrt-phone-digits-error]');
|
||||
if (error) {
|
||||
error.textContent = message || '';
|
||||
error.hidden = !message;
|
||||
}
|
||||
if (group) group.classList.toggle('has-error', !!message);
|
||||
}
|
||||
|
||||
function hidePhoneCodeArea(dialog, options) {
|
||||
var area = dialog.querySelector('[data-aksrt-phone-code-area]');
|
||||
if (!area) return;
|
||||
var wasVisible = !area.hidden;
|
||||
area.hidden = true;
|
||||
area.querySelectorAll('.account-code-send,[data-aksrt-sms-trigger]').forEach(resetCountdown);
|
||||
if (wasVisible && options && options.destroyCode) {
|
||||
// 中间位被修改:销毁服务端已发放的老号验证码状态。
|
||||
fetch(settingsRestRoot() + '/user/phone-code', {
|
||||
method: 'POST', credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': settingsRestNonce() },
|
||||
body: JSON.stringify({ stage: 'reset_old_phone' })
|
||||
}).catch(function () {});
|
||||
}
|
||||
}
|
||||
|
||||
function verifyPhoneMiddle(dialog) {
|
||||
var group = dialog.querySelector('[data-aksrt-phone-digits]');
|
||||
var boxes = phoneDigitBoxes(dialog);
|
||||
if (!group || !boxes.length) return;
|
||||
var hiddenLength = parseInt(group.getAttribute('data-hidden-length'), 10) || boxes.length;
|
||||
var middle = phoneDigitsValue(dialog);
|
||||
if (middle.length !== hiddenLength) return;
|
||||
|
||||
dialog.__aksrtPhoneMatchTicket = (dialog.__aksrtPhoneMatchTicket || 0) + 1;
|
||||
var ticket = dialog.__aksrtPhoneMatchTicket;
|
||||
fetch(settingsRestRoot() + '/user/phone-match', {
|
||||
method: 'POST', credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': settingsRestNonce() },
|
||||
body: JSON.stringify({ middle: middle })
|
||||
}).then(function (response) { return response.json().then(function (payload) { return { ok: response.ok, payload: payload }; }); }).then(function (result) {
|
||||
if (ticket !== dialog.__aksrtPhoneMatchTicket) return;
|
||||
if (result.ok && result.payload.success) {
|
||||
var area = dialog.querySelector('[data-aksrt-phone-code-area]');
|
||||
if (area) area.hidden = false;
|
||||
setPhoneDigitsError(dialog, '');
|
||||
} else {
|
||||
hidePhoneCodeArea(dialog, { destroyCode: true });
|
||||
setPhoneDigitsError(dialog, (result.payload && result.payload.message) || '填写的号码与绑定手机号不一致');
|
||||
}
|
||||
}).catch(function () {
|
||||
if (ticket === dialog.__aksrtPhoneMatchTicket) {
|
||||
hidePhoneCodeArea(dialog, { destroyCode: false });
|
||||
setPhoneDigitsError(dialog, '网络错误,请重试');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 密码显隐切换
|
||||
root.querySelectorAll('#pwd-modal .toggle-pwd, #email-modal .toggle-pwd').forEach(function(btn) {
|
||||
if (btn.dataset.aksrtSettingsInitialized === 'true') return;
|
||||
btn.dataset.aksrtSettingsInitialized = 'true';
|
||||
btn.addEventListener('click', function() {
|
||||
var inp = document.getElementById(this.getAttribute('data-target'));
|
||||
if (!inp) return;
|
||||
inp.type = (inp.type === 'password') ? 'text' : 'password';
|
||||
this.classList.toggle('is-visible');
|
||||
});
|
||||
});
|
||||
function initPhoneDigits(dialog) {
|
||||
var boxes = phoneDigitBoxes(dialog);
|
||||
if (!boxes.length || dialog.dataset.aksrtDigitsBound === 'true') return;
|
||||
dialog.dataset.aksrtDigitsBound = 'true';
|
||||
|
||||
// 密码强度
|
||||
var pInput = document.getElementById('pwd-new');
|
||||
var sBar = document.getElementById('pwd-strength-bar');
|
||||
var sTxt = document.getElementById('pwd-strength-text');
|
||||
if (pInput && sBar && sTxt && pInput.dataset.aksrtSettingsInitialized !== 'true') {
|
||||
pInput.dataset.aksrtSettingsInitialized = 'true';
|
||||
pInput.addEventListener('input', function() {
|
||||
var v = this.value;
|
||||
var f = sBar.querySelector('.pwd-strength__fill');
|
||||
if (!v) { f.className = 'pwd-strength__fill'; sTxt.className = 'pwd-strength__text'; sTxt.textContent = ''; return; }
|
||||
var s = 0;
|
||||
if (v.length >= 6) s++;
|
||||
if (v.length >= 10) s++;
|
||||
if (/[a-z]/.test(v) && /[A-Z]/.test(v)) s++;
|
||||
if (/\d/.test(v)) s++;
|
||||
if (/[^a-zA-Z0-9]/.test(v)) s++;
|
||||
var lv = '', tx = '';
|
||||
if (s <= 1) { lv = 'weak'; tx = '弱'; }
|
||||
else if (s <= 3) { lv = 'medium'; tx = '中'; }
|
||||
else { lv = 'strong'; tx = '强'; }
|
||||
f.className = 'pwd-strength__fill level-' + lv;
|
||||
sTxt.className = 'pwd-strength__text level-' + lv;
|
||||
sTxt.textContent = '密码强度: ' + tx;
|
||||
boxes.forEach(function (input, index) {
|
||||
input.addEventListener('input', function () {
|
||||
var value = String(input.value || '').replace(/\D/g, '');
|
||||
input.value = value === '' ? '' : value.charAt(value.length - 1);
|
||||
hidePhoneCodeArea(dialog, { destroyCode: true });
|
||||
setPhoneDigitsError(dialog, '');
|
||||
if (input.value !== '' && index < boxes.length - 1) boxes[index + 1].focus();
|
||||
verifyPhoneMiddle(dialog);
|
||||
});
|
||||
input.addEventListener('keydown', function (event) {
|
||||
if (event.key === 'Backspace' && !input.value && index > 0) {
|
||||
event.preventDefault();
|
||||
boxes[index - 1].value = '';
|
||||
boxes[index - 1].focus();
|
||||
hidePhoneCodeArea(dialog, { destroyCode: true });
|
||||
setPhoneDigitsError(dialog, '');
|
||||
} else if (event.key === 'ArrowLeft' && index > 0) {
|
||||
boxes[index - 1].focus();
|
||||
} else if (event.key === 'ArrowRight' && index < boxes.length - 1) {
|
||||
boxes[index + 1].focus();
|
||||
}
|
||||
});
|
||||
input.addEventListener('paste', function (event) {
|
||||
var text = String((event.clipboardData || window.clipboardData).getData('text') || '');
|
||||
var digits = text.replace(/\D/g, '');
|
||||
if (!digits) return;
|
||||
event.preventDefault();
|
||||
for (var offset = 0; offset < boxes.length - index && offset < digits.length; offset++) {
|
||||
boxes[index + offset].value = digits.charAt(offset);
|
||||
}
|
||||
boxes[Math.min(index + digits.length, boxes.length - 1)].focus();
|
||||
hidePhoneCodeArea(dialog, { destroyCode: true });
|
||||
setPhoneDigitsError(dialog, '');
|
||||
verifyPhoneMiddle(dialog);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 验证码发送
|
||||
function getSendBtnIdleText(btnId) {
|
||||
if (btnId === 'pwd-send-code') return i18n.sendToCurrentEmail || '发送到当前邮箱';
|
||||
if (btnId === 'email-send-code') return i18n.sendToNewEmail || '发送到新邮箱';
|
||||
return i18n.sendCode || '发送验证码';
|
||||
function sendRecoveryCode(button, form) {
|
||||
if (!form || button.disabled) return;
|
||||
var target = String((form.querySelector('[name="target"]') || {}).value || '').trim();
|
||||
if (!target) { notyf('请输入邮箱或手机号', 'warning'); return; }
|
||||
var dialog = form.closest('[data-account-dialog="password"]');
|
||||
button.disabled = true;
|
||||
runCaptcha('lostpassword', form).then(function (captchaPayload) {
|
||||
if (captchaPayload === null) { notyf('请先完成人机验证', 'warning'); resetCountdown(button); return null; }
|
||||
var payload = { scene: 'resetpassword', target: target, channel: dialog ? (dialog.getAttribute('data-recovery-channel') || '') : '' };
|
||||
Object.keys(captchaPayload || {}).forEach(function (key) { payload[key] = captchaPayload[key]; });
|
||||
return fetch(settingsRestRoot() + '/auth/code', {
|
||||
method: 'POST', credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': settingsRestNonce() },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
}).then(function (response) { return response ? response.json().then(function (payload) { return { ok: response.ok, payload: payload }; }) : null; }).then(function (result) {
|
||||
if (!result) return;
|
||||
if (!result.ok || !result.payload.success) throw new Error(result.payload.message || '发送失败');
|
||||
notyf((result.payload.data && result.payload.data.message) || result.payload.message || '验证码已发送', 'success');
|
||||
startCountdown(button, (result.payload.data && result.payload.data.countdown) || result.payload.countdown);
|
||||
}).catch(function (error) {
|
||||
resetCountdown(button);
|
||||
notyf(error.message || '发送失败', 'danger');
|
||||
}).finally(function () { resetCaptcha('lostpassword', form); });
|
||||
}
|
||||
|
||||
function bindSend(btnId, formId, scene) {
|
||||
var b = document.getElementById(btnId);
|
||||
if (!b) return;
|
||||
if (b.dataset.aksrtSettingsInitialized === 'true') return;
|
||||
b.dataset.aksrtSettingsInitialized = 'true';
|
||||
b.addEventListener('click', function() {
|
||||
if (this.classList.contains('is-counting')) return;
|
||||
var self = this;
|
||||
var form = document.getElementById(formId);
|
||||
var toastId = scene === 'change_password'
|
||||
? 'aksrt-settings-password-code-toast'
|
||||
: 'aksrt-settings-email-code-toast';
|
||||
runCaptcha(scene, form).then(function(captchaPayload) {
|
||||
if (captchaPayload === null) return;
|
||||
self.disabled = true;
|
||||
self.textContent = i18n.sending || '发送中...';
|
||||
notyf(i18n.sending || '正在发送验证码…', 'load', '', toastId);
|
||||
var data = new URLSearchParams();
|
||||
data.set('action', 'aksrt_send_email_code');
|
||||
data.set('_settings_nonce', settingsNonce);
|
||||
data.set('scene', scene || '');
|
||||
if (form) {
|
||||
new FormData(form).forEach(function(value, key) {
|
||||
if (data.has(key) || value === '' || value == null) return;
|
||||
data.set(key, value);
|
||||
});
|
||||
}
|
||||
Object.keys(captchaPayload || {}).forEach(function(key) { data.set(key, captchaPayload[key]); });
|
||||
fetch(ajaxUrl, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: data.toString() })
|
||||
.then(function(r) { return r.json(); }).then(function(res) {
|
||||
if (res.success) {
|
||||
notyf((res.data && res.data.message) || i18n.codeSent || '验证码已发送', 'success', 5, toastId);
|
||||
var sec = 60;
|
||||
self.classList.add('is-counting');
|
||||
self.textContent = sec + 's 后重发';
|
||||
var t = setInterval(function() {
|
||||
sec--;
|
||||
if (sec <= 0) {
|
||||
clearInterval(t);
|
||||
self.disabled = false;
|
||||
self.classList.remove('is-counting');
|
||||
self.textContent = getSendBtnIdleText(btnId);
|
||||
} else {
|
||||
self.textContent = sec + 's 后重发';
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
notyf((res.data && res.data.message) || i18n.sendFailed || '发送失败', 'danger', 5, toastId);
|
||||
self.disabled = false;
|
||||
self.textContent = getSendBtnIdleText(btnId);
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
notyf(i18n.networkError || '网络错误', 'danger', 5, toastId);
|
||||
self.disabled = false;
|
||||
self.textContent = getSendBtnIdleText(btnId);
|
||||
})
|
||||
.finally(function() {
|
||||
resetCaptcha(scene, form);
|
||||
function postRecoveryForm(form) {
|
||||
if (!formHasRequiredValues(form)) return;
|
||||
var password = form.querySelector('[name="password"]');
|
||||
var confirm = form.querySelector('[name="password_confirm"]');
|
||||
if (password && confirm && password.value !== confirm.value) { notyf('两次密码输入不一致', 'warning'); confirm.focus(); return; }
|
||||
var button = form.querySelector('[type="submit"]');
|
||||
var label = button && button.querySelector('[data-aksrt-settings-submit-label]');
|
||||
var original = label ? label.textContent : '';
|
||||
var dialog = form.closest('[data-account-dialog="password"]');
|
||||
if (button) { button.disabled = true; setSettingsSubmitLabel(button, i18n.submitting || '提交中...'); }
|
||||
runCaptcha('lostpassword', form).then(function (captchaPayload) {
|
||||
if (captchaPayload === null) {
|
||||
if (button) { button.disabled = false; setSettingsSubmitLabel(button, original); }
|
||||
return null;
|
||||
}
|
||||
var payload = {};
|
||||
new FormData(form).forEach(function (value, key) { payload[key] = value; });
|
||||
payload.channel = dialog ? (dialog.getAttribute('data-recovery-channel') || '') : '';
|
||||
Object.keys(captchaPayload || {}).forEach(function (key) { payload[key] = captchaPayload[key]; });
|
||||
return fetch(settingsRestRoot() + '/auth/reset-password', {
|
||||
method: 'POST', credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': settingsRestNonce() },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
}).then(function (response) { return response ? response.json().then(function (payload) { return { ok: response.ok, payload: payload }; }) : null; }).then(function (result) {
|
||||
if (!result) return;
|
||||
if (!result.ok || !result.payload.success) throw new Error(result.payload.message || '重设密码失败');
|
||||
if (button) { button.disabled = false; setSettingsSubmitLabel(button, original); }
|
||||
notyf(result.payload.message || (result.payload.data && result.payload.data.message) || '密码已重置', 'success');
|
||||
window.setTimeout(function () {
|
||||
var redirect = result.payload.redirect || (result.payload.data && result.payload.data.redirect);
|
||||
if (redirect) window.location.assign(redirect);
|
||||
else window.location.reload();
|
||||
}, 700);
|
||||
}).catch(function (error) {
|
||||
if (button) { button.disabled = false; setSettingsSubmitLabel(button, original); }
|
||||
notyf(error.message || '重设密码失败', 'danger');
|
||||
}).finally(function () { resetCaptcha('lostpassword', form); });
|
||||
}
|
||||
|
||||
function bindDialog(shell) {
|
||||
var dialog = shell.querySelector('[data-account-dialog]');
|
||||
if (!dialog || dialog.dataset.aksrtBound === 'true') return;
|
||||
dialog.dataset.aksrtBound = 'true';
|
||||
dialog.querySelectorAll('.toggle-pwd').forEach(function (button) {
|
||||
button.addEventListener('click', function () {
|
||||
var input = dialog.querySelector('#' + button.getAttribute('data-target'));
|
||||
if (!input) return;
|
||||
input.type = input.type === 'password' ? 'text' : 'password';
|
||||
var icon = button.querySelector('i');
|
||||
if (icon) { icon.classList.toggle('fa-eye', input.type === 'password'); icon.classList.toggle('fa-eye-slash', input.type === 'text'); }
|
||||
});
|
||||
});
|
||||
var type = dialog.getAttribute('data-account-dialog');
|
||||
if (type === 'password') {
|
||||
var recoveryTarget = dialog.querySelector('#pwd-recovery-target');
|
||||
if (recoveryTarget && dialog.dataset.recoveryTarget) recoveryTarget.value = dialog.dataset.recoveryTarget;
|
||||
dialog.querySelectorAll('[data-account-recovery-open],[data-account-recovery-back]').forEach(function (button) {
|
||||
button.addEventListener('click', function () {
|
||||
var showRecovery = button.hasAttribute('data-account-recovery-open');
|
||||
dialog.querySelectorAll('[data-account-password-pane]').forEach(function (pane) {
|
||||
pane.hidden = pane.getAttribute('data-account-password-pane') === 'recovery' ? !showRecovery : showRecovery;
|
||||
});
|
||||
}).catch(function() {
|
||||
resetCaptcha(scene, form);
|
||||
notyf('人机验证失败,请重试', 'danger', 5, toastId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bindSend('pwd-send-code', 'pwd-form', 'change_password');
|
||||
bindSend('email-send-code', 'email-form', 'change_email');
|
||||
|
||||
// 表单提交
|
||||
function submitForm(fid, act) {
|
||||
var fm = document.getElementById(fid);
|
||||
if (!fm) return;
|
||||
if (fm.dataset.aksrtSettingsInitialized === 'true') return;
|
||||
fm.dataset.aksrtSettingsInitialized = 'true';
|
||||
fm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
var btn = fm.querySelector('[type=submit]');
|
||||
var orig = btn ? btn.textContent : '';
|
||||
var toastId = fid === 'pwd-form'
|
||||
? 'aksrt-settings-password-toast'
|
||||
: 'aksrt-settings-email-toast';
|
||||
var reqs = fm.querySelectorAll('[required]');
|
||||
for (var i = 0; i < reqs.length; i++) {
|
||||
if (!reqs[i].value.trim()) { reqs[i].focus(); notyf(i18n.fillRequired || '请填写必填项', 'warning'); return; }
|
||||
}
|
||||
if (fid === 'pwd-form') {
|
||||
var pwdNew = document.getElementById('pwd-new');
|
||||
var pwdConfirm = document.getElementById('pwd-new-confirm');
|
||||
if (pwdNew && pwdConfirm && pwdNew.value !== pwdConfirm.value) {
|
||||
pwdConfirm.focus();
|
||||
notyf(i18n.passwordMismatch || '两次输入的新密码不一致', 'warning');
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (btn) { btn.disabled = true; btn.textContent = i18n.submitting || '提交中...'; }
|
||||
notyf(i18n.submitting || '正在提交…', 'load', '', toastId);
|
||||
var scene = fid === 'pwd-form' ? 'change_password' : 'change_email';
|
||||
runCaptcha(scene, fm).then(function(captchaPayload) {
|
||||
if (captchaPayload === null) {
|
||||
if (btn) { btn.disabled = false; btn.textContent = orig; }
|
||||
return null;
|
||||
var title = dialog.querySelector('[data-account-password-title]');
|
||||
if (title) {
|
||||
title.textContent = title.getAttribute(showRecovery ? 'data-recovery-label' : 'data-change-label') || title.textContent;
|
||||
var shell = dialog.closest('.aksrt-user-settings-modal');
|
||||
if (shell) shell.setAttribute('aria-label', title.textContent);
|
||||
}
|
||||
var d = new FormData(fm);
|
||||
d.set('action', act);
|
||||
d.set('_settings_nonce', settingsNonce);
|
||||
Object.keys(captchaPayload || {}).forEach(function(key) { d.set(key, captchaPayload[key]); });
|
||||
return fetch(ajaxUrl, { method: 'POST', credentials: 'same-origin', body: d });
|
||||
})
|
||||
.then(function(r) { return r ? r.json() : null; })
|
||||
.then(function(r) {
|
||||
if (!r) return;
|
||||
if (btn) { btn.disabled = false; btn.textContent = orig; }
|
||||
if (r.success) {
|
||||
notyf((r.data && r.data.message) || '操作成功', 'success', 5, toastId);
|
||||
closeModal(fm.closest('.aksrt-modal'));
|
||||
if (r.data && r.data.redirect_to) setTimeout(function() { window.location.href = r.data.redirect_to; }, 2500);
|
||||
} else {
|
||||
notyf((r.data && r.data.message) || i18n.operationFailed || '操作失败', 'danger', 5, toastId);
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
if (btn) { btn.disabled = false; btn.textContent = orig; }
|
||||
notyf(i18n.networkError || '网络错误', 'danger', 5, toastId);
|
||||
})
|
||||
.finally(function() {
|
||||
resetCaptcha(scene, fm);
|
||||
var input = dialog.querySelector(showRecovery ? '#pwd-recovery-target' : '#pwd-old');
|
||||
if (input) input.focus();
|
||||
});
|
||||
});
|
||||
var passwordForm = dialog.querySelector('#pwd-form');
|
||||
if (passwordForm) passwordForm.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
var password = passwordForm.querySelector('[name="password"]');
|
||||
var confirm = passwordForm.querySelector('[name="password2"]');
|
||||
if (password && confirm && password.value !== confirm.value) { notyf('两次密码输入不一致', 'warning'); confirm.focus(); return; }
|
||||
postAjaxForm(passwordForm, 'aksrt_change_password', 'change_password', function () { window.setTimeout(function () { window.location.reload(); }, 700); });
|
||||
});
|
||||
var recoveryForm = dialog.querySelector('#pwd-recovery-form');
|
||||
var recoveryCodeButton = dialog.querySelector('[data-account-recovery-send]');
|
||||
if (recoveryCodeButton && recoveryForm) recoveryCodeButton.addEventListener('click', function () { sendRecoveryCode(recoveryCodeButton, recoveryForm); });
|
||||
if (recoveryForm) recoveryForm.addEventListener('submit', function (event) { event.preventDefault(); postRecoveryForm(recoveryForm); });
|
||||
} else if (type === 'email') {
|
||||
dialog.querySelectorAll('.account-code-send').forEach(function (button) {
|
||||
button.addEventListener('click', function () { sendEmailCode(button, button.closest('form'), button.getAttribute('data-account-code-scene')); });
|
||||
});
|
||||
var oldForm = dialog.querySelector('#email-old-verify-form');
|
||||
var newForm = dialog.querySelector('#email-new-form');
|
||||
if (oldForm) oldForm.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
postAjaxForm(oldForm, 'aksrt_verify_account_email', 'change_email', function () { setStep(dialog, 'email-new'); });
|
||||
});
|
||||
if (newForm) newForm.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
postAjaxForm(newForm, 'aksrt_change_email', 'change_email', function () {
|
||||
if (dialog.querySelector('[data-account-step="email-success"]')) setStep(dialog, 'email-success');
|
||||
window.setTimeout(function () { window.location.reload(); }, 700);
|
||||
});
|
||||
});
|
||||
} else if (type === 'phone') {
|
||||
initPhoneDigits(dialog);
|
||||
dialog.querySelectorAll('[data-aksrt-sms-trigger]').forEach(function (button) {
|
||||
button.addEventListener('click', function () { sendPhoneCode(button, button.closest('form'), button.getAttribute('data-aksrt-sms-scene')); });
|
||||
});
|
||||
var phoneOldForm = dialog.querySelector('#phone-old-verify-form');
|
||||
var phoneNewForm = dialog.querySelector('#aksrt-bind-phone-form');
|
||||
if (phoneOldForm) phoneOldForm.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
postPhoneForm(phoneOldForm, '/user/phone-verify', function () { setStep(dialog, 'phone-new'); });
|
||||
});
|
||||
if (phoneNewForm) phoneNewForm.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
postPhoneForm(phoneNewForm, '/user/phone', function () {
|
||||
if (dialog.querySelector('[data-account-step="phone-success"]')) setStep(dialog, 'phone-success');
|
||||
window.setTimeout(function () { window.location.reload(); }, 700);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function resetSettingsModal(shell) {
|
||||
if (!shell || !shell.classList.contains('aksrt-user-settings-modal')) return;
|
||||
shell.querySelectorAll('.account-code-send,[data-aksrt-sms-trigger],[data-account-recovery-send]').forEach(resetCountdown);
|
||||
shell.querySelectorAll('form').forEach(function (form) { form.reset(); });
|
||||
shell.querySelectorAll('[data-account-dialog="phone"]').forEach(function (dialog) {
|
||||
dialog.querySelectorAll('.aksrt-phone-digit').forEach(function (input) { input.value = ''; });
|
||||
var group = dialog.querySelector('[data-aksrt-phone-digits]');
|
||||
if (group) group.classList.remove('has-error');
|
||||
var error = dialog.querySelector('[data-aksrt-phone-digits-error]');
|
||||
if (error) { error.hidden = true; error.textContent = ''; }
|
||||
var area = dialog.querySelector('[data-aksrt-phone-code-area]');
|
||||
if (area) area.hidden = !!group;
|
||||
});
|
||||
}
|
||||
|
||||
submitForm('pwd-form', 'aksrt_change_password');
|
||||
submitForm('email-form', 'aksrt_change_email');
|
||||
if (document.documentElement.dataset.aksrtSettingsModalCleanupInitialized !== 'true') {
|
||||
document.documentElement.dataset.aksrtSettingsModalCleanupInitialized = 'true';
|
||||
document.addEventListener('aksrt:modal:close', function (event) { resetSettingsModal(event.detail && event.detail.shell); });
|
||||
}
|
||||
|
||||
root.querySelectorAll('.binding-item--linkable').forEach(function(link) {
|
||||
root.querySelectorAll('[data-aksrt-settings-modal]').forEach(function (button) {
|
||||
if (button.dataset.aksrtSettingsInitialized === 'true') return;
|
||||
button.dataset.aksrtSettingsInitialized = 'true';
|
||||
button.addEventListener('click', function () { openSettingsModal(button.getAttribute('data-aksrt-settings-modal')); });
|
||||
});
|
||||
root.querySelectorAll('.binding-item--linkable').forEach(function(link) {
|
||||
if (link.dataset.aksrtBindCaptchaInitialized === 'true') return;
|
||||
link.dataset.aksrtBindCaptchaInitialized = 'true';
|
||||
link.addEventListener('click', function(event) {
|
||||
@@ -1100,113 +1362,9 @@
|
||||
initNotifications();
|
||||
initNotificationPreferences();
|
||||
initSettings();
|
||||
initBindPhone();
|
||||
initSettingsCallbackToast();
|
||||
});
|
||||
|
||||
|
||||
// 绑定手机
|
||||
function initBindPhone(root) {
|
||||
root = root || document;
|
||||
var form = root.id === 'aksrt-bind-phone-form' ? root : root.querySelector('#aksrt-bind-phone-form');
|
||||
if (!form) return;
|
||||
if (form.dataset.aksrtSettingsInitialized === 'true') return;
|
||||
form.dataset.aksrtSettingsInitialized = 'true';
|
||||
var sendButton = form.querySelector('[data-aksrt-sms-trigger]');
|
||||
if (sendButton) sendButton.addEventListener('click', function () {
|
||||
var phone = String((form.querySelector('[name="phone"]') || {}).value || '').trim();
|
||||
if (!/^1[3-9]\d{9}$/.test(phone)) { notyf('请输入正确的手机号', 'warning'); return; }
|
||||
var toastId = 'aksrt-settings-phone-code-toast';
|
||||
var restRoot = (window.aksrtData && aksrtData.apiUrl) || '/wp-json/aksrt/v1';
|
||||
var nonce = (window.aksrtData && aksrtData.restNonce) || '';
|
||||
sendButton.disabled = true;
|
||||
notyf('正在发送验证码…', 'load', '', toastId);
|
||||
runCaptcha('bind_phone', form).then(function(captchaPayload) {
|
||||
if (captchaPayload === null) {
|
||||
sendButton.disabled = false;
|
||||
return null;
|
||||
}
|
||||
return fetch(String(restRoot).replace(/\/$/, '') + '/user/phone-code', {
|
||||
method: 'POST', credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce },
|
||||
body: JSON.stringify(Object.assign({ phone: phone }, captchaPayload || {}))
|
||||
});
|
||||
}).then(function (r) { return r ? r.json().then(function (j) { return { ok: r.ok, json: j }; }) : null; }).then(function (res) {
|
||||
if (!res) return;
|
||||
if (!res.ok || !res.json.success) throw new Error(res.json.message || '发送失败');
|
||||
notyf(res.json.message || '验证码已发送', 'success', 5, toastId);
|
||||
var left = Number((res.json.data && res.json.data.countdown) || 60);
|
||||
var timer = setInterval(function () { left -= 1; sendButton.textContent = left + 's'; if (left <= 0) { clearInterval(timer); sendButton.disabled = false; sendButton.textContent = '获取验证码'; } }, 1000);
|
||||
}).catch(function (error) { sendButton.disabled = false; notyf(error.message || '发送失败', 'danger', 5, toastId); })
|
||||
.finally(function () { resetCaptcha('bind_phone', form); });
|
||||
});
|
||||
form.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
var phoneEl = form.querySelector('[name="phone"]');
|
||||
var codeEl = form.querySelector('[name="code"]');
|
||||
var phone = phoneEl ? String(phoneEl.value || '').trim() : '';
|
||||
var code = codeEl ? String(codeEl.value || '').trim() : '';
|
||||
if (!/^1[3-9]\d{9}$/.test(phone)) {
|
||||
notyf('请输入正确的手机号', 'warning');
|
||||
return;
|
||||
}
|
||||
if (!code) {
|
||||
notyf('请输入短信验证码', 'warning');
|
||||
return;
|
||||
}
|
||||
var submitBtn = form.querySelector('[type="submit"]');
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = '绑定中...';
|
||||
}
|
||||
function doBind(captcha) {
|
||||
var toastId = 'aksrt-settings-phone-bind-toast';
|
||||
notyf('正在绑定手机…', 'load', '', toastId);
|
||||
var restRoot = (window.aksrtData && aksrtData.apiUrl) || '/wp-json/aksrt/v1';
|
||||
var nonce = (window.aksrtData && aksrtData.restNonce) || '';
|
||||
return fetch(String(restRoot).replace(/\/$/, '') + '/user/phone', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-WP-Nonce': nonce
|
||||
},
|
||||
body: JSON.stringify(Object.assign({ phone: phone, code: code }, captcha))
|
||||
})
|
||||
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, json: j }; }); })
|
||||
.then(function (res) {
|
||||
if (res.ok && res.json && res.json.success) {
|
||||
notyf(res.json.message || '绑定成功', 'success', 5, toastId);
|
||||
setTimeout(function () { window.location.reload(); }, 600);
|
||||
return;
|
||||
}
|
||||
notyf((res.json && (res.json.message || res.json.error)) || '绑定失败', 'danger', 5, toastId);
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = '确认绑定';
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
notyf('网络错误', 'danger', 5, toastId);
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = '确认绑定';
|
||||
}
|
||||
});
|
||||
}
|
||||
runCaptcha('bind_phone', form).then(function(captchaPayload) {
|
||||
if (captchaPayload === null) {
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = '确认绑定';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return doBind(captchaPayload);
|
||||
}).finally(function() {
|
||||
resetCaptcha('bind_phone', form);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
})();
|
||||
@@ -1781,7 +1781,9 @@ function post_ajax(_this, con, jcon, item, loader, pag, next, trigger, replace,
|
||||
$con.append($loader);
|
||||
}
|
||||
|
||||
$loader.fadeIn();
|
||||
// 显隐只走 .is-loading 类(modal-runtime.css);fadeIn()/hide() 会写
|
||||
// inline display,而内联优先级高于类规则,残留后骨架永远打不开。
|
||||
$loader.addClass('is-loading');
|
||||
swiper_tab_AutoHeight($con);
|
||||
},
|
||||
success: function (a) {
|
||||
@@ -1837,7 +1839,8 @@ function post_ajax(_this, con, jcon, item, loader, pag, next, trigger, replace,
|
||||
}
|
||||
|
||||
$con.removeAttr('ing').trigger('post_ajax.ed', jQuery(a));
|
||||
find('.post_ajax_loader', $con).hide();
|
||||
// 同 beforeSend:只切换 .is-loading,不写 inline display。
|
||||
find('.post_ajax_loader', $con).removeClass('is-loading');
|
||||
|
||||
if (!_this.attr('no-scroll')) {
|
||||
var _scroll_To = replace ? $con : jQuery(c_c[0]);
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
(function($,window2,document2){"use strict";function closeNotyf($element){$element.addClass("notyn-out"),window2.setTimeout(function(){$element.remove()},1e3)}function notyf2(message,type,time,id){var $body=jQuery(document2.body);jQuery(".notyn").length||$body.append('<div class="notyn"></div>'),type=type||"success",type==="error"&&(type="danger"),time=time||5e3,time=time<100?time*1e3:time;var idAttribute=id?' id="'+id+'"':"",$notification=jQuery('<div class="noty1"'+idAttribute+'><div class="notyf '+type+'">'+message+"</div></div>"),shouldClose=!id;id&&jQuery("#"+id).length?(jQuery("#"+id).find(".notyf").removeClass().addClass("notyf "+type).html(message),$notification=jQuery("#"+id),shouldClose=!0):jQuery(".notyn").append($notification),shouldClose&&window2.setTimeout(function(){closeNotyf($notification)},time)}jQuery(document2.body).on("click",".noty1",function(){closeNotyf(jQuery(this))}),window2.notyf=notyf2})(jQuery,window,document),(function(window2){"use strict";var allowedKinds={identity:!0,badges:!0,orders:!0,vip:!0},allowedEvents={click:!0,shell:!0,"first-content":!0,ready:!0,"action-start":!0,"action-end":!0,unauthorized:!0,error:!0},sessions=Object.create(null),sequence=0,maxSessions=100;function normalize(value,fallback){var normalized=String(value||fallback||"").toLowerCase().replace(/[^a-z0-9-]/g,"");return normalized||fallback}function now(){return window2.performance&&typeof window2.performance.now=="function"?window2.performance.now():0}function markName(session,event){return"aksrt-modal-"+session.kind+"-"+session.id+"-"+event}function emit(session,event){if(!(!window2.performance||typeof window2.performance.mark!="function")){var name=markName(session,event);if(window2.performance.mark(name),session.marks[event]=name,typeof window2.performance.measure=="function"&&session.marks.click&&event!=="click")try{window2.performance.measure("aksrt-modal-"+session.kind+"-"+session.id+"-click-to-"+event,session.marks.click,name)}catch{}}}function start(kind){var safeKind=normalize(kind,"unknown");allowedKinds[safeKind]||(safeKind="unknown"),sequence+=1;var id=String(sequence),session={id,kind:safeKind,startedAt:now(),marks:Object.create(null)};sessions[id]=session,emit(session,"click");for(var ids=Object.keys(sessions);ids.length>maxSessions;)delete sessions[ids.shift()];return id}function mark(id,event){var session=sessions[String(id||"")],safeEvent=normalize(event,"error");!session||!allowedEvents[safeEvent]||emit(session,safeEvent)}function finish(id){mark(id,"ready")}function fail(id){mark(id,"error")}window2.AksrtModalTelemetry={start,mark,finish,error:fail}})(window),(function(window2){"use strict";function data(){return window2.aksrtData||window2.aksrt||{}}function createIdempotencyKey(){if(window2.crypto&&typeof window2.crypto.randomUUID=="function")return window2.crypto.randomUUID();if(!window2.crypto||typeof window2.crypto.getRandomValues!="function")throw new Error("当前浏览器无法生成安全的操作标识");var bytes=new Uint8Array(16);window2.crypto.getRandomValues(bytes),bytes[6]=bytes[6]&15|64,bytes[8]=bytes[8]&63|128;var hex=Array.prototype.map.call(bytes,function(value){return value.toString(16).padStart(2,"0")}).join("");return hex.slice(0,8)+"-"+hex.slice(8,12)+"-"+hex.slice(12,16)+"-"+hex.slice(16,20)+"-"+hex.slice(20)}function restBase(){var config=data(),raw=config.restUrl||config.apiUrl||config.rest_url||"",siteUrl=config.siteUrl||config.homeUrl||window2.location.origin+"/";return window2.location.protocol==="https:"&&siteUrl.substring(0,5)==="http:"&&(siteUrl="https:"+siteUrl.substring(5)),raw?String(raw).replace(/\/$/,""):String(siteUrl).replace(/\/$/,"")+"/index.php?rest_route=/aksrt/v1"}function appendQuery(url2,query){if(!query)return url2;var parts=[];return typeof query=="string"?parts=query.replace(/^\?/,"").split("&").filter(Boolean):typeof query=="object"&&Object.keys(query).forEach(function(key){query[key]===void 0||query[key]===null||parts.push(encodeURIComponent(key)+"="+encodeURIComponent(String(query[key])))}),parts.length?url2+(url2.indexOf("?")>=0?"&":"?")+parts.join("&"):url2}function url(path,query){path=String(path||"");var inlineQuery="",queryIndex=path.indexOf("?");if(queryIndex>=0&&(inlineQuery=path.slice(queryIndex+1),path=path.slice(0,queryIndex)),/^https?:\/\//i.test(path))return appendQuery(path,query||inlineQuery);path.charAt(0)!=="/"&&(path="/"+path);var base=restBase(),result=(base.indexOf("rest_route=")>=0,base+path);return appendQuery(result,query||inlineQuery)}function headers(options){var config=data(),source=options&&options.headers||{},result={},nonce=config.restNonce||config.nonce||"";return Object.keys(source).forEach(function(key){result[key]=source[key]}),result.Accept||(result.Accept="application/json, text/html;q=0.9, */*;q=0.8"),nonce&&!result["X-WP-Nonce"]&&(result["X-WP-Nonce"]=nonce),options&&options.body!==void 0&&options.body!==null&&!(window2.FormData&&options.body instanceof window2.FormData)&&!result["Content-Type"]&Line truncated
|
||||
(function($,window2,document2){"use strict";function closeNotyf($element){$element.addClass("notyn-out"),window2.setTimeout(function(){$element.remove()},1e3)}function notyf2(message,type,time,id){var $body=jQuery(document2.body);jQuery(".notyn").length||$body.append('<div class="notyn"></div>'),type=type||"success",type==="error"&&(type="danger"),time=time||5e3,time=time<100?time*1e3:time;var idAttribute=id?' id="'+id+'"':"",$notification=jQuery('<div class="noty1"'+idAttribute+'><div class="notyf '+type+'">'+message+"</div></div>"),shouldClose=!id;id&&jQuery("#"+id).length?(jQuery("#"+id).find(".notyf").removeClass().addClass("notyf "+type).html(message),$notification=jQuery("#"+id),shouldClose=!0):jQuery(".notyn").append($notification),shouldClose&&window2.setTimeout(function(){closeNotyf($notification)},time)}jQuery(document2.body).on("click",".noty1",function(){closeNotyf(jQuery(this))}),window2.notyf=notyf2})(jQuery,window,document),(function(window2){"use strict";var allowedKinds={identity:!0,badges:!0,orders:!0,vip:!0},allowedEvents={click:!0,shell:!0,"first-content":!0,ready:!0,"action-start":!0,"action-end":!0,unauthorized:!0,error:!0},sessions=Object.create(null),sequence=0,maxSessions=100;function normalize(value,fallback){var normalized=String(value||fallback||"").toLowerCase().replace(/[^a-z0-9-]/g,"");return normalized||fallback}function now(){return window2.performance&&typeof window2.performance.now=="function"?window2.performance.now():0}function markName(session,event){return"aksrt-modal-"+session.kind+"-"+session.id+"-"+event}function emit(session,event){if(!(!window2.performance||typeof window2.performance.mark!="function")){var name=markName(session,event);if(window2.performance.mark(name),session.marks[event]=name,typeof window2.performance.measure=="function"&&session.marks.click&&event!=="click")try{window2.performance.measure("aksrt-modal-"+session.kind+"-"+session.id+"-click-to-"+event,session.marks.click,name)}catch{}}}function start(kind){var safeKind=normalize(kind,"unknown");allowedKinds[safeKind]||(safeKind="unknown"),sequence+=1;var id=String(sequence),session={id,kind:safeKind,startedAt:now(),marks:Object.create(null)};sessions[id]=session,emit(session,"click");for(var ids=Object.keys(sessions);ids.length>maxSessions;)delete sessions[ids.shift()];return id}function mark(id,event){var session=sessions[String(id||"")],safeEvent=normalize(event,"error");!session||!allowedEvents[safeEvent]||emit(session,safeEvent)}function finish(id){mark(id,"ready")}function fail(id){mark(id,"error")}window2.AksrtModalTelemetry={start,mark,finish,error:fail}})(window),(function(window2){"use strict";function data(){return window2.aksrtData||window2.aksrt||{}}function createIdempotencyKey(){if(window2.crypto&&typeof window2.crypto.randomUUID=="function")return window2.crypto.randomUUID();if(!window2.crypto||typeof window2.crypto.getRandomValues!="function")throw new Error("当前浏览器无法生成安全的操作标识");var bytes=new Uint8Array(16);window2.crypto.getRandomValues(bytes),bytes[6]=bytes[6]&15|64,bytes[8]=bytes[8]&63|128;var hex=Array.prototype.map.call(bytes,function(value){return value.toString(16).padStart(2,"0")}).join("");return hex.slice(0,8)+"-"+hex.slice(8,12)+"-"+hex.slice(12,16)+"-"+hex.slice(16,20)+"-"+hex.slice(20)}function restBase(){var config=data(),raw=config.restUrl||config.apiUrl||config.rest_url||"",siteUrl=config.siteUrl||config.homeUrl||window2.location.origin+"/";return window2.location.protocol==="https:"&&siteUrl.substring(0,5)==="http:"&&(siteUrl="https:"+siteUrl.substring(5)),raw?String(raw).replace(/\/$/,""):String(siteUrl).replace(/\/$/,"")+"/index.php?rest_route=/aksrt/v1"}function appendQuery(url2,query){if(!query)return url2;var parts=[];return typeof query=="string"?parts=query.replace(/^\?/,"").split("&").filter(Boolean):typeof query=="object"&&Object.keys(query).forEach(function(key){query[key]===void 0||query[key]===null||parts.push(encodeURIComponent(key)+"="+encodeURIComponent(String(query[key])))}),parts.length?url2+(url2.indexOf("?")>=0?"&":"?")+parts.join("&"):url2}function url(path,query){path=String(path||"");var inlineQuery="",queryIndex=path.indexOf("?");if(queryIndex>=0&&(inlineQuery=path.slice(queryIndex+1),path=path.slice(0,queryIndex)),/^https?:\/\//i.test(path))return appendQuery(path,query||inlineQuery);path.charAt(0)!=="/"&&(path="/"+path);var base=restBase(),result=(base.indexOf("rest_route=")>=0,base+path);return appendQuery(result,query||inlineQuery)}function headers(options){var config=data(),source=options&&options.headers||{},result={},nonce=config.restNonce||config.nonce||"";return Object.keys(source).forEach(function(key){result[key]=source[key]}),result.Accept||(result.Accept="application/json, text/html;q=0.9, */*;q=0.8"),nonce&&!result["X-WP-Nonce"]&&(result["X-WP-Nonce"]=nonce),options&&options.body!==void 0&&options.body!==null&&!(window2.FormData&&options.body instanceof window2.FormData)&&!result["Content-Type"]&Line truncated
|
||||
@@ -377,7 +377,7 @@
|
||||
if (data.redirect && (data.payment_url || data.qr_content)) {
|
||||
callback(options, "onPending", data);
|
||||
if (!data.order_number) {
|
||||
var missing = { ok: false, status: 500, json: { message: "流水号缺失" } };
|
||||
var missing = { ok: false, status: 500, json: { message: "订单号缺失" } };
|
||||
callback(options, "onError", missing);
|
||||
return missing;
|
||||
}
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
/**
|
||||
* AKSRT Waterfall — 瀑布流/异步列表
|
||||
*
|
||||
* 提供纯 CSS column-based 瀑布流布局和无限滚动加载能力。
|
||||
* 无外部依赖。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Initialize waterfall layout on a container.
|
||||
*
|
||||
* @param {HTMLElement} container - The container element
|
||||
* @param {Object} options
|
||||
* @param {number} options.columns - Number of columns (default: auto from CSS)
|
||||
* @param {string} options.itemSelector - Child item selector (default: '.waterfall-item')
|
||||
* @param {number} options.gap - Gap between items in px (default: 16)
|
||||
*/
|
||||
function Waterfall(container, options) {
|
||||
if (!container) return;
|
||||
|
||||
this.container = container;
|
||||
this.options = Object.assign({
|
||||
columns: 0,
|
||||
itemSelector: '.waterfall-item',
|
||||
gap: 16,
|
||||
}, options || {});
|
||||
|
||||
this._resizeHandler = this.layout.bind(this);
|
||||
window.addEventListener('resize', this._resizeHandler);
|
||||
this.layout();
|
||||
}
|
||||
|
||||
Waterfall.prototype.layout = function () {
|
||||
var items = this.container.querySelectorAll(this.options.itemSelector);
|
||||
if (items.length === 0) return;
|
||||
|
||||
var cols = this.options.columns;
|
||||
if (cols <= 0) {
|
||||
var containerWidth = this.container.offsetWidth;
|
||||
cols = Math.max(1, Math.floor(containerWidth / 280));
|
||||
}
|
||||
|
||||
var gap = this.options.gap;
|
||||
var colWidth = (this.container.offsetWidth - gap * (cols - 1)) / cols;
|
||||
var colHeights = new Array(cols).fill(0);
|
||||
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var item = items[i];
|
||||
item.style.width = colWidth + 'px';
|
||||
item.style.position = 'absolute';
|
||||
|
||||
// Find shortest column
|
||||
var minHeight = Infinity;
|
||||
var minCol = 0;
|
||||
for (var c = 0; c < cols; c++) {
|
||||
if (colHeights[c] < minHeight) {
|
||||
minHeight = colHeights[c];
|
||||
minCol = c;
|
||||
}
|
||||
}
|
||||
|
||||
item.style.left = (minCol * (colWidth + gap)) + 'px';
|
||||
item.style.top = colHeights[minCol] + 'px';
|
||||
|
||||
colHeights[minCol] += item.offsetHeight + gap;
|
||||
}
|
||||
|
||||
// Set container height to tallest column
|
||||
var maxHeight = Math.max.apply(null, colHeights);
|
||||
this.container.style.height = maxHeight + 'px';
|
||||
this.container.style.position = 'relative';
|
||||
};
|
||||
|
||||
Waterfall.prototype.relayout = function () {
|
||||
this.layout();
|
||||
};
|
||||
|
||||
Waterfall.prototype.destroy = function () {
|
||||
window.removeEventListener('resize', this._resizeHandler);
|
||||
};
|
||||
|
||||
/**
|
||||
* Infinite scroll loader.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {string} options.containerSelector - List container selector
|
||||
* @param {string} options.loadMoreSelector - Load-more button selector
|
||||
* @param {Function} options.loadMore - Async function that loads more items
|
||||
* @param {number} options.threshold - Distance from bottom to trigger (default: 200)
|
||||
*/
|
||||
function InfiniteScroll(options) {
|
||||
this.options = Object.assign({
|
||||
containerSelector: '.aksrt-infinite-list',
|
||||
loadMoreSelector: '.aksrt-load-more',
|
||||
loadMore: null,
|
||||
threshold: 200,
|
||||
}, options || {});
|
||||
|
||||
this.loading = false;
|
||||
this.hasMore = true;
|
||||
this._scrollHandler = this._check.bind(this);
|
||||
|
||||
window.addEventListener('scroll', this._scrollHandler);
|
||||
this._check();
|
||||
}
|
||||
|
||||
InfiniteScroll.prototype._check = function () {
|
||||
if (this.loading || !this.hasMore) return;
|
||||
|
||||
var loadMoreBtn = document.querySelector(this.options.loadMoreSelector);
|
||||
if (!loadMoreBtn) {
|
||||
this.hasMore = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var rect = loadMoreBtn.getBoundingClientRect();
|
||||
if (rect.top < window.innerHeight + this.options.threshold) {
|
||||
this._load();
|
||||
}
|
||||
};
|
||||
|
||||
InfiniteScroll.prototype._load = function () {
|
||||
if (!this.options.loadMore) return;
|
||||
|
||||
this.loading = true;
|
||||
var self = this;
|
||||
|
||||
this.options.loadMore().then(function (hasMore) {
|
||||
self.hasMore = hasMore !== false;
|
||||
self.loading = false;
|
||||
if (self.hasMore) {
|
||||
self._check();
|
||||
}
|
||||
}).catch(function () {
|
||||
self.loading = false;
|
||||
});
|
||||
};
|
||||
|
||||
InfiniteScroll.prototype.destroy = function () {
|
||||
window.removeEventListener('scroll', this._scrollHandler);
|
||||
};
|
||||
|
||||
// Auto-init
|
||||
var waterfallInstances = [];
|
||||
|
||||
function relayoutAll() {
|
||||
waterfallInstances.forEach(function (instance) {
|
||||
if (instance && typeof instance.relayout === 'function') {
|
||||
instance.relayout();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function autoInit() {
|
||||
document.querySelectorAll('[data-aksrt-waterfall]:not([data-aksrt-waterfall-bound])').forEach(function (el) {
|
||||
el.dataset.aksrtWaterfallBound = '1';
|
||||
var cols = parseInt(el.dataset.aksrtWaterfallColumns) || 0;
|
||||
var gap = parseInt(el.dataset.aksrtWaterfallGap) || 16;
|
||||
waterfallInstances.push(new Waterfall(el, { columns: cols, gap: gap }));
|
||||
});
|
||||
relayoutAll();
|
||||
}
|
||||
|
||||
jQuery(document).off('auto_fun.aksrt-waterfall').on('auto_fun.aksrt-waterfall', autoInit);
|
||||
jQuery(document).off('lazyloaded.aksrt-waterfall').on('lazyloaded.aksrt-waterfall', relayoutAll);
|
||||
|
||||
// Export to global
|
||||
window.AksrtWaterfall = Waterfall;
|
||||
window.AksrtInfiniteScroll = InfiniteScroll;
|
||||
})();
|
||||
@@ -109,23 +109,50 @@
|
||||
}
|
||||
|
||||
function loaderOf(container) {
|
||||
if (!container) return null;
|
||||
return container.querySelector('.post_ajax_loader');
|
||||
}
|
||||
|
||||
function fallbackLoader() {
|
||||
var text = (window.aksrtData && aksrtData.ajaxLoading) || '加载中...';
|
||||
var node = document.createElement('div');
|
||||
node.className = 'post_ajax_loader';
|
||||
node.setAttribute('role', 'status');
|
||||
node.innerHTML = '<div class="mb20"><div class="text-center muted-2-color mt20"><i class="loading mr10"></i>' + text + '</div></div>';
|
||||
return node;
|
||||
}
|
||||
|
||||
function setBusy(container, busy) {
|
||||
if (!container) return;
|
||||
if (busy) {
|
||||
container.dataset.loading = '1';
|
||||
container.setAttribute('aria-busy', 'true');
|
||||
var loader = loaderOf(container);
|
||||
if (loader) loader.style.display = '';
|
||||
// 骨架开关走 .is-loading 类(modal-runtime.css),不写 inline display。
|
||||
var loader = loaderOf(container) || container.appendChild(fallbackLoader());
|
||||
loader.classList.add('is-loading');
|
||||
} else {
|
||||
delete container.dataset.loading;
|
||||
container.setAttribute('aria-busy', 'false');
|
||||
var loader = loaderOf(container);
|
||||
if (loader) loader.style.display = 'none';
|
||||
if (loader) loader.classList.remove('is-loading');
|
||||
}
|
||||
}
|
||||
|
||||
// 参考主题 post_ajax 的 beforeSend 语义:骨架每次请求都挪到列表末尾
|
||||
// (最后一个条目之后、分页控件之前),请求成功后只隐藏而不删除,
|
||||
// 下一轮翻页复用同一个节点。
|
||||
function anchorLoader(container) {
|
||||
var pag = container.querySelector(':scope > .ajax-pag');
|
||||
var loader = loaderOf(container);
|
||||
if (!loader) return null;
|
||||
if (pag) {
|
||||
pag.parentNode.insertBefore(loader, pag);
|
||||
} else {
|
||||
container.appendChild(loader);
|
||||
}
|
||||
return loader;
|
||||
}
|
||||
|
||||
function loadItems(container, page, append) {
|
||||
var owner = container.closest('[data-widget]') || container;
|
||||
var widget = owner.dataset.widget || container.dataset.widget;
|
||||
@@ -133,28 +160,49 @@
|
||||
var segment = container.dataset.segment || owner.dataset.segment || '';
|
||||
if (!widget || !instance || !config.restUrl || container.dataset.loading === '1') return;
|
||||
setBusy(container, true);
|
||||
fetch(itemsUrl(widget, instance, page, segment), { credentials: 'same-origin', headers: { Accept: 'application/json' } })
|
||||
anchorLoader(container);
|
||||
return fetch(itemsUrl(widget, instance, page, segment), { credentials: 'same-origin', headers: { Accept: 'application/json' } })
|
||||
.then(function (response) { if (!response.ok) throw new Error('items'); return response.json(); })
|
||||
.then(function (data) {
|
||||
var fragment = document.createElement('div');
|
||||
fragment.innerHTML = String(data.html || '');
|
||||
Array.prototype.slice.call(container.querySelectorAll(':scope > .ajax-pag')).forEach(function (node) { node.remove(); });
|
||||
if (append) {
|
||||
Array.prototype.slice.call(fragment.children).forEach(function (node) { container.appendChild(node); });
|
||||
// 追加方向为底部:新条目插到骨架之前,骨架始终贴住最后一张卡片。
|
||||
var anchor = loaderOf(container) || container.querySelector(':scope > .ajax-pag');
|
||||
Array.prototype.slice.call(fragment.children).forEach(function (node) {
|
||||
if (anchor && anchor.parentNode === container) {
|
||||
container.insertBefore(node, anchor);
|
||||
} else {
|
||||
container.appendChild(node);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
container.innerHTML = fragment.innerHTML;
|
||||
// 替换时保留骨架节点本身(参考主题 success 后只 hide() 不删除)。
|
||||
var loader = loaderOf(container);
|
||||
Array.prototype.slice.call(container.children).forEach(function (child) {
|
||||
if (child !== loader && !child.classList.contains('ajax-pag')) child.remove();
|
||||
});
|
||||
Array.prototype.slice.call(fragment.children).forEach(function (node) { container.appendChild(node); });
|
||||
}
|
||||
Array.prototype.slice.call(container.querySelectorAll(':scope > .ajax-pag')).forEach(function (node) { node.remove(); });
|
||||
if (data.paginate_html) container.insertAdjacentHTML('beforeend', data.paginate_html);
|
||||
container.dataset.page = String(data.page || page);
|
||||
anchorLoader(container);
|
||||
scan(container);
|
||||
})
|
||||
.catch(function () { container.innerHTML = errorMarkup(); })
|
||||
.catch(function () {
|
||||
var loader = loaderOf(container);
|
||||
if (loader && loader.parentNode === container) {
|
||||
loader.insertAdjacentHTML('beforebegin', errorMarkup());
|
||||
} else {
|
||||
container.insertAdjacentHTML('afterbegin', errorMarkup());
|
||||
}
|
||||
})
|
||||
.finally(function () { setBusy(container, false); });
|
||||
}
|
||||
|
||||
function initItems(container) {
|
||||
if (!once(container, 'Items')) return;
|
||||
if (container.classList.contains('is-loading')) loadItems(container, 1, false);
|
||||
container.addEventListener('click', function (event) {
|
||||
var retry = event.target.closest('[data-widget-retry]');
|
||||
var trigger = event.target.closest('[data-next-page]');
|
||||
|
||||
@@ -139,7 +139,6 @@ if (function_exists('aksrt_archive_open_shell')) {
|
||||
'stream_class' => 'archive__ajax-wrap content-stream--archive content-stream--category',
|
||||
'empty_text' => __('该分类下暂无文章', 'aksrt'),
|
||||
'ajax_enabled' => $ajax_filter,
|
||||
'show_loading' => $ajax_filter,
|
||||
]);
|
||||
}
|
||||
?>
|
||||
|
||||
+23
-6
@@ -167,6 +167,15 @@ if (class_exists('\AKSRT\Settings\MailNotifications')) {
|
||||
\AKSRT\Auth\Router::get_instance()->init();
|
||||
\AKSRT\Core\Templates::get_instance()->init();
|
||||
\AKSRT\Auth\Moderation::get_instance()->init();
|
||||
if (class_exists('\AKSRT\Extensions\SecurityHardening')) {
|
||||
\AKSRT\Extensions\SecurityHardening::get_instance()->init();
|
||||
}
|
||||
if (class_exists('\AKSRT\Extensions\SystemTools')) {
|
||||
\AKSRT\Extensions\SystemTools::get_instance()->init();
|
||||
}
|
||||
if (class_exists('\AKSRT\Frontend\PageAdminSet')) {
|
||||
\AKSRT\Frontend\PageAdminSet::get_instance()->init();
|
||||
}
|
||||
|
||||
// 短代码统一注册入口(registry)
|
||||
if (class_exists('\AKSRT\Core\Shortcodes')) {
|
||||
@@ -230,6 +239,9 @@ if (class_exists('\AKSRT\Payment\ChargeCard')) {
|
||||
if (class_exists('\AKSRT\Payment\OrderCenterRest')) {
|
||||
\AKSRT\Payment\OrderCenterRest::get_instance()->init();
|
||||
}
|
||||
if (class_exists('\AKSRT\Payment\AfterSaleService')) {
|
||||
\AKSRT\Payment\AfterSaleService::get_instance()->init();
|
||||
}
|
||||
if (class_exists('\AKSRT\Credits\BalanceService')) {
|
||||
\AKSRT\Credits\BalanceService::get_instance()->init();
|
||||
}
|
||||
@@ -243,9 +255,6 @@ if (class_exists('\AKSRT\Core\DocsPresenter')) {
|
||||
if (class_exists('\AKSRT\Content\Visibility')) {
|
||||
\AKSRT\Content\Visibility::get_instance()->init();
|
||||
}
|
||||
if (class_exists('\AKSRT\Sms\SMS')) {
|
||||
\AKSRT\Sms\SMS::get_instance()->init();
|
||||
}
|
||||
if (class_exists('\AKSRT\Ai\AI')) {
|
||||
\AKSRT\Ai\AI::get_instance(); // config-gated; available when admin enables
|
||||
}
|
||||
@@ -286,6 +295,9 @@ if (class_exists('\AKSRT\Extensions\MeiliSearch')) {
|
||||
if (class_exists('\AKSRT\Extensions\SearchPush')) {
|
||||
\AKSRT\Extensions\SearchPush::get_instance()->init();
|
||||
}
|
||||
if (class_exists('\AKSRT\Extensions\SearchPushMetaBox')) {
|
||||
\AKSRT\Extensions\SearchPushMetaBox::get_instance()->init();
|
||||
}
|
||||
if (class_exists('\AKSRT\Core\SEOHead')) {
|
||||
\AKSRT\Core\SEOHead::get_instance()->init();
|
||||
}
|
||||
@@ -374,6 +386,9 @@ if (class_exists('\AKSRT\User\Level')) {
|
||||
if (class_exists('\AKSRT\User\LevelAdmin')) {
|
||||
\AKSRT\User\LevelAdmin::get_instance()->init();
|
||||
}
|
||||
if (class_exists('\AKSRT\User\ProfileAdmin')) {
|
||||
\AKSRT\User\ProfileAdmin::init();
|
||||
}
|
||||
if (class_exists('\AKSRT\User\Metrics')) {
|
||||
\AKSRT\User\Metrics::get_instance()->init();
|
||||
}
|
||||
@@ -394,9 +409,6 @@ if (is_admin() && class_exists('\AKSRT\Admin\Native\PayOrders')) {
|
||||
if (is_admin() && class_exists('\AKSRT\Admin\Native\PayProducts')) {
|
||||
\AKSRT\Admin\Native\PayProducts::get_instance()->init();
|
||||
}
|
||||
if (is_admin() && class_exists('\AKSRT\Admin\Native\PayDownloadLogs')) {
|
||||
\AKSRT\Admin\Native\PayDownloadLogs::get_instance()->init();
|
||||
}
|
||||
if (is_admin() && class_exists('\AKSRT\Admin\Native\PayWithdrawals')) {
|
||||
\AKSRT\Admin\Native\PayWithdrawals::get_instance()->init();
|
||||
}
|
||||
@@ -404,6 +416,11 @@ if (is_admin() && class_exists('\AKSRT\Admin\Native\PayIncome')) {
|
||||
\AKSRT\Admin\Native\PayIncome::get_instance()->init();
|
||||
}
|
||||
|
||||
// 后台文章列表增强:互动/销售数据列 + 快捷编辑配置项
|
||||
if (is_admin() && class_exists('\AKSRT\Admin\Native\PostListEnhance')) {
|
||||
\AKSRT\Admin\Native\PostListEnhance::get_instance()->init();
|
||||
}
|
||||
|
||||
// WordPress 原生用户菜单:消息/推广码/认证/禁封(legacy users.php 子菜单对齐)
|
||||
if (is_admin() && class_exists('\AKSRT\Admin\Native\UsersMenus')) {
|
||||
\AKSRT\Admin\Native\UsersMenus::get_instance()->init();
|
||||
|
||||
@@ -19,6 +19,7 @@ require_once AKSRT_DIR . '/inc/helpers/aksrt-avatar.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-domain-model.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-template-tags.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-frontend-tags.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-phone.php';
|
||||
// Shared comment kernel.
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-ip-geo.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/comments/content.php';
|
||||
@@ -55,6 +56,7 @@ require_once AKSRT_DIR . '/inc/helpers/aksrt-null.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/user-ban.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-ajax-list.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-links.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/message-placeholder.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/search/aksrt-search-posts.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/search/aksrt-search-box.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-header-userbox.php';
|
||||
|
||||
+2
-62
@@ -62,11 +62,10 @@ add_action('save_post', static function ($post_id, $post = null): void {
|
||||
|
||||
// ============================================================
|
||||
|
||||
// 前端隐藏 WordPress Admin Bar
|
||||
// 前端隐藏 WordPress Admin Bar:由 SystemTools 按「扩展&增强 → 系统工具」
|
||||
// 的 hide_admin_bar 设置门控(默认开启),不再无条件注入。
|
||||
// ============================================================
|
||||
|
||||
add_filter('show_admin_bar', '__return_false');
|
||||
|
||||
// ============================================================
|
||||
// SMTP 邮件配置注入
|
||||
// ============================================================
|
||||
@@ -460,65 +459,6 @@ add_filter('wp_kses_allowed_html', function ($allowed, $context) {
|
||||
return $allowed;
|
||||
}, 10, 2);
|
||||
|
||||
// ============================================================
|
||||
// WP 后台文章列表增强列 — 浏览/点赞/收藏/下载
|
||||
// ============================================================
|
||||
|
||||
add_filter('manage_post_posts_columns', function ($columns) {
|
||||
$new_columns = [];
|
||||
foreach ($columns as $key => $label) {
|
||||
$new_columns[$key] = $label;
|
||||
if ($key === 'title') {
|
||||
$new_columns['aksrt_stats'] = __('互动数据', 'aksrt');
|
||||
}
|
||||
}
|
||||
return $new_columns;
|
||||
});
|
||||
|
||||
add_action('admin_enqueue_scripts', function ($hook) {
|
||||
if ($hook !== 'edit.php') return;
|
||||
aksrt_enqueue_fontawesome();
|
||||
});
|
||||
|
||||
add_action('manage_post_posts_custom_column', function ($column, $post_id) {
|
||||
if ($column !== 'aksrt_stats') return;
|
||||
|
||||
$views = (int) get_post_meta($post_id, 'views', true);
|
||||
$likes = (int) get_post_meta($post_id, 'like', true);
|
||||
$favorites = (int) get_post_meta($post_id, 'favorite', true);
|
||||
$downloads = (int) get_post_meta($post_id, 'download_count', true);
|
||||
|
||||
$format = function (int $n): string {
|
||||
if ($n >= 10000) return round($n / 10000, 1) . '万';
|
||||
if ($n >= 1000) return round($n / 1000, 1) . 'k';
|
||||
return (string) $n;
|
||||
};
|
||||
|
||||
$parts = [];
|
||||
$parts[] = '<span title="' . esc_attr__('浏览', 'aksrt') . '" style="margin-right:4px;"><i class="fa fas fa-eye" style="color:#8a909c;font-size:12px;"></i> ' . esc_html($format($views)) . '</span>';
|
||||
$parts[] = '<span title="' . esc_attr__('点赞', 'aksrt') . '" style="margin-right:4px;"><i class="fa fas fa-thumbs-up" style="color:#1890ff;font-size:12px;"></i> ' . esc_html($format($likes)) . '</span>';
|
||||
$parts[] = '<span title="' . esc_attr__('收藏', 'aksrt') . '" style="margin-right:4px;"><i class="fa fas fa-star" style="color:#f39c12;font-size:12px;"></i> ' . esc_html($format($favorites)) . '</span>';
|
||||
|
||||
if ($downloads > 0) {
|
||||
$parts[] = '<span title="' . esc_attr__('下载', 'aksrt') . '"><i class="fa fas fa-download" style="color:#52c41a;font-size:12px;"></i> ' . esc_html($format($downloads)) . '</span>';
|
||||
}
|
||||
|
||||
echo implode(' · ', $parts);
|
||||
}, 10, 2);
|
||||
|
||||
// Support sorting by views/likes/favorites in admin
|
||||
add_action('pre_get_posts', function ($query) {
|
||||
if (!is_admin() || !$query->is_main_query()) return;
|
||||
|
||||
$orderby = $query->get('orderby');
|
||||
$allowed = ['views', 'like', 'favorite', 'download_count'];
|
||||
|
||||
if (in_array($orderby, $allowed, true)) {
|
||||
$query->set('meta_key', $orderby);
|
||||
$query->set('orderby', 'meta_value_num');
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// Gutenberg 自定义块注册
|
||||
// ============================================================
|
||||
|
||||
@@ -178,7 +178,6 @@ if (!function_exists('aksrt_archive_render_stream')) :
|
||||
'stream_class' => '',
|
||||
'ajax_enabled' => false,
|
||||
'ajax_attrs' => '',
|
||||
'show_loading' => false,
|
||||
]);
|
||||
|
||||
$classes = [
|
||||
@@ -211,10 +210,6 @@ if (!function_exists('aksrt_archive_render_stream')) :
|
||||
<?php endif; ?>
|
||||
<?php echo $args['ajax_attrs']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
>
|
||||
<?php if (!empty($args['show_loading'])) : ?>
|
||||
<div class="archive__ajax-loading" aria-hidden="true"><span class="archive__ajax-spinner"></span></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($has_posts) : ?>
|
||||
<?php
|
||||
// legacy 契约:分页渲染在 .ajaxpager 内部(zib_paging 位置),
|
||||
@@ -226,9 +221,19 @@ if (!function_exists('aksrt_archive_render_stream')) :
|
||||
]);
|
||||
$pagination_html = (string) ob_get_clean();
|
||||
|
||||
// 归档面与首页共用同一套骨架契约:.post_ajax_loader + .is-loading。
|
||||
// 挂在分页之后(列表末尾),archive.js 靠它在 wrap 内定位加载态。
|
||||
$loader_html = '<div class="post_ajax_loader" role="status" aria-label="'
|
||||
. esc_attr__('正在加载', 'aksrt') . '">'
|
||||
. aksrt_render_placeholder(
|
||||
(string) $render_config['mode'] === 'card' ? 'card' : 'list',
|
||||
4
|
||||
)
|
||||
. '</div>';
|
||||
|
||||
aksrt_render_post_list(array_merge($render_config, [
|
||||
'wrapper_class' => 'content-stream__list',
|
||||
'append_inner_html' => $pagination_html,
|
||||
'append_inner_html' => $pagination_html . $loader_html,
|
||||
]), $query);
|
||||
?>
|
||||
<?php else : ?>
|
||||
|
||||
Loaded 100 of 355 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user