Merge branch 'preview-fixes' into master-cdn

This commit is contained in:
the1812 2025-06-29 11:39:12 +08:00
commit 86616225f0
10 changed files with 593 additions and 222 deletions

View File

@ -74,10 +74,10 @@ const sideCards: { [id: number]: SideCardType } = {
className: 'profile',
displayName: '个人资料',
},
1: {
className: 'following-tags',
displayName: '话题',
},
// 1: {
// className: 'following-tags',
// displayName: '',
// },
2: {
className: 'notice',
displayName: '公告栏',
@ -98,6 +98,10 @@ const sideCards: { [id: number]: SideCardType } = {
className: 'compose',
displayName: '发布动态',
},
7: {
className: 'search-trendings',
displayName: '热搜',
},
}
if (getComponentSettings('extendFeedsLive').enabled) {
delete sideCards[3]

View File

@ -92,6 +92,11 @@
display: none !important;
}
}
&.#{$side-block}-search-trendings {
.bili-dyn-search-trendings {
display: none !important;
}
}
&.#{$side-block}-most-viewed {
.bili-dyn-up-list,
.card-list .most-viewed-panel {

View File

@ -1,66 +1,204 @@
import { defineComponentMetadata } from '@/components/define'
import { childList } from '@/core/observer'
import { select } from '@/core/spin-query'
import { useScopedConsole } from '@/core/utils/log'
import { bilibiliApi, getJsonWithCredentials } from '@/core/ajax'
let relationList: Element
let oldObserver: MutationObserver
const displayName = '关注时间显示'
const console = useScopedConsole(displayName)
const observeFans = async (node: Element) => {
// 监听关注列表元素变化
const [observer] = childList(node, () => {
// 读取Vue属性里的关注列表
// eslint-disable-next-line no-underscore-dangle
const subscribeTime = (
relationList.parentElement.parentElement.parentElement.parentElement as any
).__vue__.relationList.map(l => l.mtime)
const SELECTORS = {
profileFollowContainer: '.space-head-follow.b-follow',
profileTextClass: 'subscribe-time-text',
relationCardAnchor: '.relation-card-info__uname',
relationCardContainer: '.relation-card-info',
relationCardTimeClass: 'relation-card-info__time',
relationCardSign: '.relation-card-info__sign',
}
// 为所有子元素添加关注时间显示
relationList.querySelectorAll('.list-item>.content').forEach((e, index) => {
// 防止重复添加元素
if (e.querySelector('.subscribe-time-fix') === null) {
const time = subscribeTime[index]
if (time !== undefined) {
e.querySelector('p').insertAdjacentHTML(
'afterend',
`<div class="desc subscribe-time-fix">关注时间:${new Date(
time * 1000,
).toLocaleString()}</div>`,
)
}
type FollowTimeInfo = {
mtime: number
label: string
}
const midMap: Record<number, FollowTimeInfo> = {}
const insertFollowTimeToCard = (mid: number, dateStr: string, label: string) => {
const anchor = document.querySelector<HTMLAnchorElement>(
`${SELECTORS.relationCardAnchor}[href*="/${mid}"]`,
)
const card = anchor?.closest(SELECTORS.relationCardContainer)
if (!card) {
return
}
if (card.querySelector(`.${SELECTORS.relationCardTimeClass}`)) {
return
}
const div = document.createElement('div')
div.className = SELECTORS.relationCardTimeClass
div.textContent = `${label}${dateStr}`
const sign = card.querySelector(SELECTORS.relationCardSign)
if (sign?.parentNode) {
sign.parentNode.insertBefore(div, sign.nextSibling)
}
}
let fetchWrapped = false
let pendingUpdate = false
const updateCards = () => {
if (pendingUpdate) {
return
}
pendingUpdate = true
requestAnimationFrame(() => {
pendingUpdate = false
const mids = Array.from(
document.querySelectorAll<HTMLAnchorElement>(SELECTORS.relationCardAnchor),
)
.map(el => el.href.match(/\/(\d+)/)?.[1])
.filter(Boolean)
.map(Number)
mids.forEach(mid => {
const data = midMap[mid]
if (data) {
const dateStr = new Date(data.mtime * 1000).toLocaleString()
insertFollowTimeToCard(mid, dateStr, data.label)
}
})
})
// 移除旧的MutationObserver
oldObserver?.disconnect()
oldObserver = observer
const { addImportantStyle } = await import('@/core/style')
const { default: style } = await import('./subscribe-time.scss')
addImportantStyle(style, 'subscribe-time-style')
}
const entry = async () => {
const spaceContainer = await select('.s-space')
childList(spaceContainer, async () => {
if (!document.URL.match(/^https:\/\/space\.bilibili\.com\/\d+\/fans/)) {
return
const waitForProfileContainer = () =>
new Promise<Element>(resolve => {
const check = () => document.querySelector(SELECTORS.profileFollowContainer)
let container = check()
if (container) {
resolve(container)
} else {
const observer = new MutationObserver(() => {
container = check()
if (container) {
observer.disconnect()
resolve(container)
}
})
observer.observe(document.body, { childList: true, subtree: true })
}
relationList = await select('.relation-list')
observeFans(relationList)
})
const insertSubscribeTime = (followTimeStr: string) => {
const container = document.querySelector(SELECTORS.profileFollowContainer)
if (!container) {
return
}
if (container.querySelector(`.${SELECTORS.profileTextClass}`)) {
return
}
if (getComputedStyle(container).position === 'static') {
;(container as HTMLElement).style.position = 'relative'
}
const infoEl = document.createElement('div')
infoEl.className = SELECTORS.profileTextClass
infoEl.textContent = `关注于 ${followTimeStr}`
container.appendChild(infoEl)
}
const entry = async () => {
try {
const { addImportantStyle } = await import('@/core/style')
const { default: style } = await import('./subscribe-time.scss')
addImportantStyle(style, 'subscribe-time-style')
} catch (e) {
console.error('样式加载失败:', e)
}
new MutationObserver(updateCards).observe(document.body, { childList: true, subtree: true })
if (!fetchWrapped) {
fetchWrapped = true
const originalFetch = unsafeWindow.fetch
unsafeWindow.fetch = new Proxy(originalFetch, {
apply(target, thisArg, args) {
const url = typeof args[0] === 'string' ? args[0] : args[0].url
if (url.includes('/x/relation/fans') || url.includes('/x/relation/followings')) {
return target.apply(thisArg, args).then(res => {
res
.clone()
.json()
.then(json => {
const list = json?.data?.list
if (!Array.isArray(list)) {
console.warn('接口数据结构异常:', json)
return
}
list.forEach(user => {
if (typeof user.mid === 'number' && typeof user.mtime === 'number') {
const label = url.includes('/x/relation/fans')
? 'Ta 关注你的时间'
: '你关注 Ta 的时间'
midMap[user.mid] = { mtime: user.mtime, label }
insertFollowTimeToCard(
user.mid,
new Date(user.mtime * 1000).toLocaleString(),
label,
)
}
})
})
.catch(err => console.warn('JSON 解析失败:', err))
return res
})
}
return target.apply(thisArg, args)
},
})
}
const match = location.href.match(/space\.bilibili\.com\/(\d+)/)
if (!match) {
console.warn('无法提取 mid')
return
}
const mid = Number(match[1])
const info = await bilibiliApi(
getJsonWithCredentials(`https://api.bilibili.com/x/web-interface/relation?mid=${mid}`),
)
if (!info?.relation) {
console.warn('未获取到 relation 信息')
return
}
if (!info.relation.mtime) {
console.log('当前未关注或关注时间无效')
return
}
try {
await waitForProfileContainer()
} catch (e) {
console.warn('等待关注容器超时,跳过插入关注时间')
return
}
insertSubscribeTime(new Date(info.relation.mtime * 1000).toLocaleString())
}
export const component = defineComponentMetadata({
name: 'subscribeTimeShow',
author: {
name: 'Light_Quanta',
link: 'https://github.com/LightQuanta',
},
displayName: '关注时间显示',
tags: [componentsTags.utils],
urlInclude: [/^https:\/\/space\.bilibili\.com/],
entry,
description: {
'zh-CN': '在粉丝/关注列表显示关注的具体时间',
author: {
name: 'CNOCM',
link: 'https://github.com/CNOCM',
},
tags: [componentsTags.utils],
urlInclude: [
/^https:\/\/space\.bilibili\.com\/\d+\/(relation|fans)\/(fans|follow)/,
/https:\/\/space\.bilibili\.com\/\d+/,
],
entry,
description: { 'zh-CN': '在粉丝/关注列表及用户主页显示关注的具体时间。' },
})

View File

@ -1,14 +1,25 @@
/* 上移原名称 */
#page-follows .list-item .content .title {
margin-top: -9px;
.space-head-follow.b-follow {
position: relative;
.subscribe-time-text {
position: absolute;
top: -20px;
left: 50%;
transform: translateX(-50%);
font-size: 12px;
color: #fff;
line-height: 16px;
user-select: none;
pointer-events: none;
white-space: nowrap;
font-weight: 500;
text-shadow: 0 0 3px rgba(0, 0, 0, 0.7);
}
}
/* 上移原简介/官方认证 */
#page-follows .list-item .content p {
margin-top: -1px;
}
/* 修复关注时间元素的高度带来的布局影响 */
.subscribe-time-fix {
margin-bottom: -10px;
.relation-card-info__time {
color: #888;
font-size: 12px;
margin-bottom: 4px;
user-select: none;
}

View File

@ -4,6 +4,7 @@ import { videoUrls, watchlaterUrls } from '@/core/utils/urls'
import { playerAgent } from '@/components/video/player-agent'
import { getWatchlaterList, toggleWatchlater } from '@/components/video/watchlater'
let listener: (() => Promise<void>) | null = null
export const component = defineComponentMetadata({
name: 'autoRemoveWatchlater',
displayName: '自动移出稍后再看',
@ -12,12 +13,16 @@ export const component = defineComponentMetadata({
entry: () => {
videoChange(async ({ aid }) => {
const videoElement = await playerAgent.query.video.element()
videoElement.addEventListener('ended', async () => {
if (listener !== null) {
videoElement.removeEventListener('ended', listener)
}
listener = async () => {
const list = await getWatchlaterList()
if (list.includes(parseInt(aid))) {
await toggleWatchlater(aid)
}
})
}
videoElement.addEventListener('ended', listener)
})
},
})

View File

@ -0,0 +1,130 @@
import Vue from 'vue'
import { ComponentMetadata } from '@/components/types'
import { getFormatStr, Video } from './video'
import { getVue2Data } from '@/core/utils'
import { getComponentSettings } from '@/core/settings'
import { VideoInfo } from '@/components/video/video-info'
interface RecommendList extends Vue {
isOpen: boolean
// 组件添加元素非b站自有元素
mark: boolean
related: {
aid: string
title: string
pubdate: number
owner: {
name: string
}
}[]
$children: (RecommendList & VideoPageCard)[]
}
interface VideoPageCard extends Vue {
name: string
title: string
// 组件添加元素非b站自有元素
mark: boolean
oldname: string
item: {
aid: string
pubdate: number
owner: {
// 组件添加元素非b站自有元素
mark: boolean
name: string
}
}
}
export class DefaultVideo implements Video {
console: any
metadata: ComponentMetadata
constructor(console: any) {
this.console = console
}
readonly videoClasses = ['video-page-operator-card-small', 'video-page-card-small']
readonly showUploadTime = (relist: VideoPageCard[], forceUpdate = false, formatString = '') => {
if (!formatString) {
const { options } = getComponentSettings(this.metadata.name)
formatString = options.formatString?.toString()
}
relist.forEach(async video => {
// 使用临时变量保存视频名称,以避免计算属性导致的问题
let videoName: string = video.name
// 确认存放推荐视频列表的List中的元素是否被更新
if (forceUpdate || !video.item.owner.mark) {
video.item.owner.mark = true
// 确认推荐视频卡片是否被更新
if (forceUpdate || !video.mark) {
video.mark = true
if (!video.item.pubdate) {
const info = new VideoInfo(video.item.aid)
await info.fetchInfo()
// 保存查询到的pubdate以便后续使用
video.item.pubdate = info.pubdate
}
const createTime: Date = new Date(video.item.pubdate * 1000)
if (!video.oldname) {
video.oldname = video.name
}
videoName = getFormatStr(createTime, formatString, video.oldname)
video.name = videoName
}
// 保存生成后的name
video.item.owner.name = videoName
}
})
}
readonly getRecoList = () => {
let reco_list = dq('#reco_list')
// 2024.10.17 兼容最近的b站前端改动
if (reco_list == null) {
reco_list = dq('.recommend-list-v1')
}
let recoList: RecommendList = getVue2Data(reco_list)
if (recoList.isOpen === undefined) {
recoList = recoList.$children[0]
if (recoList.isOpen === undefined) {
this.console.log('结构获取失败')
this.console.log(document.URL)
this.console.log(recoList)
}
}
return recoList
}
settingChange(metadata: ComponentMetadata, setting: string): void {
this.metadata = metadata
const recoList: RecommendList = this.getRecoList()
const relist: VideoPageCard[] = recoList.$children.filter(video =>
this.videoClasses.includes(video.$el.className),
)
this.showUploadTime(relist, true, setting)
}
urlChange(metadata: ComponentMetadata): void {
this.metadata = metadata
const recoList: RecommendList = this.getRecoList()
this.console.debug('urlChange recoList.mark', recoList.mark)
if (!recoList.mark) {
recoList.mark = true
// 使用vue组件自带的$watch方法监视推荐列表信息是否变更如果变更则更新
recoList.$watch(
'recListItems',
() => {
this.console.debug('recoListItems changed, now url is', document.URL)
const relist = recoList.$children.filter(video =>
this.videoClasses.includes(video.$el.className),
)
this.showUploadTime(relist)
},
{ deep: true, immediate: true },
)
}
}
}

View File

@ -1,43 +1,14 @@
import Vue from 'vue'
import { defineComponentMetadata } from '@/components/define'
import { urlChange } from '@/core/observer'
import { playerReady, getVue2Data } from '@/core/utils'
import { videoUrls } from '@/core/utils/urls'
import { VideoInfo } from '@/components/video/video-info'
import { playerReady, matchUrlPattern } from '@/core/utils'
import { videoUrls, mediaListUrls } from '@/core/utils/urls'
import { useScopedConsole } from '@/core/utils/log'
import { addComponentListener, getComponentSettings } from '@/core/settings'
import { addComponentListener } from '@/core/settings'
import { TestPattern } from '@/core/common-types'
import desc from './desc.md'
interface RecommendList extends Vue {
isOpen: boolean
mark: boolean
related: {
aid: string
title: string
pubdate: number
owner: {
name: string
}
}[]
$children: (RecommendList & VideoPageCard)[]
}
interface VideoPageCard extends Vue {
name: string
title: string
// 组件添加元素非b站自有元素
mark: boolean
oldname: string
item: {
aid: string
pubdate: number
owner: {
// 组件添加元素非b站自有元素
mark: boolean
name: string
}
}
}
import { Video } from './video'
import { DefaultVideo } from './defaultVideo'
import { MediaListVideo } from './mediaListVideo'
const displayName = '显示视频投稿时间'
const console = useScopedConsole(displayName)
@ -62,118 +33,38 @@ export const component = defineComponentMetadata({
},
],
entry: async ({ metadata }) => {
const getFormatStr = (time: Date, format: string, upName: string) => {
const formatMap: any = {
'M+': time.getMonth() + 1, // 月
'd+': time.getDate(), // 日
'h+': time.getHours(), // 时
'm+': time.getMinutes(), // 分
's+': time.getSeconds(), // 秒
'q+': Math.floor((time.getMonth() + 3) / 3), // 季度
}
const constMap: any = {
up: upName, // up名
'\\\\r': '\r', // 回车符
'\\\\n': '\n', // 换行符
'\\\\t': '\t', // 制表符
}
// 处理年份
let matchResult: RegExpMatchArray | null = format.match(/(y+)/)
if (matchResult !== null) {
format = format.replace(
matchResult[0],
`${time.getFullYear()}`.substring(4 - matchResult[0].length),
)
}
// 处理除年份外的时间
for (const key in formatMap) {
if (!key) {
continue
}
matchResult = format.match(new RegExp(`(${key})`))
if (matchResult !== null) {
format = format.replace(
matchResult[0],
matchResult[0].length === 1
? formatMap[key]
: `00${formatMap[key]}`.substring(`${formatMap[key]}`.length),
)
}
}
// 处理自定义替换文本
for (const key in constMap) {
if (!key) {
continue
}
matchResult = format.match(new RegExp(`(${key})`))
if (matchResult !== null) {
format = format.replace(matchResult[0], constMap[key])
}
}
return format
}
const defaultVideo = new DefaultVideo(console)
const VideoMap: { TestPattern: TestPattern; video: Video }[] = [
{
// 普通视频页面
TestPattern: videoUrls.slice(0, 1),
video: defaultVideo,
},
{
// 合集类视频页面
TestPattern: mediaListUrls,
video: new MediaListVideo(console),
},
]
const showUploadTime = (relist: VideoPageCard[], forceUpdate = false, formatString = '') => {
if (!formatString) {
const { options } = getComponentSettings(metadata.name)
formatString = options.formatString?.toString()
}
relist.forEach(async video => {
// 使用临时变量保存视频名称,以避免计算属性导致的问题
let videoName: string = video.name
// 确认存放推荐视频列表的List中的元素是否被更新
if (forceUpdate || !video.item.owner.mark) {
video.item.owner.mark = true
// 确认推荐视频卡片是否被更新
if (forceUpdate || !video.mark) {
video.mark = true
if (!video.item.pubdate) {
const info = new VideoInfo(video.item.aid)
await info.fetchInfo()
// 保存查询到的pubdate以便后续使用
video.item.pubdate = info.pubdate
}
const createTime: Date = new Date(video.item.pubdate * 1000)
if (!video.oldname) {
video.oldname = video.name
}
videoName = getFormatStr(createTime, formatString, video.oldname)
video.name = videoName
const getMatchVideo = () => {
for (const item of VideoMap) {
for (const pattern of item.TestPattern) {
if (matchUrlPattern(pattern)) {
return item.video
}
// 保存生成后的name
video.item.owner.name = videoName
}
})
}
const getRecoList = () => {
let reco_list = dq('#reco_list')
// 2024.10.17 兼容最近的b站前端改动
if (reco_list == null) {
reco_list = dq('.recommend-list-v1')
}
let recoList: RecommendList = getVue2Data(reco_list)
if (recoList.isOpen === undefined) {
recoList = recoList.$children[0]
if (recoList.isOpen === undefined) {
console.log('结构获取失败')
console.log(document.URL)
console.log(recoList)
}
}
return recoList
return defaultVideo // 默认使用 DefaultVideo
}
const videoClasses = ['video-page-operator-card-small', 'video-page-card-small']
addComponentListener(
`${metadata.name}.formatString`,
(value: string) => {
const recoList: RecommendList = getRecoList()
const relist: VideoPageCard[] = recoList.$children.filter(video =>
videoClasses.includes(video.$el.className),
)
showUploadTime(relist, true, value)
(value: string, oldValue: string) => {
const video = getMatchVideo()
if (video) {
video.settingChange(metadata, value, oldValue)
}
},
false,
)
@ -181,22 +72,9 @@ export const component = defineComponentMetadata({
urlChange(async () => {
console.debug('urlChange now url is', document.URL)
await playerReady()
const recoList: RecommendList = getRecoList()
console.debug('urlChange recoList.mark', recoList.mark)
if (!recoList.mark) {
recoList.mark = true
// 使用vue组件自带的$watch方法监视推荐列表信息是否变更如果变更则更新
recoList.$watch(
'recListItems',
() => {
console.debug('recoListItems changed, now url is', document.URL)
const relist = recoList.$children.filter(video =>
videoClasses.includes(video.$el.className),
)
showUploadTime(relist)
},
{ deep: true, immediate: true },
)
const video = getMatchVideo()
if (video) {
video.urlChange(metadata)
}
})
},

View File

@ -0,0 +1,117 @@
import Vue from 'vue'
import { ComponentMetadata } from '@/components/types'
import { getFormatStr, Video } from './video'
import { getVue2Data } from '@/core/utils'
import { getComponentSettings } from '@/core/settings'
import { VideoInfo } from '@/components/video/video-info'
interface RecommendListUgc extends Vue {
isFolded: boolean
recLimit: number
// 组件添加元素非b站自有元素
mark: boolean
$children: VideoCard[]
}
interface VideoCard extends Vue {
cardIndex: number
// 组件添加元素非b站自有元素
mark: boolean
info: {
aid: string
title: string
pubdate: number
owner: {
// 组件添加元素非b站自有元素
mark: boolean
oldname: string
name: string
}
}
isModern: boolean
isLazyloaded: boolean
isMounted: boolean
}
export class MediaListVideo implements Video {
console: any
metadata: ComponentMetadata
constructor(console: any) {
this.console = console
}
readonly videoClasses = ['recommend-video-card video-card']
readonly showUploadTime = (relist: VideoCard[], forceUpdate = false, formatString = '') => {
if (!formatString) {
const { options } = getComponentSettings(this.metadata.name)
formatString = options.formatString?.toString()
}
relist.forEach(async video => {
// 使用临时变量保存视频名称,以避免计算属性导致的问题
let videoName: string = video.info.owner.name
// 确认存放推荐视频列表的List中的元素是否被更新
if (forceUpdate || !video.info.owner.mark) {
video.info.owner.mark = true
// 确认推荐视频卡片是否被更新
if (forceUpdate || !video.mark) {
video.mark = true
if (!video.info.pubdate) {
const info = new VideoInfo(video.info.aid)
await info.fetchInfo()
// 保存查询到的pubdate以便后续使用
video.info.pubdate = info.pubdate
}
const createTime: Date = new Date(video.info.pubdate * 1000)
if (!video.info.owner.oldname) {
video.info.owner.oldname = video.info.owner.name
}
videoName = getFormatStr(createTime, formatString, video.info.owner.oldname)
}
// 保存生成后的name
video.info.owner.name = videoName
}
})
}
readonly getRecoList = () => {
const reco_list = dq('.recommend-list-container')
const recoList: RecommendListUgc = getVue2Data(reco_list)
if (recoList.isFolded === undefined) {
this.console.log('结构获取失败')
this.console.log(document.URL)
this.console.log(recoList)
}
return recoList
}
settingChange(metadata: ComponentMetadata, setting: string): void {
this.metadata = metadata
const recoList: RecommendListUgc = this.getRecoList()
const relist: VideoCard[] = recoList.$children.filter(video =>
this.videoClasses.includes(video.$el.className),
)
this.showUploadTime(relist, true, setting)
}
urlChange(metadata: ComponentMetadata): void {
this.metadata = metadata
const recoList: RecommendListUgc = this.getRecoList()
this.console.debug('urlChange recoList.mark', recoList.mark)
if (!recoList.mark) {
recoList.mark = true
// 使用vue组件自带的$watch方法监视推荐列表信息是否变更如果变更则更新
recoList.$watch(
'visibleRelated',
() => {
this.console.debug('visibleRelated changed, now url is', document.URL)
const relist = recoList.$children.filter(video =>
this.videoClasses.includes(video.$el.className),
)
this.showUploadTime(relist)
},
{ deep: true, immediate: true },
)
}
}
}

View File

@ -20,3 +20,11 @@
white-space: pre !important;
}
}
// 稍后再看 css
.recommend-video-card .card-box .info .upname {
height: auto !important;
.name {
-webkit-line-clamp: unset !important;
white-space: pre !important;
}
}

View File

@ -0,0 +1,75 @@
import { ComponentMetadata } from '@/components/types'
/**
*
* URL变更时的处理方法
*/
export interface Video {
console: any
metadata: ComponentMetadata
/**
*
* @param metadata
* @param setting
* @param oldSetting ()
*/
settingChange(metadata: ComponentMetadata, setting: string, oldSetting?: string): void
/**
* URL变更事件
* @param metadata
*/
urlChange(metadata: ComponentMetadata): void
}
export const getFormatStr = (time: Date, format: string, upName: string) => {
const formatMap = {
'M+': time.getMonth() + 1, // 月
'd+': time.getDate(), // 日
'h+': time.getHours(), // 时
'm+': time.getMinutes(), // 分
's+': time.getSeconds(), // 秒
'q+': Math.floor((time.getMonth() + 3) / 3), // 季度
}
const constMap = {
up: upName, // up名
'\\\\r': '\r', // 回车符
'\\\\n': '\n', // 换行符
'\\\\t': '\t', // 制表符
}
// 处理年份
let matchResult: RegExpMatchArray | null = format.match(/(y+)/)
if (matchResult !== null) {
format = format.replace(
matchResult[0],
`${time.getFullYear()}`.substring(4 - matchResult[0].length),
)
}
// 处理除年份外的时间
for (const key in formatMap) {
if (!key) {
continue
}
matchResult = format.match(new RegExp(`(${key})`))
if (matchResult !== null) {
format = format.replace(
matchResult[0],
matchResult[0].length === 1
? formatMap[key]
: `00${formatMap[key]}`.substring(`${formatMap[key]}`.length),
)
}
}
// 处理自定义替换文本
for (const key in constMap) {
if (!key) {
continue
}
matchResult = format.match(new RegExp(`(${key})`))
if (matchResult !== null) {
format = format.replace(matchResult[0], constMap[key])
}
}
return format
}