feat(share): align social sharing runtime

This commit is contained in:
kate522 committed 2026-09-27 13:49:32 +08:00
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: '海报或分享卡片的描述文案' },
],
},
],
}
-179
View File
@@ -837,185 +837,6 @@ article.article.box-body,
.action-btn i { font-size: 1.2em; }
/* ===== 分享下拉面板 ===== */
/* 确保父容器作为定位参考 */
.share-dropdown {
position: absolute;
left: 50%;
bottom: calc(100% + 8px);
transform: translateX(-50%);
z-index: 100;
/* 默认隐藏 — 最高优先级确保不被覆盖 */
display: none !important;
}
.share-dropdown[aria-hidden="false"] {
display: block !important;
animation: shareDropdownIn 0.2s ease-out forwards;
}
@keyframes shareDropdownIn {
from {
opacity: 0;
transform: translateX(-50%) translateY(8px);
}
to {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}
.share-dropdown__grid {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 16px;
background: var(--surface-page);
border: 1px solid var(--color-line);
border-radius: var(--radius-lg, 8px);
box-shadow: var(--shadow-md, 0 4px 16px rgba(0,0,0,0.1));
}
.share-item {
display: inline-flex;
flex-direction: column;
align-items: center;
gap: 4px;
padding: 8px 12px;
border: none;
border-radius: var(--radius-btn, 6px);
background: transparent;
color: var(--color-ink-2, var(--color-ink-3));
cursor: pointer;
font-size: 11px;
transition: background 0.15s, color 0.15s, transform 0.15s;
white-space: nowrap;
}
.share-item:hover {
transform: none;
}
.share-item i {
font-size: 18px;
}
.share-item[data-platform="qq"]:hover { background: #e8f4fd; color: #12b7f5; }
.share-item[data-platform="weibo"]:hover { background: #fceae9; color: #ff6a00; }
.share-item[data-platform="twitter"]:hover { background: #f0f0f0; color: #000; }
.share-item[data-platform="wechat"]:hover { background: #e6f7ec; color: #07c160; }
.share-item[data-platform="copy"]:hover { background: #f0f2f5; color: #555; }
.share-item[data-platform="poster"]:hover { background: #fff4e8; color: #f59e0b; }
/* 微信二维码浮层 */
#share-qrcode-box {
position: absolute;
bottom: calc(100% + 10px);
left: 50%;
transform: translateX(-50%);
padding: 14px;
background: var(--surface-page);
border: 1px solid var(--color-line);
border-radius: var(--radius-lg, 8px);
box-shadow: var(--shadow-md, 0 4px 16px rgba(0,0,0,0.12));
text-align: center;
z-index: 101;
pointer-events: none;
opacity: 0;
transition: opacity 0.2s ease, transform 0.2s ease;
}
#share-qrcode-box.is-visible {
pointer-events: auto;
opacity: 1;
}
#share-qrcode-image {
width: 140px;
height: 140px;
display: block;
}
/* 暗色模式 */
.theme-dark .share-dropdown__grid,
html.dark .share-dropdown__grid {
background: var(--surface-1);
border-color: var(--color-line);
box-shadow: 0 4px 16px rgba(0,0,0,0.35);
}
.theme-dark #share-qrcode-box,
html.dark #share-qrcode-box {
background: var(--surface-1);
border-color: var(--color-line);
}
.theme-dark .share-item,
html.dark .share-item {
color: var(--color-ink-2, #aaa);
}
.theme-dark .share-item[data-platform="qq"]:hover { background: rgba(18,183,245,0.12); color: #12b7f5; }
.theme-dark .share-item[data-platform="weibo"]:hover { background: rgba(255,106,0,0.12); color: #ff6a00; }
.theme-dark .share-item[data-platform="twitter"]:hover { background: rgba(255,255,255,0.08); color: #fff; }
.theme-dark .share-item[data-platform="wechat"]:hover { background: rgba(7,193,96,0.12); color: #07c160; }
.theme-dark .share-item[data-platform="copy"]:hover { background: rgba(255,255,255,0.06); color: #ccc; }
.theme-dark .share-item[data-platform="poster"]:hover { background: rgba(245,158,11,0.12); color: #f59e0b; }
/* Narrow screens: bottom sheet so dropdown never clips off-screen */
@media (max-width: 767px) {
.share-dropdown {
position: fixed;
left: 0;
right: 0;
bottom: 0;
top: auto;
transform: none;
width: 100%;
max-width: none;
z-index: 1200;
padding: 0 0 calc(12px + env(safe-area-inset-bottom, 0px));
background: transparent;
}
.share-dropdown[aria-hidden="false"] {
animation: shareSheetIn 0.22s ease-out forwards;
}
@keyframes shareSheetIn {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
.share-dropdown__grid {
justify-content: space-around;
flex-wrap: wrap;
gap: 6px;
margin: 0 10px;
padding: 14px 12px 16px;
border-radius: 14px 14px 10px 10px;
box-shadow: 0 -6px 28px rgba(0, 0, 0, 0.16);
}
.share-item {
min-width: 64px;
flex: 1 1 28%;
max-width: 33%;
}
#share-qrcode-box {
position: static;
margin: 10px auto 0;
transform: none;
}
body.aksrt-share-sheet-open {
overflow: hidden;
}
body.aksrt-share-sheet-open::before {
content: '';
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.35);
z-index: 1190;
}
}
.theme-dark .post-drop-meta__menu,
html.dark .post-drop-meta__menu {
background: var(--surface-1, #323335);
+1 -1
View File
@@ -1 +1 @@
.relative{position:relative}.flex{display:flex}.flex.ac,.ac{align-items:center}.flex.jc,.jc{justify-content:center}.flex.jsb,.jsb{justify-content:space-between}.flex.at,.at{align-items:flex-start}.flex.ae,.ae{align-items:flex-end}.flex0{flex:0 0 auto}.flex1{flex:1 1 auto;min-width:0}.flex.column{flex-direction:column}.flex.wrap{flex-wrap:wrap}.flex.flex-row,.flex-row{flex-direction:row}.flex.jse,.jse{justify-content:space-evenly}.flex.jsa,.jsa{justify-content:space-around}.flex>.flex1,.flex .flex1{flex:1;overflow:hidden;min-width:0}.relative-h{position:relative;height:100%}.padding-10{padding:10px}.font-bold{font-weight:700}.mt6{margin-top:6px}.gutters-5{margin:-5px}.gutters-5>*{padding:5px}@media(max-width:767px){.flex.flex-row.flex-col-sm-2{flex-wrap:wrap}.flex.flex-row.flex-col-sm-2>*{flex-basis:50%}}.clearfix:before,.clearfix:after{content:"";display:table}.clearfix:after{clear:both}.abs-right{position:absolute;right:0;top:0}.abs-center{position:absolute;left:0;width:100%;top:50%;right:.7em;transform:translateY(-50%);z-index:1}.abs-center.left-top,.abs-center.right-top{transform:unset;top:0}.abs-center.right-top{left:auto;width:auto;text-align:right}.abs-center.left-top{text-align:left}.abs-center.conter-bottom,.abs-center.conter-conter{text-align:center}.abs-center.right-top,.abs-center.right-bottom,.abs-center.right-conter{text-align:right}.abs-center.conter-bottom,.abs-center.right-bottom,.abs-center.left-bottom{top:auto;transform:unset;bottom:0}.abs-center.left-bottom{left:0;right:auto;text-align:left}.abs-center.right-bottom,.abs-center.right-conter{left:auto;right:0;text-align:right}.graphic,.page-cover{text-shadow:0 0 5px rgba(0,0,0,.2);--main-color: #fff;color:#fff;position:relative;overflow:hidden;box-shadow:0 0 10px var(--main-shadow, rgba(116, 116, 116, .08));padding:0 0 70%;border-radius:var(--main-radius, var(--radius, 8px))}.graphic>.fit-cover,.graphic>img.fit-cover{position:absolute;width:100%;height:100%;object-fit:cover;inset:0}.fit-cover{object-fit:cover;max-width:100%}.title-h-left{position:relative;padding-bottom:8px}.title-h-left:before{transition:.4s;transform-origin:left;position:absolute;content:"";width:40px;height:3px;background:var(--theme-color, var(--focus-color, #f04494));left:0;bottom:3px;border-radius:5px;box-shadow:1px 1px 3px -1px var(--theme-color, var(--focus-color, #f04494))}.line-form-line,.line-form-line:before{position:absolute;bottom:0;left:0;height:1px;width:100%;transition:.8s;background:var(--main-border-color, rgba(50, 50, 50, .06))}.line-form-line:before{content:"";width:0;background:var(--focus-color, #f04494)}.avatar-img{position:relative;display:inline-block;flex-shrink:0;--this-size: 38px;width:var(--this-size);height:var(--this-size)}.avatar-img .avatar,.avatar-img img{width:100%;height:100%;border-radius:50%;object-fit:cover}.avatar-lg{--this-size: 80px}.avatar-md{--this-size: 55px}.avatar-mini{--this-size: 20px}.shrink0{flex-shrink:0}.opacity8{opacity:.8}.muted-color{color:var(--muted-color, var(--color-ink-3))}.muted-2-color{color:var(--muted-2-color, var(--color-ink-4))}.muted-3-color{color:var(--muted-3-color, #b1b1b1)}.muted-4-color{color:var(--muted-4-color, #d2d2d2)}.text-center{text-align:center}.em09{font-size:.9em}.em12{font-size:1.2em}.px12-sm{font-size:12px}.ml3{margin-left:3px}.ml6{margin-left:6px}.ml10{margin-left:10px}.mr3{margin-right:3px}.mr6{margin-right:6px}.mt10{margin-top:10px}.mt20{margin-top:20px}.mb10{margin-bottom:10px}.mb20{margin-bottom:20px}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}.text-ellipsis-2{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word}.main-bg{background-color:var(--main-bg-color, var(--surface-1))}.separator{position:relative;letter-spacing:.08em}.icon-circle+.icon-circle:before{content:"·";display:inline-block;margin:0 .45em;opacity:.55}.article-avatar name,.article-avatar .name,.user-name{display:inline-flex;align-items:center;gap:4px;min-width:0;max-width:100%}.pull-right{float:right}.pull-left{float:left}body.single main.container .content-layout{display:block;min-width:0}body.single main.container .content-layout>*{min-width:0;max-width:100%}body.single main.container .content-layout>.breadcrumb,body.single main.container .content-layout>ul.breadcrumb,body.single main.container .content-layout>.breadcrumb-nav,body.single main.container .content-layout>.breadcrumbs-wrap{position:relative;z-index:1;margin-top:0;margin-bottom:12px;padding-top:2px}body.single .sidebar .aksrt-panel,body.single .sidebar .widget{margin-bottom:15px}body.single main.container{box-sizing:border-box}.article,article.article.box-body,.article.main-bg.box-body{padding:0 30px;overflow:hidden}@media(max-width:767px){.article,article.article.box-body,.article.main-bg.box-body{padding:0 15px}.article-header .article-title{font-size:18px}}.entry-header,.article-header{margin-bottom:10px;padding-bottom:0;border-bottom:none;background:tranLine truncated
.relative{position:relative}.flex{display:flex}.flex.ac,.ac{align-items:center}.flex.jc,.jc{justify-content:center}.flex.jsb,.jsb{justify-content:space-between}.flex.at,.at{align-items:flex-start}.flex.ae,.ae{align-items:flex-end}.flex0{flex:0 0 auto}.flex1{flex:1 1 auto;min-width:0}.flex.column{flex-direction:column}.flex.wrap{flex-wrap:wrap}.flex.flex-row,.flex-row{flex-direction:row}.flex.jse,.jse{justify-content:space-evenly}.flex.jsa,.jsa{justify-content:space-around}.flex>.flex1,.flex .flex1{flex:1;overflow:hidden;min-width:0}.relative-h{position:relative;height:100%}.padding-10{padding:10px}.font-bold{font-weight:700}.mt6{margin-top:6px}.gutters-5{margin:-5px}.gutters-5>*{padding:5px}@media(max-width:767px){.flex.flex-row.flex-col-sm-2{flex-wrap:wrap}.flex.flex-row.flex-col-sm-2>*{flex-basis:50%}}.clearfix:before,.clearfix:after{content:"";display:table}.clearfix:after{clear:both}.abs-right{position:absolute;right:0;top:0}.abs-center{position:absolute;left:0;width:100%;top:50%;right:.7em;transform:translateY(-50%);z-index:1}.abs-center.left-top,.abs-center.right-top{transform:unset;top:0}.abs-center.right-top{left:auto;width:auto;text-align:right}.abs-center.left-top{text-align:left}.abs-center.conter-bottom,.abs-center.conter-conter{text-align:center}.abs-center.right-top,.abs-center.right-bottom,.abs-center.right-conter{text-align:right}.abs-center.conter-bottom,.abs-center.right-bottom,.abs-center.left-bottom{top:auto;transform:unset;bottom:0}.abs-center.left-bottom{left:0;right:auto;text-align:left}.abs-center.right-bottom,.abs-center.right-conter{left:auto;right:0;text-align:right}.graphic,.page-cover{text-shadow:0 0 5px rgba(0,0,0,.2);--main-color: #fff;color:#fff;position:relative;overflow:hidden;box-shadow:0 0 10px var(--main-shadow, rgba(116, 116, 116, .08));padding:0 0 70%;border-radius:var(--main-radius, var(--radius, 8px))}.graphic>.fit-cover,.graphic>img.fit-cover{position:absolute;width:100%;height:100%;object-fit:cover;inset:0}.fit-cover{object-fit:cover;max-width:100%}.title-h-left{position:relative;padding-bottom:8px}.title-h-left:before{transition:.4s;transform-origin:left;position:absolute;content:"";width:40px;height:3px;background:var(--theme-color, var(--focus-color, #f04494));left:0;bottom:3px;border-radius:5px;box-shadow:1px 1px 3px -1px var(--theme-color, var(--focus-color, #f04494))}.line-form-line,.line-form-line:before{position:absolute;bottom:0;left:0;height:1px;width:100%;transition:.8s;background:var(--main-border-color, rgba(50, 50, 50, .06))}.line-form-line:before{content:"";width:0;background:var(--focus-color, #f04494)}.avatar-img{position:relative;display:inline-block;flex-shrink:0;--this-size: 38px;width:var(--this-size);height:var(--this-size)}.avatar-img .avatar,.avatar-img img{width:100%;height:100%;border-radius:50%;object-fit:cover}.avatar-lg{--this-size: 80px}.avatar-md{--this-size: 55px}.avatar-mini{--this-size: 20px}.shrink0{flex-shrink:0}.opacity8{opacity:.8}.muted-color{color:var(--muted-color, var(--color-ink-3))}.muted-2-color{color:var(--muted-2-color, var(--color-ink-4))}.muted-3-color{color:var(--muted-3-color, #b1b1b1)}.muted-4-color{color:var(--muted-4-color, #d2d2d2)}.text-center{text-align:center}.em09{font-size:.9em}.em12{font-size:1.2em}.px12-sm{font-size:12px}.ml3{margin-left:3px}.ml6{margin-left:6px}.ml10{margin-left:10px}.mr3{margin-right:3px}.mr6{margin-right:6px}.mt10{margin-top:10px}.mt20{margin-top:20px}.mb10{margin-bottom:10px}.mb20{margin-bottom:20px}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}.text-ellipsis-2{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word}.main-bg{background-color:var(--main-bg-color, var(--surface-1))}.separator{position:relative;letter-spacing:.08em}.icon-circle+.icon-circle:before{content:"·";display:inline-block;margin:0 .45em;opacity:.55}.article-avatar name,.article-avatar .name,.user-name{display:inline-flex;align-items:center;gap:4px;min-width:0;max-width:100%}.pull-right{float:right}.pull-left{float:left}body.single main.container .content-layout{display:block;min-width:0}body.single main.container .content-layout>*{min-width:0;max-width:100%}body.single main.container .content-layout>.breadcrumb,body.single main.container .content-layout>ul.breadcrumb,body.single main.container .content-layout>.breadcrumb-nav,body.single main.container .content-layout>.breadcrumbs-wrap{position:relative;z-index:1;margin-top:0;margin-bottom:12px;padding-top:2px}body.single .sidebar .aksrt-panel,body.single .sidebar .widget{margin-bottom:15px}body.single main.container{box-sizing:border-box}.article,article.article.box-body,.article.main-bg.box-body{padding:0 30px;overflow:hidden}@media(max-width:767px){.article,article.article.box-body,.article.main-bg.box-body{padding:0 15px}.article-header .article-title{font-size:18px}}.entry-header,.article-header{margin-bottom:10px;padding-bottom:0;border-bottom:none;background:tranLine truncated
@@ -157,185 +157,6 @@
.action-btn i { font-size: 1.2em; }
/* ===== 分享下拉面板 ===== */
/* 确保父容器作为定位参考 */
.share-dropdown {
position: absolute;
left: 50%;
bottom: calc(100% + 8px);
transform: translateX(-50%);
z-index: 100;
/* 默认隐藏 — 最高优先级确保不被覆盖 */
display: none !important;
}
.share-dropdown[aria-hidden="false"] {
display: block !important;
animation: shareDropdownIn 0.2s ease-out forwards;
}
@keyframes shareDropdownIn {
from {
opacity: 0;
transform: translateX(-50%) translateY(8px);
}
to {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}
.share-dropdown__grid {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 16px;
background: var(--surface-page);
border: 1px solid var(--color-line);
border-radius: var(--radius-lg, 8px);
box-shadow: var(--shadow-md, 0 4px 16px rgba(0,0,0,0.1));
}
.share-item {
display: inline-flex;
flex-direction: column;
align-items: center;
gap: 4px;
padding: 8px 12px;
border: none;
border-radius: var(--radius-btn, 6px);
background: transparent;
color: var(--color-ink-2, var(--color-ink-3));
cursor: pointer;
font-size: 11px;
transition: background 0.15s, color 0.15s, transform 0.15s;
white-space: nowrap;
}
.share-item:hover {
transform: none;
}
.share-item i {
font-size: 18px;
}
.share-item[data-platform="qq"]:hover { background: #e8f4fd; color: #12b7f5; }
.share-item[data-platform="weibo"]:hover { background: #fceae9; color: #ff6a00; }
.share-item[data-platform="twitter"]:hover { background: #f0f0f0; color: #000; }
.share-item[data-platform="wechat"]:hover { background: #e6f7ec; color: #07c160; }
.share-item[data-platform="copy"]:hover { background: #f0f2f5; color: #555; }
.share-item[data-platform="poster"]:hover { background: #fff4e8; color: #f59e0b; }
/* 微信二维码浮层 */
#share-qrcode-box {
position: absolute;
bottom: calc(100% + 10px);
left: 50%;
transform: translateX(-50%);
padding: 14px;
background: var(--surface-page);
border: 1px solid var(--color-line);
border-radius: var(--radius-lg, 8px);
box-shadow: var(--shadow-md, 0 4px 16px rgba(0,0,0,0.12));
text-align: center;
z-index: 101;
pointer-events: none;
opacity: 0;
transition: opacity 0.2s ease, transform 0.2s ease;
}
#share-qrcode-box.is-visible {
pointer-events: auto;
opacity: 1;
}
#share-qrcode-image {
width: 140px;
height: 140px;
display: block;
}
/* 暗色模式 */
.theme-dark .share-dropdown__grid,
html.dark .share-dropdown__grid {
background: var(--surface-1);
border-color: var(--color-line);
box-shadow: 0 4px 16px rgba(0,0,0,0.35);
}
.theme-dark #share-qrcode-box,
html.dark #share-qrcode-box {
background: var(--surface-1);
border-color: var(--color-line);
}
.theme-dark .share-item,
html.dark .share-item {
color: var(--color-ink-2, #aaa);
}
.theme-dark .share-item[data-platform="qq"]:hover { background: rgba(18,183,245,0.12); color: #12b7f5; }
.theme-dark .share-item[data-platform="weibo"]:hover { background: rgba(255,106,0,0.12); color: #ff6a00; }
.theme-dark .share-item[data-platform="twitter"]:hover { background: rgba(255,255,255,0.08); color: #fff; }
.theme-dark .share-item[data-platform="wechat"]:hover { background: rgba(7,193,96,0.12); color: #07c160; }
.theme-dark .share-item[data-platform="copy"]:hover { background: rgba(255,255,255,0.06); color: #ccc; }
.theme-dark .share-item[data-platform="poster"]:hover { background: rgba(245,158,11,0.12); color: #f59e0b; }
/* Narrow screens: bottom sheet so dropdown never clips off-screen */
@media (max-width: 767px) {
.share-dropdown {
position: fixed;
left: 0;
right: 0;
bottom: 0;
top: auto;
transform: none;
width: 100%;
max-width: none;
z-index: 1200;
padding: 0 0 calc(12px + env(safe-area-inset-bottom, 0px));
background: transparent;
}
.share-dropdown[aria-hidden="false"] {
animation: shareSheetIn 0.22s ease-out forwards;
}
@keyframes shareSheetIn {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
.share-dropdown__grid {
justify-content: space-around;
flex-wrap: wrap;
gap: 6px;
margin: 0 10px;
padding: 14px 12px 16px;
border-radius: 14px 14px 10px 10px;
box-shadow: 0 -6px 28px rgba(0, 0, 0, 0.16);
}
.share-item {
min-width: 64px;
flex: 1 1 28%;
max-width: 33%;
}
#share-qrcode-box {
position: static;
margin: 10px auto 0;
transform: none;
}
body.aksrt-share-sheet-open {
overflow: hidden;
}
body.aksrt-share-sheet-open::before {
content: '';
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.35);
z-index: 1190;
}
}
.theme-dark .post-drop-meta__menu,
html.dark .post-drop-meta__menu {
background: var(--surface-1, #323335);
+28
View File
@@ -8887,6 +8887,26 @@ img.alignleft {
margin-top: 10px;
}
.share-btn icon {
width: 36px;
height: 36px;
display: inline-flex;
border-radius: 100%;
background: var(--main-border-color);
justify-content: center;
align-items: center;
font-size: 22px;
flex-shrink: 0;
}
.share-btn icon .icon {
margin: auto !important;
}
.share-button .share-btn icon {
margin-right: 10px;
}
.share-button .share-btn .icon {
width: 1em;
font-size: inherit;
@@ -8907,6 +8927,14 @@ img.alignleft {
align-items: center;
}
.horizontal .share-btn icon {
display: flex;
margin-bottom: 6px;
width: 50px;
height: 50px;
font-size: 28px;
}
.drop-newadd {
border-radius: var(--main-radius);
}
+1 -1
View File
@@ -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
-305
View File
@@ -1,305 +0,0 @@
/**
* AKSRT Post Share
*
* Handles share dropdowns, social share targets, copy link,
* and WeChat bridge handoff with QR fallback.
*
* @package AKSRT
*/
(function () {
'use strict';
function getMetaContent(selectors) {
for (var i = 0; i < selectors.length; i += 1) {
var node = document.querySelector(selectors[i]);
var content = node ? node.getAttribute('content') : '';
if (content) return content;
}
return '';
}
function getPlatform(node) {
if (!node) return '';
return node.getAttribute('data-share-platform') || node.getAttribute('data-platform') || '';
}
function resolvePayload(node) {
// Unified payload via the shared builder; fall back to local resolution
// only if the builder script is unavailable.
if (window.AksrtShareData && typeof window.AksrtShareData.resolve === 'function') {
return window.AksrtShareData.resolve(node);
}
var dropdown = document.getElementById('share-dropdown');
var source = node && typeof node.closest === 'function'
? node.closest('[data-share-url], [data-share-title], [data-share-desc], [data-share-image]')
: null;
var context = source || dropdown;
function readAttr(name) {
return (node && node.getAttribute(name)) || (context && context.getAttribute(name)) || '';
}
return {
title: readAttr('data-share-title') || document.title,
url: readAttr('data-share-url') || window.location.href,
desc: readAttr('data-share-desc') || getMetaContent(['meta[property="og:description"]', 'meta[name="description"]']),
text: readAttr('data-share-text') || '',
image: readAttr('data-share-image') || getMetaContent(['meta[property="og:image"]']),
qrcode: readAttr('data-share-qrcode') || ''
};
}
function openWindow(url) {
if (!url) return;
window.open(url, '_blank', 'width=640,height=560,scrollbars=yes,resizable=yes');
}
function buildShareUrl(platform, payload) {
switch (platform) {
case 'weibo':
return 'https://service.weibo.com/share/share.php?url=' + encodeURIComponent(payload.url) + '&title=' + encodeURIComponent(payload.title + (payload.text ? ' ' + payload.text : ''));
case 'qq':
return 'https://connect.qq.com/widget/shareqq/index.html?url=' + encodeURIComponent(payload.url) + '&title=' + encodeURIComponent(payload.title) + '&desc=' + encodeURIComponent(payload.desc || payload.text || '');
case 'twitter':
return 'https://x.com/intent/tweet?url=' + encodeURIComponent(payload.url) + '&text=' + encodeURIComponent(payload.title);
case 'telegram':
return 'https://t.me/share/url?url=' + encodeURIComponent(payload.url) + '&text=' + encodeURIComponent(payload.title);
case 'linkedin':
return 'https://www.linkedin.com/sharing/share-offsite/?url=' + encodeURIComponent(payload.url);
default:
return '';
}
}
function fallbackCopy(text) {
var textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.cssText = 'position:fixed;left:-9999px;top:-9999px;opacity:0;';
textarea.setAttribute('readonly', '');
document.body.appendChild(textarea);
try {
textarea.select();
textarea.setSelectionRange(0, textarea.value.length);
if (!document.execCommand('copy')) {
throw new Error('copy command failed');
}
} finally {
document.body.removeChild(textarea);
}
}
function copyLink(text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
return navigator.clipboard.writeText(text).catch(function () {
fallbackCopy(text);
});
}
try {
fallbackCopy(text);
return Promise.resolve();
} catch (error) {
return Promise.reject(error);
}
}
function initShareDropdown() {
var dropdown = document.getElementById('share-dropdown');
var triggers = Array.prototype.slice.call(document.querySelectorAll('#action-share-trigger, .action-share[data-type="share"], .footer-tabbar .action-share')).filter(function (el, idx, arr) { return arr.indexOf(el) === idx; });
var trigger = triggers[0] || null;
if (!trigger || !dropdown || dropdown.dataset.aksrtShareBound === '1') return;
dropdown.dataset.aksrtShareBound = '1';
var qrBox = document.getElementById('share-qrcode-box');
var qrImage = document.getElementById('share-qrcode-image');
function closeDropdown() {
dropdown.setAttribute('aria-hidden', 'true');
triggers.forEach(function (t) { t.setAttribute('aria-expanded', 'false'); });
document.body.classList.remove('aksrt-share-sheet-open');
if (window.AksrtShare && typeof window.AksrtShare.hideQrFallback === 'function') {
window.AksrtShare.hideQrFallback(qrBox);
} else if (qrBox) {
qrBox.classList.remove('is-visible');
qrBox.setAttribute('aria-hidden', 'true');
}
}
function toggleDropdown() {
var isOpen = dropdown.getAttribute('aria-hidden') === 'false';
if (isOpen) {
closeDropdown();
return;
}
dropdown.setAttribute('aria-hidden', 'false');
triggers.forEach(function (t) { t.setAttribute('aria-expanded', 'true'); });
if (window.matchMedia && window.matchMedia('(max-width: 767px)').matches) {
document.body.classList.add('aksrt-share-sheet-open');
}
}
trigger.addEventListener('click', function (event) {
event.preventDefault();
event.stopPropagation();
toggleDropdown();
});
triggers.slice(1).forEach(function (extraTrigger) {
extraTrigger.addEventListener('click', function (event) {
event.preventDefault();
event.stopPropagation();
// Reuse primary trigger click semantics by synthetic toggle
trigger.click();
});
});
document.addEventListener('click', function (event) {
if (!trigger.contains(event.target) && !dropdown.contains(event.target)) {
closeDropdown();
}
});
document.addEventListener('keydown', function (event) {
if (event.key === 'Escape') {
closeDropdown();
}
});
dropdown.addEventListener('click', function (event) {
var item = event.target.closest('.share-item');
if (!item) return;
event.preventDefault();
event.stopPropagation();
handleShareAction(item, {
qrBox: qrBox,
qrImage: qrImage,
closeDropdown: closeDropdown,
withinDropdown: true
});
});
}
function handleWeixinShare(node, payload, options) {
options = options || {};
var share = window.AksrtShare;
if (!share || typeof share.openWeixinShare !== 'function') {
notyf('微信分享模块未加载', 'danger');
return Promise.resolve({ mode: 'error' });
}
if (
options.qrBox &&
options.qrBox.classList.contains('is-visible') &&
(!share.isWeixinEnv || !share.isWeixinEnv())
) {
share.hideQrFallback(options.qrBox);
return Promise.resolve({ mode: 'closed' });
}
return share.openWeixinShare({
title: payload.title,
url: payload.url,
desc: payload.desc || payload.text || '',
image: payload.image,
qrBox: options.qrBox || null,
qrImage: options.qrImage || null
}).then(function (result) {
if (result && result.message) {
notyf(result.message, result.mode === 'error' ? 'danger' : (result.mode === 'bridge' ? 'success' : 'info'));
}
return result;
});
}
function handleShareAction(node, options) {
var platform = getPlatform(node);
if (!platform) return Promise.resolve({ mode: 'noop' });
var payload = resolvePayload(node);
if (!payload.url) return Promise.resolve({ mode: 'noop' });
if (platform === 'copy') {
return copyLink(payload.url).then(function () {
notyf('链接已复制', 'success');
if (options && typeof options.closeDropdown === 'function') {
options.closeDropdown();
}
return { mode: 'copy' };
}).catch(function () {
notyf('复制失败,请手动复制链接', 'danger');
return { mode: 'error' };
});
}
if (platform === 'wechat') {
return handleWeixinShare(node, payload, options).then(function (result) {
if (result && result.mode !== 'qr' && result.mode !== 'error' && options && typeof options.closeDropdown === 'function') {
options.closeDropdown();
}
return result;
});
}
if (platform === 'poster') {
if (window.AksrtPosterShare && typeof window.AksrtPosterShare.open === 'function') {
window.AksrtPosterShare.open(payload);
if (options && typeof options.closeDropdown === 'function') options.closeDropdown();
return Promise.resolve({ mode: 'poster' });
}
notyf('海报模块未加载', 'danger');
return Promise.resolve({ mode: 'error' });
}
var shareUrl = buildShareUrl(platform, payload);
if (!shareUrl) {
return Promise.resolve({ mode: 'noop' });
}
openWindow(shareUrl);
if (options && typeof options.closeDropdown === 'function') {
options.closeDropdown();
}
return Promise.resolve({ mode: 'window' });
}
function bindGenericShareButtons() {
document.addEventListener('click', function (event) {
var node = event.target.closest('[data-share-platform]');
if (!node || node.closest('#share-dropdown')) return;
event.preventDefault();
handleShareAction(node, {});
});
document.addEventListener('click', function (event) {
var node = event.target.closest('[data-share-copy]');
if (!node) return;
event.preventDefault();
var url = node.getAttribute('data-share-url') || window.location.href;
copyLink(url).then(function () {
notyf('链接已复制', 'success');
}).catch(function () {
notyf('复制失败,请手动复制链接', 'danger');
});
});
}
function init() {
if (document.documentElement.dataset.aksrtPostShareInitialized === '1') return;
document.documentElement.dataset.aksrtPostShareInitialized = '1';
initShareDropdown();
bindGenericShareButtons();
}
jQuery(document).off('auto_fun.aksrt-post-share').on('auto_fun.aksrt-post-share', function () { init(document); });
})();
+180 -387
View File
@@ -1,406 +1,199 @@
/**
* AKSRT Poster Share — 海报分享
*
* 生成文章分享海报图片,支持下载和分享。
* 使用 Canvas API 绘制,无需外部依赖。
* Reference-theme poster sharing runtime.
*
* @package AKSRT
*/
(function () {
(function (window, document, $) {
'use strict';
/**
* 初始化海报分享
*
* @param {Object} options 配置项
*/
function PosterShare(options) {
this.options = Object.assign({
triggerSelector: '[data-aksrt-poster]',
title: '',
excerpt: '',
author: '',
date: '',
siteName: '',
qrCodeUrl: '',
width: 750,
height: 1334,
backgroundColor: '#ffffff',
textColor: '#333333',
accentColor: '#1890ff',
}, options || {});
var WIDTH = 768;
var HEIGHT = 1168;
var FONT = '-apple-system,BlinkMacSystemFont,"Segoe UI","Microsoft YaHei",Arial,sans-serif';
this.init();
function notice(message, type) {
if (typeof window.notyf === 'function') window.notyf(message, type || '');
}
PosterShare.prototype.init = function () {
var self = this;
function ajaxUrl() {
return (window.aksrtData && window.aksrtData.ajaxUrl) || (window._win && window._win.ajax_url) || '';
}
document.querySelectorAll(this.options.triggerSelector).forEach(function (btn) {
btn.addEventListener('click', function (e) {
e.preventDefault();
function nonce() {
return (window.aksrtData && window.aksrtData.nonce) || '';
}
// Unified payload first (title/excerpt/author/date/qr), then allow an
// explicit data-aksrt-poster JSON blob to override individual fields.
var resolved = (window.AksrtShareData && typeof window.AksrtShareData.resolve === 'function')
? window.AksrtShareData.resolve(btn)
: {};
function loadImage(sources) {
sources = sources.filter(Boolean);
return new Promise(function (resolve, reject) {
var index = 0;
function next() {
if (index >= sources.length) { reject(new Error('image unavailable')); return; }
var image = new Image();
image.crossOrigin = 'anonymous';
image.onload = function () { resolve(image); };
image.onerror = function () { index += 1; next(); };
image.src = sources[index];
}
next();
});
}
var config = {};
function qrCodeDataUrl(text) {
return new Promise(function (resolve) {
if (!text || typeof window.tbquire !== 'function') { resolve(''); return; }
window.tbquire(['qrcode'], function () {
var host = $('<div>').css({ position: 'fixed', left: '-9999px', top: '-9999px' }).appendTo('body');
try {
config = JSON.parse(btn.dataset.aksrtPoster || '{}');
} catch (err) {}
var merged = Object.assign({}, self.options, {
title: resolved.title || self.options.title,
excerpt: resolved.desc || self.options.excerpt,
author: resolved.author || self.options.author,
date: resolved.date || self.options.date,
siteName: resolved.siteName || self.options.siteName,
qrCodeUrl: resolved.qrcode || self.options.qrCodeUrl
}, config);
self.generate(merged);
host.qrcode({ width: 160, height: 160, correctLevel: 0, text: text, background: '#fff', foreground: '#333' });
var canvas = host.find('canvas').get(0);
resolve(canvas ? canvas.toDataURL('image/png') : '');
} catch (error) {
resolve('');
} finally {
host.remove();
}
});
});
};
var WEEKDAYS = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
var FONT_FAMILY = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
PosterShare.prototype.generate = function (config) {
var self = this;
config.width = config.width || 768;
config.height = config.height || 1168;
var overlay = this.createOverlay();
document.body.appendChild(overlay);
var canvas = document.createElement('canvas');
canvas.width = config.width;
canvas.height = config.height;
var ctx = canvas.getContext('2d');
// Background
ctx.fillStyle = config.backgroundColor;
ctx.fillRect(0, 0, config.width, config.height);
var coverSources = [config.coverImg, config.banner, config.banner_default, config.banner_spare].filter(Boolean);
if (coverSources.length) {
self.loadImageWithRetry(coverSources, 0, function (coverImg) {
self.drawPosterContent(ctx, canvas, config, overlay, coverImg);
}, function () {
self.drawPosterContent(ctx, canvas, config, overlay, null);
});
} else {
self.drawPosterContent(ctx, canvas, config, overlay, null);
}
};
PosterShare.prototype.loadImageWithRetry = function (sources, idx, onLoad, onFail) {
if (idx >= sources.length) { onFail(); return; }
var self = this;
var retries = 0;
var src = sources[idx];
function tryLoad() {
var img = new Image();
img.crossOrigin = 'anonymous';
img.onload = function () { onLoad(img); };
img.onerror = function () {
retries++;
if (retries < 3) { tryLoad(); }
else { self.loadImageWithRetry(sources, idx + 1, onLoad, onFail); }
};
img.src = src;
}
tryLoad();
};
PosterShare.prototype.drawPosterContent = function (ctx, canvas, config, overlay, coverImg) {
var self = this;
var W = config.width;
var isProduct = config.isProduct;
var y = 0;
// Cover image area (top)
var coverH = isProduct ? W : Math.round(W * 0.52);
if (coverImg) {
var imgRatio = coverImg.width / coverImg.height;
var drawW, drawH, drawX, drawY;
if (imgRatio > W / coverH) {
drawH = coverH; drawW = coverH * imgRatio;
drawX = -(drawW - W) / 2; drawY = 0;
} else {
drawW = W; drawH = W / imgRatio;
drawX = 0; drawY = -(drawH - coverH) / 2;
}
ctx.drawImage(coverImg, drawX, drawY, drawW, drawH);
} else {
ctx.fillStyle = config.accentColor;
ctx.fillRect(0, 0, W, coverH);
}
// Date/weekday badge (top-left)
if (config.date) {
var d = new Date(config.date);
if (!isNaN(d.getTime())) {
var weekday = WEEKDAYS[d.getDay()];
var dateStr = (d.getMonth() + 1) + '/' + d.getDate();
ctx.fillStyle = 'rgba(0,0,0,0.55)';
ctx.beginPath();
ctx.roundRect ? ctx.roundRect(20, 20, 80, 65, 8) : ctx.rect(20, 20, 80, 65);
ctx.fill();
ctx.fillStyle = '#fff';
ctx.font = 'bold 20px ' + FONT_FAMILY;
ctx.textAlign = 'center';
ctx.fillText(weekday, 60, 47);
ctx.font = '16px ' + FONT_FAMILY;
ctx.fillText(dateStr, 60, 72);
ctx.textAlign = 'left';
}
}
y = coverH + 36;
// Title with vertical accent line
ctx.fillStyle = config.accentColor;
ctx.fillRect(50, y - 6, 4, 44);
ctx.fillStyle = config.textColor;
ctx.font = 'bold 32px ' + FONT_FAMILY;
var titleLines = this.wrapText(ctx, config.title, W - 140);
titleLines.slice(0, 3).forEach(function (line) {
ctx.fillText(line, 66, y + 28);
y += 44;
});
y += 16;
// Product price section
if (isProduct && config.price !== undefined) {
ctx.font = 'bold 36px ' + FONT_FAMILY;
ctx.fillStyle = '#e74c3c';
var priceLabel = config.priceUnit === 'points' ? (config.price + ' 积分') : ('¥' + config.price);
ctx.fillText(priceLabel, 60, y + 10);
if (config.originalPrice && config.originalPrice > config.price) {
var priceW = ctx.measureText(priceLabel).width;
ctx.font = '22px ' + FONT_FAMILY;
ctx.fillStyle = '#999';
var origText = '¥' + config.originalPrice;
ctx.fillText(origText, 60 + priceW + 16, y + 10);
var origW = ctx.measureText(origText).width;
ctx.strokeStyle = '#999';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(60 + priceW + 16, y + 5);
ctx.lineTo(60 + priceW + 16 + origW, y + 5);
ctx.stroke();
}
if (config.priceTag) {
ctx.font = '18px ' + FONT_FAMILY;
ctx.fillStyle = '#fff';
var tagW = ctx.measureText(config.priceTag).width + 16;
ctx.fillStyle = config.accentColor;
ctx.beginPath();
ctx.roundRect ? ctx.roundRect(W - 60 - tagW, y - 10, tagW, 30, 4) : ctx.rect(W - 60 - tagW, y - 10, tagW, 30);
ctx.fill();
ctx.fillStyle = '#fff';
ctx.textAlign = 'center';
ctx.fillText(config.priceTag, W - 60 - tagW / 2, y + 10);
ctx.textAlign = 'left';
}
y += 48;
}
// Excerpt
if (config.excerpt) {
ctx.fillStyle = '#666';
ctx.font = '22px ' + FONT_FAMILY;
var excerptLines = this.wrapText(ctx, config.excerpt, W - 120);
excerptLines.slice(0, 5).forEach(function (line) {
ctx.fillText(line, 60, y + 4);
y += 34;
});
if (excerptLines.length > 5) {
ctx.fillText('...', 60, y + 4);
y += 34;
}
}
y += 20;
// Author & date
ctx.fillStyle = '#999';
ctx.font = '18px ' + FONT_FAMILY;
if (config.author) { ctx.fillText(config.author, 60, y); y += 26; }
// Bottom: site name + QR code
var bottomY = config.height - 110;
ctx.strokeStyle = '#eee';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(50, bottomY);
ctx.lineTo(W - 50, bottomY);
ctx.stroke();
if (config.siteName) {
ctx.fillStyle = '#999';
ctx.font = '18px ' + FONT_FAMILY;
ctx.fillText(config.siteName, 60, bottomY + 36);
}
if (config.qrCodeUrl) {
var qrImg = new Image();
qrImg.crossOrigin = 'anonymous';
qrImg.onload = function () {
var qrSize = 90;
var qrX = W - 50 - qrSize;
var qrY = bottomY + 6;
ctx.drawImage(qrImg, qrX, qrY, qrSize, qrSize);
ctx.fillStyle = '#999';
ctx.font = '13px ' + FONT_FAMILY;
ctx.textAlign = 'center';
ctx.fillText('扫码阅读', qrX + qrSize / 2, qrY + qrSize + 16);
ctx.textAlign = 'left';
self.showPoster(canvas, overlay);
};
qrImg.onerror = function () { self.showPoster(canvas, overlay); };
qrImg.src = config.qrCodeUrl;
} else {
self.showPoster(canvas, overlay);
}
};
PosterShare.prototype.showPoster = function (canvas, overlay) {
// 创建海报容器
var posterWrap = document.createElement('div');
posterWrap.className = 'aksrt-poster-share__wrap';
var img = document.createElement('img');
img.src = canvas.toDataURL('image/png');
img.className = 'aksrt-poster-share__image';
img.alt = '分享海报';
posterWrap.appendChild(img);
// 操作按钮
var actions = document.createElement('div');
actions.className = 'aksrt-poster-share__actions';
var downloadBtn = document.createElement('button');
downloadBtn.type = 'button';
downloadBtn.className = 'aksrt-poster-share__btn';
downloadBtn.innerHTML = '<i class="fa fas fa-download"></i> 保存图片';
downloadBtn.addEventListener('click', function () {
var link = document.createElement('a');
var postId = (window.aksrtData && aksrtData.postId) || Date.now();
link.download = 'poster_' + postId + '.png';
link.href = canvas.toDataURL('image/png');
link.click();
});
actions.appendChild(downloadBtn);
var closeBtn = document.createElement('button');
closeBtn.type = 'button';
closeBtn.className = 'aksrt-poster-share__btn aksrt-poster-share__btn--secondary';
closeBtn.textContent = '关闭';
closeBtn.addEventListener('click', function () {
overlay.remove();
});
actions.appendChild(closeBtn);
posterWrap.appendChild(actions);
// 替换加载状态
overlay.innerHTML = '';
overlay.appendChild(posterWrap);
};
PosterShare.prototype.createOverlay = function () {
var overlay = document.createElement('div');
overlay.className = 'aksrt-poster-share__overlay';
overlay.innerHTML = '<div class="aksrt-poster-share__loading"><i class="fa fas fa-spinner fa-spin"></i> 生成海报中...</div>';
overlay.addEventListener('click', function (e) {
if (e.target === overlay) overlay.remove();
});
return overlay;
};
PosterShare.prototype.wrapText = function (ctx, text, maxWidth) {
var lines = [];
var currentLine = '';
for (var i = 0; i < text.length; i++) {
var char = text[i];
var testLine = currentLine + char;
var metrics = ctx.measureText(testLine);
if (metrics.width > maxWidth && currentLine !== '') {
lines.push(currentLine);
currentLine = char;
} else {
currentLine = testLine;
}
}
if (currentLine) {
lines.push(currentLine);
}
return lines;
};
// 自动初始化
function autoInit() {
document.querySelectorAll('[data-aksrt-poster]').forEach(function (el) {
if (el.dataset.posterInit) return;
el.dataset.posterInit = '1';
var config = {};
try {
config = JSON.parse(el.dataset.aksrtPoster || '{}');
} catch (e) {}
new PosterShare({ triggerSelector: '[data-aksrt-poster]' });
});
}
jQuery(document).off('auto_fun.aksrt-poster-share').on('auto_fun.aksrt-poster-share', autoInit);
function coverRect(boxWidth, boxHeight, sourceWidth, sourceHeight) {
var sx = 0, sy = 0, sw = sourceWidth, sh = sourceHeight;
if (sourceWidth / sourceHeight > boxWidth / boxHeight) {
sw = boxWidth * sourceHeight / boxHeight;
sx = (sourceWidth - sw) / 2;
} else {
sh = boxHeight * sourceWidth / boxWidth;
sy = (sourceHeight - sh) / 2;
}
return { sx: sx, sy: sy, sw: sw, sh: sh };
}
/**
* Generate a poster directly from a unified share payload, without binding
* to DOM triggers. Used by post-share.js for the "生成海报" share item.
*
* @param {Object} payload Canonical share payload (AksrtShareData shape).
*/
PosterShare.open = function (payload) {
payload = payload || {};
var inst = Object.create(PosterShare.prototype);
inst.options = {
width: 750,
height: 1334,
backgroundColor: '#ffffff',
textColor: '#333333',
accentColor: '#1890ff'
};
inst.generate(Object.assign({}, inst.options, {
title: payload.title || document.title,
excerpt: payload.excerpt || payload.desc || '',
author: payload.author || '',
date: payload.date || '',
siteName: payload.siteName || '',
qrCodeUrl: payload.qrCodeUrl || payload.qrcode || ''
}));
};
function containRect(x, y, boxWidth, boxHeight, sourceWidth, sourceHeight) {
var ratio = Math.min(boxWidth / sourceWidth, boxHeight / sourceHeight);
var width = sourceWidth * ratio;
var height = sourceHeight * ratio;
return { x: x + (boxWidth - width) / 2, y: y + (boxHeight - height) / 2, width: width, height: height };
}
window.AksrtPosterShare = PosterShare;
})();
function drawLines(ctx, text, maxWidth, maxRows, x, y, lineHeight) {
var chars = String(text || '').replace(/[\r\n]+/g, ' ').split('');
var rows = [], line = '';
chars.forEach(function (char) {
var next = line + char;
if (line && ctx.measureText(next).width > maxWidth) { rows.push(line); line = char; }
else line = next;
});
if (line) rows.push(line);
if (rows.length > maxRows) {
rows = rows.slice(0, maxRows);
while (rows[maxRows - 1] && ctx.measureText(rows[maxRows - 1] + '...').width > maxWidth) {
rows[maxRows - 1] = rows[maxRows - 1].slice(0, -1);
}
rows[maxRows - 1] += '...';
}
rows.forEach(function (row, index) { ctx.fillText(row, x, y + index * lineHeight); });
}
function renderPoster(data) {
return Promise.all([
loadImage([data.banner, data.banner_default, data.banner_spare]),
data.logo ? loadImage([data.logo]).catch(function () { return null; }) : Promise.resolve(null),
qrCodeDataUrl(data.url).then(function (src) { return src ? loadImage([src]).catch(function () { return null; }) : null; })
]).then(function (assets) {
var banner = assets[0], logo = assets[1], qr = assets[2];
var canvas = document.createElement('canvas');
canvas.width = WIDTH; canvas.height = HEIGHT;
var ctx = canvas.getContext('2d');
var imageHeight = HEIGHT / 2;
ctx.fillStyle = '#fff'; ctx.fillRect(0, 0, WIDTH, HEIGHT);
var crop = coverRect(WIDTH, imageHeight, banner.width, banner.height);
ctx.drawImage(banner, crop.sx, crop.sy, crop.sw, crop.sh, 0, 0, WIDTH, imageHeight);
var now = new Date();
ctx.fillStyle = '#fff'; ctx.font = '35px ' + FONT; ctx.fillText('周' + '日一二三四五六'.charAt(now.getDay()), 30, 70);
ctx.font = '28px ' + FONT; ctx.fillText((now.getMonth() + 1) + ' / ' + now.getDate(), 30, 115);
ctx.strokeStyle = '#00a1ff'; ctx.lineWidth = 7; ctx.lineCap = 'round'; ctx.beginPath();
ctx.moveTo(40, imageHeight + 38); ctx.lineTo(40, imageHeight + 74); ctx.stroke();
ctx.fillStyle = '#424242'; ctx.font = 'bold 35px ' + FONT;
drawLines(ctx, data.title, WIDTH - 120, 2, 58, imageHeight + 68, 52);
ctx.fillStyle = '#fc4e1f'; ctx.font = '24px ' + FONT;
drawLines(ctx, data.tags, WIDTH - 80, 1, 40, imageHeight + 172, 36);
ctx.fillStyle = '#585858'; ctx.font = '25px ' + FONT;
drawLines(ctx, data.content, WIDTH - 80, 4, 40, imageHeight + 232, 39);
ctx.strokeStyle = 'rgba(122,122,122,.2)'; ctx.lineWidth = 2; ctx.strokeRect(30, HEIGHT - 230, WIDTH - 60, 200);
if (logo) {
var logoRect = containRect(80, HEIGHT - 190, 300, 100, logo.width, logo.height);
ctx.drawImage(logo, logoRect.x, logoRect.y, logoRect.width, logoRect.height);
}
if (qr) ctx.drawImage(qr, WIDTH - 200, HEIGHT - 200, 120, 120);
ctx.fillStyle = '#b4b4b4'; ctx.font = '22px ' + FONT; ctx.textAlign = 'center';
ctx.fillText(data.description || '扫一扫 立即查看', WIDTH / 2, HEIGHT - 52);
return canvas.toDataURL('image/png');
});
}
function modalId(postId) { return 'modal_poster_' + postId; }
function createModal(postId) {
var id = modalId(postId);
var html = '<div class="modal fade flex jc" id="' + id + '" tabindex="-1" role="dialog" aria-hidden="true">' +
'<div class="modal-dialog poster-share hover-show" role="document"><div class="modal-content" style="height:220px;overflow:hidden">' +
'<div class="poster-imgbox hover-show"></div><div class="flex jc loading-mask absolute main-bg radius8">' +
'<div class="loading zts fa-2x"></div><div class="muted-2-color box-body">正在生成图片,请稍候...</div></div></div>' +
'<div class="padding-6 abs-center hover-show-con text-center poster-share-actions" style="top:auto">' +
'<a href="#" class="but cir jb-blue poster-download hide"><i class="fa fas fa-download" aria-hidden="true"></i></a>' +
'<button type="button" data-dismiss="modal" class="but cir jb-yellow ml10"><svg aria-hidden="true" class="icon em12"><use xlink:href="#icon-close"></use></svg></button>' +
'</div></div></div>';
$('body').append(html);
return $('#' + id);
}
function showPoster($modal, postId, src) {
var $box = $modal.find('.poster-imgbox').empty().append($('<img>', { src: src, alt: '分享海报', class: 'loaded-img' })).addClass('loaded');
$modal.find('.poster-download').attr({ href: src, download: 'poster_' + postId + '.png' }).removeClass('hide');
$modal.find('.loading-mask').fadeOut(160);
$modal.find('.modal-content').css({ height: $box.outerHeight(), transition: '.2s' });
window.setTimeout(function () { $modal.find('.modal-content').css({ height: '', overflow: '', transition: '' }); }, 220);
}
function requestPoster($trigger, postId, $modal) {
$.ajax({
type: 'POST', url: ajaxUrl(), dataType: 'json',
data: { action: 'aksrt_poster_share_data', id: postId, nonce: nonce() }
}).done(function (response) {
if (!response || !response.success || !response.data) {
notice((response && response.data && response.data.message) || '海报数据加载失败', 'danger');
$modal.modal('hide');
$modal.remove();
return;
}
renderPoster(response.data).then(function (src) { showPoster($modal, postId, src); }).catch(function () {
notice('海报加载失败,请检查图片跨域设置', 'danger');
$modal.modal('hide');
$modal.remove();
});
}).fail(function (xhr) {
var message = xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message;
notice(message || '海报数据加载失败', 'danger');
$modal.modal('hide');
}).always(function () { $trigger.removeAttr('disabled'); });
}
$('body').off('click.aksrtPosterShare', '[poster-share]').on('click.aksrtPosterShare', '[poster-share]', function (event) {
event.preventDefault();
var $trigger = $(this);
var postId = parseInt($trigger.attr('poster-share'), 10) || 0;
if (!postId || $trigger.attr('disabled')) return;
var $modal = $('#' + modalId(postId));
if ($modal.length) { $modal.modal('show'); return; }
$trigger.attr('disabled', 'disabled');
$('.modal').modal('hide');
$modal = createModal(postId);
$modal.modal('show');
requestPoster($trigger, postId, $modal);
});
})(window, document, window.jQuery);
-108
View File
@@ -1,108 +0,0 @@
/**
* AKSRT Share Data Builder
*
* Single source of truth for share payloads. Every share surface
* (dropdown links, copy, WeChat bridge, poster) resolves its data through
* window.AksrtShareData so no script assembles title/url/cover/qrcode on its
* own.
*
* Canonical payload shape:
* { title, url, desc, text, image, qrcode, author, date, siteName }
*
* Data is read from the nearest [data-share-*] context (falling back to
* #share-dropdown), then to OpenGraph/meta tags and document defaults.
*
* @package AKSRT
*/
(function () {
'use strict';
function metaContent(selectors) {
for (var i = 0; i < selectors.length; i += 1) {
var node = document.querySelector(selectors[i]);
var content = node ? node.getAttribute('content') : '';
if (content) return content;
}
return '';
}
function siteName() {
return (
metaContent(['meta[property="og:site_name"]']) ||
(document.querySelector('.site-title, .logo-text, .navbar-logo') || {}).textContent ||
document.title ||
''
).trim();
}
/**
* Find the element that carries the share data attributes for a node.
*/
function context(node) {
var source = node && typeof node.closest === 'function'
? node.closest('[data-share-url], [data-share-title], [data-share-desc], [data-share-image], [data-aksrt-poster]')
: null;
return source || document.getElementById('share-dropdown');
}
/**
* Generate a QR data URL for a link, reusing the WeChat share encoder
* when available. Returns '' if no encoder is loaded.
*/
function qrcode(url) {
if (window.AksrtShare && typeof window.AksrtShare.buildQrDataUrl === 'function' && url) {
return window.AksrtShare.buildQrDataUrl(url, 200) || '';
}
return '';
}
/**
* Resolve a canonical share payload for a triggering node.
*
* @param {Element} node Triggering element (button/link).
* @param {Object} [overrides] Optional explicit overrides.
* @returns {Object}
*/
function resolve(node, overrides) {
var ctx = context(node);
function attr(name) {
return (node && node.getAttribute && node.getAttribute(name)) ||
(ctx && ctx.getAttribute && ctx.getAttribute(name)) || '';
}
var url = attr('data-share-url') || window.location.href;
var payload = {
title: attr('data-share-title') || document.title,
url: url,
desc: attr('data-share-desc') || metaContent(['meta[property="og:description"]', 'meta[name="description"]']),
text: attr('data-share-text') || '',
image: attr('data-share-image') || metaContent(['meta[property="og:image"]']),
qrcode: attr('data-share-qrcode') || '',
author: attr('data-share-author') || '',
date: attr('data-share-date') || '',
siteName: attr('data-share-site') || siteName()
};
if (!payload.qrcode) {
payload.qrcode = qrcode(payload.url);
}
if (overrides && typeof overrides === 'object') {
Object.keys(overrides).forEach(function (key) {
if (overrides[key] !== undefined && overrides[key] !== null && overrides[key] !== '') {
payload[key] = overrides[key];
}
});
}
return payload;
}
window.AksrtShareData = {
resolve: resolve,
qrcode: qrcode,
siteName: siteName
};
})();
-481
View File
@@ -1,481 +0,0 @@
/**
* AKSRT Weixin Share
*
* Bridge-first WeChat share helper with QR fallback.
*
* @package AKSRT
*/
(function () {
'use strict';
/**
* Local QR code data URL generator — no third-party services.
* Uses canvas rendering with a minimal QR matrix encoder.
*/
function buildQRCodeDataUrl(text, size) {
size = size || 160;
try {
var matrix = generateQRMatrix(text || ' ');
if (!matrix) return '';
var canvas = document.createElement('canvas');
var moduleCount = matrix.length;
var scale = Math.floor(size / (moduleCount + 8));
var margin = Math.floor((size - scale * moduleCount) / 2);
canvas.width = size;
canvas.height = size;
var ctx = canvas.getContext('2d');
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, size, size);
ctx.fillStyle = '#000000';
for (var r = 0; r < moduleCount; r++) {
for (var c = 0; c < moduleCount; c++) {
if (matrix[r][c]) {
ctx.fillRect(margin + c * scale, margin + r * scale, scale, scale);
}
}
}
return canvas.toDataURL('image/png');
} catch (e) {
return '';
}
}
/**
* Minimal QR Code matrix generator (Version 1-6, Byte mode, Error Correction L).
* Returns a 2D boolean array or null on failure.
*/
function generateQRMatrix(text) {
var data = encodeUTF8(text);
// Choose version based on data length (EC level L, Byte mode)
var version = 1;
var capacities = [0, 17, 32, 53, 78, 106, 134];
for (var v = 1; v <= 6; v++) {
if (data.length <= capacities[v]) { version = v; break; }
if (v === 6) return null; // data too long for small versions
}
var moduleCount = version * 4 + 17;
var matrix = createMatrix(moduleCount);
var reserved = createMatrix(moduleCount);
// Place finder patterns
placeFinder(matrix, reserved, 0, 0);
placeFinder(matrix, reserved, moduleCount - 7, 0);
placeFinder(matrix, reserved, 0, moduleCount - 7);
// Place alignment patterns (version >= 2)
if (version >= 2) {
var alignPos = getAlignmentPositions(version);
for (var i = 0; i < alignPos.length; i++) {
for (var j = 0; j < alignPos.length; j++) {
if (isInFinder(alignPos[i], alignPos[j], moduleCount)) continue;
placeAlignment(matrix, reserved, alignPos[i], alignPos[j]);
}
}
}
// Place timing patterns
for (var i = 8; i < moduleCount - 8; i++) {
if (!reserved[6][i]) { matrix[6][i] = (i % 2 === 0); reserved[6][i] = true; }
if (!reserved[i][6]) { matrix[i][6] = (i % 2 === 0); reserved[i][6] = true; }
}
// Reserve format info areas
for (var i = 0; i < 8; i++) {
reserved[8][i] = true; reserved[i][8] = true;
reserved[8][moduleCount - 1 - i] = true; reserved[moduleCount - 1 - i][8] = true;
}
reserved[8][8] = true;
if (version >= 2) {
for (var i = moduleCount - 11; i < moduleCount - 8; i++) {
for (var j = 0; j < 6; j++) { reserved[j][i] = true; reserved[i][j] = true; }
}
}
// Dark module
matrix[moduleCount - 8][8] = true;
reserved[moduleCount - 8][8] = true;
// Encode data
var dataBits = encodeData(data, version);
placeData(matrix, reserved, dataBits, moduleCount);
// Apply best mask
var bestMask = 0;
var bestPenalty = Infinity;
for (var m = 0; m < 8; m++) {
var masked = applyMask(matrix, reserved, m, moduleCount);
var penalty = calcPenalty(masked, moduleCount);
if (penalty < bestPenalty) { bestPenalty = penalty; bestMask = m; }
}
matrix = applyMask(matrix, reserved, bestMask, moduleCount);
// Place format info
placeFormatInfo(matrix, bestMask, moduleCount);
return matrix;
}
function createMatrix(n) {
var m = [];
for (var i = 0; i < n; i++) { m[i] = []; for (var j = 0; j < n; j++) m[i][j] = false; }
return m;
}
function encodeUTF8(str) {
var bytes = [];
for (var i = 0; i < str.length; i++) {
var c = str.charCodeAt(i);
if (c < 0x80) bytes.push(c);
else if (c < 0x800) { bytes.push(0xc0 | (c >> 6)); bytes.push(0x80 | (c & 0x3f)); }
else { bytes.push(0xe0 | (c >> 12)); bytes.push(0x80 | ((c >> 6) & 0x3f)); bytes.push(0x80 | (c & 0x3f)); }
}
return bytes;
}
function placeFinder(m, r, row, col) {
for (var dr = -1; dr <= 7; dr++) {
for (var dc = -1; dc <= 7; dc++) {
var rr = row + dr, cc = col + dc;
if (rr < 0 || rr >= m.length || cc < 0 || cc >= m.length) continue;
var val = (dr >= 0 && dr <= 6 && (dc === 0 || dc === 6)) || (dc >= 0 && dc <= 6 && (dr === 0 || dr === 6)) || (dr >= 2 && dr <= 4 && dc >= 2 && dc <= 4);
m[rr][cc] = val;
r[rr][cc] = true;
}
}
}
function placeAlignment(m, r, row, col) {
for (var dr = -2; dr <= 2; dr++) {
for (var dc = -2; dc <= 2; dc++) {
var val = Math.abs(dr) === 2 || Math.abs(dc) === 2 || (dr === 0 && dc === 0);
m[row + dr][col + dc] = val;
r[row + dr][col + dc] = true;
}
}
}
function getAlignmentPositions(v) {
if (v <= 1) return [];
if (v === 2) return [6, 18];
if (v === 3) return [6, 22];
if (v === 4) return [6, 26];
if (v === 5) return [6, 30];
return [6, 34];
}
function isInFinder(row, col, n) {
return (row < 9 && col < 9) || (row < 9 && col >= n - 8) || (row >= n - 8 && col < 9);
}
function encodeData(data, version) {
var bits = [];
// Mode indicator: Byte = 0100
bits.push(0, 1, 0, 0);
// Character count
var ccBits = version <= 9 ? 8 : 16;
for (var i = ccBits - 1; i >= 0; i--) bits.push((data.length >> i) & 1);
// Data
for (var i = 0; i < data.length; i++) {
for (var j = 7; j >= 0; j--) bits.push((data[i] >> j) & 1);
}
// Terminator
var capacities = [0, 152, 272, 440, 640, 864, 1088];
var cap = capacities[version] || 152;
for (var i = 0; i < 4 && bits.length < cap; i++) bits.push(0);
// Pad to byte boundary
while (bits.length % 8 !== 0) bits.push(0);
// Pad bytes
var padBytes = [0xEC, 0x11];
var pi = 0;
while (bits.length < cap) {
var pb = padBytes[pi % 2];
for (var j = 7; j >= 0; j--) bits.push((pb >> j) & 1);
pi++;
}
return bits;
}
function placeData(matrix, reserved, bits, n) {
var bitIndex = 0;
for (var right = n - 1; right >= 1; right -= 2) {
if (right === 6) right = 5;
for (var vert = 0; vert < n; vert++) {
for (var j = 0; j < 2; j++) {
var col = right - j;
var upward = ((right + 1) & 2) === 0;
var row = upward ? n - 1 - vert : vert;
if (!reserved[row][col] && bitIndex < bits.length) {
matrix[row][col] = bits[bitIndex] === 1;
bitIndex++;
}
}
}
}
}
function applyMask(matrix, reserved, maskNum, n) {
var result = createMatrix(n);
for (var r = 0; r < n; r++) {
for (var c = 0; c < n; c++) {
result[r][c] = matrix[r][c];
if (!reserved[r][c]) {
var invert = false;
switch (maskNum) {
case 0: invert = (r + c) % 2 === 0; break;
case 1: invert = r % 2 === 0; break;
case 2: invert = c % 3 === 0; break;
case 3: invert = (r + c) % 3 === 0; break;
case 4: invert = (Math.floor(r / 2) + Math.floor(c / 3)) % 2 === 0; break;
case 5: invert = (r * c) % 2 + (r * c) % 3 === 0; break;
case 6: invert = ((r * c) % 2 + (r * c) % 3) % 2 === 0; break;
case 7: invert = ((r + c) % 2 + (r * c) % 3) % 2 === 0; break;
}
if (invert) result[r][c] = !result[r][c];
}
}
}
return result;
}
function calcPenalty(matrix, n) {
var penalty = 0;
// Rule 1: consecutive same-color modules in row/column
for (var r = 0; r < n; r++) {
var run = 1;
for (var c = 1; c < n; c++) {
if (matrix[r][c] === matrix[r][c - 1]) { run++; } else { run = 1; }
if (run === 5) penalty += 3;
else if (run > 5) penalty += 1;
}
}
for (var c = 0; c < n; c++) {
var run = 1;
for (var r = 1; r < n; r++) {
if (matrix[r][c] === matrix[r - 1][c]) { run++; } else { run = 1; }
if (run === 5) penalty += 3;
else if (run > 5) penalty += 1;
}
}
return penalty;
}
function placeFormatInfo(matrix, maskNum, n) {
var formatInfo = getFormatInfo(maskNum);
// Place around top-left finder
var positions = [
[8, 0], [8, 1], [8, 2], [8, 3], [8, 4], [8, 5], [8, 7], [8, 8],
[7, 8], [5, 8], [4, 8], [3, 8], [2, 8], [1, 8], [0, 8]
];
for (var i = 0; i < 15; i++) {
var r = positions[i][0], c = positions[i][1];
matrix[r][c] = formatInfo[i] === 1;
}
// Place around bottom-left and top-right finders
var positions2 = [
[n - 1, 8], [n - 2, 8], [n - 3, 8], [n - 4, 8], [n - 5, 8], [n - 6, 8], [n - 7, 8],
[8, n - 8], [8, n - 7], [8, n - 6], [8, n - 5], [8, n - 4], [8, n - 3], [8, n - 2], [8, n - 1]
];
for (var i = 0; i < 15; i++) {
var r = positions2[i][0], c = positions2[i][1];
matrix[r][c] = formatInfo[i] === 1;
}
}
function getFormatInfo(maskNum) {
// EC level L = 01, mask number = maskNum
var data = (0x01 << 3) | maskNum; // 5 bits
var bits = data << 10;
// BCH(15,5) error correction
var gen = 0x537; // generator polynomial
for (var i = 14; i >= 10; i--) {
if (bits & (1 << i)) bits ^= gen << (i - 10);
}
var formatBits = (data << 10) | bits;
// XOR with mask pattern
formatBits ^= 0x5412;
var result = [];
for (var i = 14; i >= 0; i--) result.push((formatBits >> i) & 1);
return result;
}
function isWeixinEnv() {
return /micromessenger/i.test(window.navigator.userAgent || '') || !!window.WeixinJSBridge;
}
function normalizePayload(payload) {
payload = payload || {};
return {
title: payload.title || document.title,
url: payload.url || window.location.href,
desc: payload.desc || '',
image: payload.image || ''
};
}
function hideQrFallback(qrBox) {
if (!qrBox) return;
qrBox.classList.remove('is-visible');
qrBox.setAttribute('aria-hidden', 'true');
}
function showQrFallback(payload) {
var qrBox = payload.qrBox;
var qrImage = payload.qrImage;
if (!qrBox || !qrImage || !payload.url) {
return Promise.resolve({
mode: 'error',
message: '当前页面缺少微信二维码容器'
});
}
if (qrImage.dataset.qrValue !== payload.url || !qrImage.getAttribute('src')) {
qrImage.dataset.qrValue = payload.url;
var dataUrl = buildQRCodeDataUrl(payload.url, 160);
if (dataUrl) {
qrImage.src = dataUrl;
} else {
qrImage.alt = '二维码生成失败,请直接复制链接分享';
}
}
qrBox.classList.add('is-visible');
qrBox.setAttribute('aria-hidden', 'false');
return Promise.resolve({
mode: 'qr',
message: '请使用微信扫一扫分享'
});
}
function bridgeWithWxSdk(payload) {
return new Promise(function (resolve, reject) {
if (!window.wx || typeof window.wx.ready !== 'function') {
reject(new Error('wx sdk unavailable'));
return;
}
var done = false;
var timer = window.setTimeout(function () {
if (done) return;
done = true;
reject(new Error('微信分享桥接未就绪'));
}, 1200);
function finishSuccess() {
if (done) return;
done = true;
window.clearTimeout(timer);
resolve({
mode: 'bridge',
message: '已同步微信分享信息,请使用右上角菜单继续分享'
});
}
function finishError(error) {
if (done) return;
done = true;
window.clearTimeout(timer);
reject(error instanceof Error ? error : new Error('微信分享桥接失败'));
}
try {
if (typeof window.wx.error === 'function') {
window.wx.error(function (error) {
finishError(new Error((error && error.errMsg) || '微信签名校验失败'));
});
}
window.wx.ready(function () {
try {
if (typeof window.wx.updateAppMessageShareData === 'function') {
window.wx.updateAppMessageShareData({
title: payload.title,
desc: payload.desc,
link: payload.url,
imgUrl: payload.image
});
}
if (typeof window.wx.updateTimelineShareData === 'function') {
window.wx.updateTimelineShareData({
title: payload.title,
link: payload.url,
imgUrl: payload.image
});
}
finishSuccess();
} catch (error) {
finishError(error);
}
});
} catch (error) {
finishError(error);
}
});
}
function bridgeWithWeixinJsBridge(payload) {
return new Promise(function (resolve, reject) {
var bridge = window.WeixinJSBridge;
if (!bridge || typeof bridge.invoke !== 'function') {
reject(new Error('WeixinJSBridge unavailable'));
return;
}
try {
bridge.invoke('sendAppMessage', {
title: payload.title,
desc: payload.desc,
link: payload.url,
img_url: payload.image
}, function (response) {
var errMsg = response && response.err_msg ? response.err_msg : '';
if (!errMsg || /:ok$/i.test(errMsg)) {
resolve({
mode: 'bridge',
message: '已调起微信分享面板'
});
return;
}
reject(new Error(errMsg));
});
} catch (error) {
reject(error);
}
});
}
function openWeixinShare(payload) {
var data = normalizePayload(payload);
data.qrBox = payload && payload.qrBox ? payload.qrBox : null;
data.qrImage = payload && payload.qrImage ? payload.qrImage : null;
if (!isWeixinEnv()) {
return showQrFallback(data);
}
return bridgeWithWxSdk(data)
.catch(function () {
return bridgeWithWeixinJsBridge(data);
})
.catch(function (error) {
return showQrFallback(data).then(function (result) {
result.message = (error && error.message ? error.message + ',' : '') + '已切换为二维码分享';
return result;
});
});
}
window.AksrtShare = Object.assign({}, window.AksrtShare || {}, {
isWeixinEnv: isWeixinEnv,
hideQrFallback: hideQrFallback,
showQrFallback: showQrFallback,
openWeixinShare: openWeixinShare,
// Exposed so the unified share-data-builder can render poster QR codes
// without a second encoder.
buildQrDataUrl: buildQRCodeDataUrl
});
})();