feat(share): align social sharing runtime
This commit is contained in:
1 parent
26ac8167f3
commit
cf6c11852e
33 files changed
+802
-1919
No files matched your search
@@ -82,6 +82,12 @@
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</div>
|
||||
<AksSorterField
|
||||
v-else-if="field.type === 'custom' && field.renderer === 'sorter'"
|
||||
:model-value="getFieldValue(field.key) || {}"
|
||||
:options="field.options || []"
|
||||
@update:model-value="setFieldValue(field.key, $event)"
|
||||
/>
|
||||
<slot
|
||||
v-else-if="field.type === 'custom'"
|
||||
:name="`field-${field.key}`"
|
||||
@@ -93,7 +99,7 @@
|
||||
:form-data="formData"
|
||||
:error-message="errors?.[field.key]"
|
||||
@update:model-value="setFieldValue(field.key, $event)"
|
||||
@action="emit('field-action', $event)"
|
||||
@action="emit('field-action', $event)"
|
||||
/>
|
||||
</slot>
|
||||
<AksField
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<div class="aksrt-sorter">
|
||||
<div class="aksrt-sorter__column">
|
||||
<div class="aksrt-sorter__title">
|
||||
<span>已启用</span>
|
||||
<n-tag size="small" type="success" :bordered="false">{{ columns.enabled.length }}</n-tag>
|
||||
</div>
|
||||
<draggable
|
||||
v-model="columns.enabled"
|
||||
item-key="value"
|
||||
group="aksrt-sorter"
|
||||
handle=".aksrt-sorter__handle"
|
||||
ghost-class="aksrt-sorter__ghost"
|
||||
animation="180"
|
||||
class="aksrt-sorter__list"
|
||||
@change="commit"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<div class="aksrt-sorter__item">
|
||||
<i class="fa fas fa-grip-vertical aksrt-sorter__handle" aria-hidden="true"></i>
|
||||
<i v-if="element.icon" :class="element.icon" aria-hidden="true"></i>
|
||||
<span>{{ element.label }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
<n-empty v-if="columns.enabled.length === 0" size="small" description="暂无启用项" />
|
||||
</div>
|
||||
|
||||
<div class="aksrt-sorter__column aksrt-sorter__column--disabled">
|
||||
<div class="aksrt-sorter__title">
|
||||
<span>未启用</span>
|
||||
<n-tag size="small" :bordered="false">{{ columns.disabled.length }}</n-tag>
|
||||
</div>
|
||||
<draggable
|
||||
v-model="columns.disabled"
|
||||
item-key="value"
|
||||
group="aksrt-sorter"
|
||||
handle=".aksrt-sorter__handle"
|
||||
ghost-class="aksrt-sorter__ghost"
|
||||
animation="180"
|
||||
class="aksrt-sorter__list"
|
||||
@change="commit"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<div class="aksrt-sorter__item">
|
||||
<i class="fa fas fa-grip-vertical aksrt-sorter__handle" aria-hidden="true"></i>
|
||||
<i v-if="element.icon" :class="element.icon" aria-hidden="true"></i>
|
||||
<span>{{ element.label }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
<n-empty v-if="columns.disabled.length === 0" size="small" description="暂无未启用项" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, watch } from 'vue'
|
||||
import draggable from 'vuedraggable'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Object, default: () => ({}) },
|
||||
options: { type: Array, default: () => [] },
|
||||
})
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const columns = reactive({ enabled: [], disabled: [] })
|
||||
let syncing = false
|
||||
|
||||
function keysOf(value) {
|
||||
if (Array.isArray(value)) return value.map(String)
|
||||
return value && typeof value === 'object' ? Object.keys(value) : []
|
||||
}
|
||||
|
||||
function syncColumns() {
|
||||
syncing = true
|
||||
const byKey = new Map(props.options.map(option => [String(option.value), option]))
|
||||
const used = new Set()
|
||||
const normalize = keys => keys.filter(key => byKey.has(key) && !used.has(key)).map(key => {
|
||||
used.add(key)
|
||||
return byKey.get(key)
|
||||
})
|
||||
columns.enabled = normalize(keysOf(props.modelValue?.enabled))
|
||||
columns.disabled = normalize(keysOf(props.modelValue?.disabled))
|
||||
for (const option of props.options) {
|
||||
if (!used.has(String(option.value))) columns.disabled.push(option)
|
||||
}
|
||||
syncing = false
|
||||
}
|
||||
|
||||
function commit() {
|
||||
if (syncing) return
|
||||
const toMap = items => Object.fromEntries(items.map(item => [String(item.value), String(item.label)]))
|
||||
emit('update:modelValue', {
|
||||
enabled: toMap(columns.enabled),
|
||||
disabled: toMap(columns.disabled),
|
||||
})
|
||||
}
|
||||
|
||||
watch(() => [props.modelValue, props.options], syncColumns, { immediate: true, deep: true })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-sorter { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:14px; }
|
||||
.aksrt-sorter__column { min-height:210px; padding:12px; border:1px solid var(--aksrt-color-border); border-radius:6px; background:var(--aksrt-color-surface); }
|
||||
.aksrt-sorter__column--disabled { background:var(--aksrt-color-canvas); }
|
||||
.aksrt-sorter__title { display:flex; align-items:center; justify-content:space-between; margin-bottom:10px; font-weight:600; }
|
||||
.aksrt-sorter__list { display:grid; gap:7px; min-height:44px; }
|
||||
.aksrt-sorter__item { display:flex; align-items:center; gap:9px; min-height:38px; padding:7px 10px; border:1px solid var(--aksrt-color-border); border-radius:4px; background:var(--aksrt-color-surface); }
|
||||
.aksrt-sorter__handle { color:var(--aksrt-color-text-tertiary); cursor:grab; }
|
||||
.aksrt-sorter__ghost { opacity:.45; border-color:var(--aksrt-color-primary); }
|
||||
@media (max-width:720px) { .aksrt-sorter { grid-template-columns:1fr; } }
|
||||
</style>
|
||||
@@ -84,6 +84,16 @@ export const globalSection = {
|
||||
canonicalDomain: 'global',
|
||||
section: 'i18n',
|
||||
}),
|
||||
schemaPage({
|
||||
key: 'global-share',
|
||||
name: 'GlobalShare',
|
||||
label: '社交分享',
|
||||
route: '/global/share',
|
||||
title: '社交分享',
|
||||
desc: '配置分享平台、微信 JSAPI 与海报分享。',
|
||||
canonicalDomain: 'global',
|
||||
section: 'share',
|
||||
}),
|
||||
schemaPage({
|
||||
key: 'global-email',
|
||||
name: 'GlobalEmail',
|
||||
|
||||
@@ -57,16 +57,6 @@ export const postListSection = {
|
||||
canonicalDomain: 'post_list',
|
||||
section: 'feature',
|
||||
}),
|
||||
schemaPage({
|
||||
key: 'post-list-share',
|
||||
name: 'PostListShare',
|
||||
label: '分享设置',
|
||||
route: '/post-list/share',
|
||||
title: '分享设置',
|
||||
desc: '配置微信分享凭据、海报分享和社交分享选项。',
|
||||
canonicalDomain: 'post_list',
|
||||
section: 'share',
|
||||
}),
|
||||
schemaPage({
|
||||
key: 'post-list-comment',
|
||||
name: 'PostListComment',
|
||||
|
||||
@@ -14,6 +14,7 @@ import { customCodeSchema } from './custom-code.js'
|
||||
import { seoSchema } from './seo.js'
|
||||
import { codeHighlightSchema } from './code-highlight.js'
|
||||
import { systemNoticeSchema } from './system-notice.js'
|
||||
import { shareSchema } from './share.js'
|
||||
|
||||
export const globalSchemas = {
|
||||
logo: logoSchema,
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
const themeRoot = typeof window !== 'undefined' && window.aksrtAdminData?.themeUri
|
||||
? String(window.aksrtAdminData.themeUri).replace(/\/$/, '')
|
||||
: ''
|
||||
|
||||
const shareOptions = [
|
||||
{ label: 'QQ空间', value: 'qzone', icon: 'fa fab fa-qq' },
|
||||
{ label: '微博', value: 'weibo', icon: 'fa fab fa-weibo' },
|
||||
{ label: 'QQ好友', value: 'qq', icon: 'fa fab fa-qq' },
|
||||
{ label: 'Facebook', value: 'facebook', icon: 'fa fab fa-facebook-f' },
|
||||
{ label: 'X', value: 'twitter', icon: 'fa fab fa-twitter' },
|
||||
{ label: 'Telegram', value: 'telegram', icon: 'fa fab fa-telegram-plane' },
|
||||
{ label: 'WhatsApp', value: 'whatsapp', icon: 'fa fab fa-whatsapp' },
|
||||
{ label: 'LinkedIn', value: 'linkedin', icon: 'fa fab fa-linkedin-in' },
|
||||
{ label: 'Reddit', value: 'reddit', icon: 'fa fab fa-reddit-alien' },
|
||||
{ label: 'LINE', value: 'line', icon: 'fa fab fa-line' },
|
||||
{ label: 'VK', value: 'vk', icon: 'fa fab fa-vk' },
|
||||
{ label: '邮件', value: 'email', icon: 'fa fas fa-envelope' },
|
||||
{ label: '海报分享', value: 'poster', icon: 'fa fas fa-image' },
|
||||
{ label: '复制链接', value: 'copy', icon: 'fa fas fa-link' },
|
||||
]
|
||||
|
||||
const defaultShareItems = {
|
||||
enabled: { qzone: 'QQ空间', weibo: '微博', qq: 'QQ好友', poster: '海报分享', copy: '复制链接' },
|
||||
disabled: { facebook: 'Facebook', twitter: 'X', telegram: 'Telegram', whatsapp: 'WhatsApp', linkedin: 'LinkedIn', reddit: 'Reddit', line: 'LINE', vk: 'VK', email: '邮件' },
|
||||
}
|
||||
|
||||
export const shareSchema = {
|
||||
key: 'global.share',
|
||||
title: '社交分享',
|
||||
icon: 'fa fas fa-share-alt',
|
||||
description: '配置文章分享平台、微信 JSAPI 分享和海报分享。',
|
||||
defaults: {
|
||||
share_s: true,
|
||||
share_items: defaultShareItems,
|
||||
wechat_share_s: false,
|
||||
wechat_share_option: { appid: '', app_secret: '', only_logo: false },
|
||||
share_img: true,
|
||||
share_img_compatible_s: false,
|
||||
share_img_byimg: themeRoot ? `${themeRoot}/assets/img/slider-bg.jpg` : '',
|
||||
share_logo: themeRoot ? `${themeRoot}/assets/img/logo.png` : '',
|
||||
share_desc: '扫一扫 立即查看',
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'share',
|
||||
title: '社交分享',
|
||||
icon: 'fa fas fa-share-alt',
|
||||
fields: [
|
||||
{ key: 'share_s', label: '文章分享功能', type: 'switcher', default: true, help: '关闭后,在文章页面将不显示分享按钮。' },
|
||||
{
|
||||
key: 'share_items',
|
||||
label: '分享平台',
|
||||
type: 'custom',
|
||||
renderer: 'sorter',
|
||||
sanitize_type: 'array',
|
||||
default: defaultShareItems,
|
||||
options: shareOptions,
|
||||
description: '左侧启用并拖拽排序,右侧为未启用项(同时启用的数量不建议超过 6 个)。若开启海外平台,用户网络必须能正常访问对应服务。',
|
||||
},
|
||||
{ key: 'wechat_share_s', label: '微信分享有图', type: 'switcher', default: false, help: '使用微信 JSAPI 在微信 App 内分享。' },
|
||||
{
|
||||
key: 'wechat_share_hint',
|
||||
label: '',
|
||||
type: 'content',
|
||||
ui_only: true,
|
||||
dependsOn: { key: 'wechat_share_s', value: true },
|
||||
content: '<p><strong>微信分享有图配置</strong></p><p>此功能用于用户从公众号进入页面后调用微信默认分享能力,并不会额外显示分享按钮。请在公众号后台将当前站点域名配置为 JS 接口安全域名。</p><p><a href="https://mp.weixin.qq.com/" target="_blank" rel="noopener noreferrer">前往微信公众平台</a></p>',
|
||||
},
|
||||
{
|
||||
key: 'wechat_share_option',
|
||||
label: '微信分享有图配置',
|
||||
type: 'fieldset',
|
||||
default: { appid: '', app_secret: '', only_logo: false },
|
||||
dependsOn: { key: 'wechat_share_s', value: true },
|
||||
fields: [
|
||||
{ key: 'appid', label: 'AppID', type: 'input', default: '' },
|
||||
{ key: 'app_secret', label: 'AppSecret', type: 'input', default: '' },
|
||||
{ key: 'only_logo', label: '全部使用网站图标', type: 'switcher', default: false, help: '默认自动使用页面封面图;开启后所有页面均使用网站图标。图片启用防盗链时建议开启。' },
|
||||
],
|
||||
},
|
||||
{ key: 'share_img', label: '生成海报分享', type: 'switcher', default: true, help: '网站图片使用 OSS 等云存储时,请先配置允许本站 Canvas 读取图片的跨域规则。' },
|
||||
{
|
||||
key: 'share_img_warning',
|
||||
label: '',
|
||||
type: 'notice',
|
||||
ui_only: true,
|
||||
alertType: 'warning',
|
||||
content: '海报图片加载失败时,请检查图片 CDN/OSS 的跨域响应头。',
|
||||
dependsOn: { key: 'share_img', value: true },
|
||||
},
|
||||
{ key: 'share_img_compatible_s', label: '海报分享兼容模式', type: 'switcher', default: false, dependsOn: { key: 'share_img', value: true }, help: '通过服务端代理读取海报图片,改善跨域兼容性;会增加服务器图片处理开销。' },
|
||||
{ key: 'share_img_byimg', label: '海报分享默认图片', type: 'media', default: themeRoot ? `${themeRoot}/assets/img/slider-bg.jpg` : '', dependsOn: { key: 'share_img', value: true }, help: '文章没有图片时使用,建议尺寸 800×500。' },
|
||||
{ key: 'share_logo', label: '海报分享 LOGO', type: 'media', default: themeRoot ? `${themeRoot}/assets/img/logo.png` : '', dependsOn: { key: 'share_img', value: true }, help: '显示在海报底部,建议尺寸 300×100、大小 50KB 以内。' },
|
||||
{ key: 'share_desc', label: '海报分享底部文案', type: 'input', default: '扫一扫 立即查看', dependsOn: { key: 'share_img', value: true } },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import { pageSchema } from './page.js'
|
||||
import { filterSchema } from './filter.js'
|
||||
import { featureSchema } from './feature.js'
|
||||
import { paySchema } from './pay.js'
|
||||
import { shareSchema } from './share.js'
|
||||
import { commentSchema } from './comment.js'
|
||||
import { submitSchema } from './submit.js'
|
||||
|
||||
@@ -19,7 +18,6 @@ export const postListSchemas = {
|
||||
filter: filterSchema,
|
||||
feature: featureSchema,
|
||||
pay: paySchema,
|
||||
share: shareSchema,
|
||||
comment: commentSchema,
|
||||
submit: submitSchema,
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
export const shareSchema = {
|
||||
key: 'post_list.share',
|
||||
title: '分享设置',
|
||||
icon: 'fa fas fa-share-alt',
|
||||
description: '配置微信分享凭据、海报分享和社交分享选项。',
|
||||
defaults: {
|
||||
share_s: true,
|
||||
share_wechat_s: false,
|
||||
share_wechat_appid: '',
|
||||
share_wechat_app_secret: '',
|
||||
share_only_logo: false,
|
||||
share_img_compatible_s: false,
|
||||
share_img_byimg: '',
|
||||
share_logo: '',
|
||||
share_img: '',
|
||||
share_desc: '扫一扫 立即查看',
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'basic',
|
||||
title: '分享按钮',
|
||||
icon: 'fa fas fa-share-alt',
|
||||
fields: [
|
||||
{ key: 'share_s', label: '分享按钮', type: 'switch', default: true, help: '关闭后,在文章页面将不显示分享按钮,论坛及商城不受影响' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'wechat',
|
||||
title: '微信分享',
|
||||
icon: 'fa fab fa-weixin',
|
||||
fields: [
|
||||
{ key: 'share_wechat_s', label: '启用微信分享', type: 'switch', default: false, help: '启用后文章页将支持微信 JSAPI 分享' },
|
||||
{ key: 'share_wechat_appid', label: 'AppID', type: 'input', default: '', dependsOn: { key: 'share_wechat_s', value: true }, help: '微信公众号 AppID' },
|
||||
{ key: 'share_wechat_app_secret', label: 'AppSecret', type: 'input', default: '', dependsOn: { key: 'share_wechat_s', value: true }, help: '微信公众号 AppSecret(请确保 JS 安全域名已配置)' },
|
||||
{ key: 'share_only_logo', label: '仅显示 Logo', type: 'switch', default: false, help: '分享时仅显示站点 Logo' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'poster',
|
||||
title: '海报分享',
|
||||
icon: 'fa fas fa-image',
|
||||
fields: [
|
||||
{ key: 'share_img_compatible_s', label: '兼容模式', type: 'switch', default: false, help: '启用后使用兼容模式生成分享海报' },
|
||||
{ key: 'share_img_byimg', label: '海报默认图', type: 'media', default: '', help: '海报分享使用的默认背景图' },
|
||||
{ key: 'share_logo', label: '海报 LOGO', type: 'media', default: '', help: '海报上叠加的站点 LOGO' },
|
||||
{ key: 'share_img', label: '分享图片', type: 'media', default: '', help: '社交平台分享预览图' },
|
||||
{ key: 'share_desc', label: '分享描述', type: 'textarea', default: '扫一扫 立即查看', help: '海报或分享卡片的描述文案' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
Reference in new issue
Block a user