Merge pull request #4277 from timongh/update-to-vue3

Update to vue3
This commit is contained in:
Grant Howard 2023-11-08 07:57:39 +08:00 committed by GitHub
commit 88953d7052
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
488 changed files with 7358 additions and 7965 deletions

View File

@ -4,9 +4,9 @@ module.exports = {
es2020: true, es2020: true,
}, },
extends: [ extends: [
'plugin:vue/recommended',
'plugin:@typescript-eslint/recommended',
'airbnb-base', 'airbnb-base',
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended', 'plugin:prettier/recommended',
], ],
globals: { globals: {
@ -30,10 +30,11 @@ module.exports = {
'import/no-default-export': 'error', 'import/no-default-export': 'error',
'import/no-named-default': 'off', 'import/no-named-default': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/member-delimiter-style': 'off', '@typescript-eslint/member-delimiter-style': 'off',
'@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/no-shadow': ['error', { builtinGlobals: false }], '@typescript-eslint/no-shadow': ['error', { builtinGlobals: false }],
'@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-use-before-define': ['error'], '@typescript-eslint/no-use-before-define': ['error'],
@ -52,6 +53,7 @@ module.exports = {
'vue/require-prop-types': 'off', 'vue/require-prop-types': 'off',
'vue/one-component-per-file': 'off', 'vue/one-component-per-file': 'off',
'vue/singleline-html-element-content-newline': 'off', 'vue/singleline-html-element-content-newline': 'off',
'vue/multi-word-component-names': 'off',
// 使用 @typescript-eslint/no-unused-vars, 否则 interface 都是 unused // 使用 @typescript-eslint/no-unused-vars, 否则 interface 都是 unused
'no-unused-vars': 'off', 'no-unused-vars': 'off',
@ -77,6 +79,7 @@ module.exports = {
'arrow-body-style': 'off', 'arrow-body-style': 'off',
'prefer-arrow-callback': 'off', 'prefer-arrow-callback': 'off',
'prefer-regex-literals': 'off',
'object-curly-newline': 'off', 'object-curly-newline': 'off',
'linebreak-style': 'off', 'linebreak-style': 'off',
camelcase: 'off', camelcase: 'off',

2
.vscode/tasks.json vendored
View File

@ -54,7 +54,7 @@
}, },
{ {
"type": "shell", "type": "shell",
"command": "pnpm tsc -p tsconfig.type-check.json --noEmit", "command": "pnpm vue-tsc -p tsconfig.type-check.json --noEmit",
"group": "build", "group": "build",
"problemMatcher": [], "problemMatcher": [],
"label": "生产:类型检查 prod:type" "label": "生产:类型检查 prod:type"

View File

@ -153,9 +153,6 @@ pnpm install
### 全局 ### 全局
全局变量, 无需 `import` 就可以直接使用. (Tampermonkey API 这里不再列出了, 可根据代码提示使用) 全局变量, 无需 `import` 就可以直接使用. (Tampermonkey API 这里不再列出了, 可根据代码提示使用)
- `Vue`: Vue 库的主对象, 在创建 `.vue` 组件时, 其中的 `<script>` 可以直接使用 `Vue.extend()`
> 出于历史原因, 项目中用的还是 Vue 2, 由于其糟糕的 TypeScript 支持, 在 VS Code + Vetur 的环境下浏览 `.vue` 文件可能会报各种奇奇怪怪的类型错误, 无视就好. (类型是否正确以 `pnpm run type` 的结果为准)
- `lodash`: 包含所有 Lodash 库提供的方法 - `lodash`: 包含所有 Lodash 库提供的方法
- `dq` / `dqa`: `document.querySelector``document.querySelectorAll` 的简写, `dqa` 会返回真实数组 - `dq` / `dqa`: `document.querySelector``document.querySelectorAll` 的简写, `dqa` 会返回真实数组
> 在 `bwp-video` 出现后, 这两个查询函数还会自动将对 `video` 的查询扩展到 `bwp-video` > 在 `bwp-video` 出现后, 这两个查询函数还会自动将对 `video` 的查询扩展到 `bwp-video`

View File

@ -1,4 +1,4 @@
import { readFileSync, existsSync } from 'fs' import { existsSync, readFileSync } from 'fs'
interface DevServerConfig { interface DevServerConfig {
port?: number port?: number

View File

@ -1,8 +1,9 @@
import webpack from 'webpack'
import exitHook from 'async-exit-hook' import exitHook from 'async-exit-hook'
import webpack from 'webpack'
import webpackConfig from '../../webpack/webpack.dev' import webpackConfig from '../../webpack/webpack.dev'
import { sendMessage } from './web-socket-server'
import { defaultWatcherHandler } from './watcher-common' import { defaultWatcherHandler } from './watcher-common'
import { sendMessage } from './web-socket-server'
export const startCoreWatcher = () => export const startCoreWatcher = () =>
new Promise<void>(resolve => { new Promise<void>(resolve => {

View File

@ -1,5 +1,5 @@
import { startDevServer } from './server'
import { startCoreWatcher } from './core-watcher' import { startCoreWatcher } from './core-watcher'
import { startDevServer } from './server'
import { startWebSocketServer } from './web-socket-server' import { startWebSocketServer } from './web-socket-server'
startDevServer().then(server => { startDevServer().then(server => {

View File

@ -1,9 +1,11 @@
import { Watching, Configuration, webpack } from 'webpack'
import exitHook from 'async-exit-hook' import exitHook from 'async-exit-hook'
import type { Configuration, Watching } from 'webpack'
import { webpack } from 'webpack'
import { fromId } from '../../registry/lib/id' import { fromId } from '../../registry/lib/id'
import { devServerConfig } from './config'
import { defaultWatcherHandler } from './watcher-common' import { defaultWatcherHandler } from './watcher-common'
import { sendMessage } from './web-socket-server' import { sendMessage } from './web-socket-server'
import { devServerConfig } from './config'
export const watchers: { url: string; instance: Watching }[] = [] export const watchers: { url: string; instance: Watching }[] = []
export const parseRegistryUrl = (url: string) => { export const parseRegistryUrl = (url: string) => {

View File

@ -1,11 +1,13 @@
import { createServer, Server } from 'http'
import { Configuration } from 'webpack'
import exitHook from 'async-exit-hook' import exitHook from 'async-exit-hook'
import type { Server } from 'http'
import { createServer } from 'http'
import handler from 'serve-handler' import handler from 'serve-handler'
import { devServerConfig } from './config' import type { Configuration } from 'webpack'
import { buildByEntry } from '../../registry/webpack/config' import { buildByEntry } from '../../registry/webpack/config'
import { devServerConfig } from './config'
import { parseRegistryUrl, startRegistryWatcher, watchers } from './registry-watcher'
import { exitWebSocketServer } from './web-socket-server' import { exitWebSocketServer } from './web-socket-server'
import { watchers, parseRegistryUrl, startRegistryWatcher } from './registry-watcher'
export const startDevServer = () => export const startDevServer = () =>
new Promise<Server>(resolve => { new Promise<Server>(resolve => {

View File

@ -1,4 +1,4 @@
import { Stats } from 'webpack' import type { Stats } from 'webpack'
export const defaultWatcherHandler = ( export const defaultWatcherHandler = (
initCallback: (result: Stats) => void, initCallback: (result: Stats) => void,

View File

@ -1,7 +1,8 @@
import exitHook from 'async-exit-hook' import exitHook from 'async-exit-hook'
import { Server } from 'http' import type { Server } from 'http'
import { WebSocketServer } from 'ws' import { WebSocketServer } from 'ws'
import { Payload } from './payload'
import type { Payload } from './payload'
import { stopInstance, watchers } from './registry-watcher' import { stopInstance, watchers } from './registry-watcher'
let server: WebSocketServer let server: WebSocketServer
@ -32,9 +33,6 @@ export const startWebSocketServer = (httpServer: Server) =>
const payload: Payload = JSON.parse(data.toString()) const payload: Payload = JSON.parse(data.toString())
console.log('收到 DevClient 消息:', payload) console.log('收到 DevClient 消息:', payload)
switch (payload.type) { switch (payload.type) {
default: {
break
}
case 'itemStop': { case 'itemStop': {
const { path } = payload const { path } = payload
const watcherIndex = watchers.findIndex(it => it.url === path) const watcherIndex = watchers.findIndex(it => it.url === path)
@ -50,6 +48,9 @@ export const startWebSocketServer = (httpServer: Server) =>
sendMessage({ type: 'querySessionsResponse', sessions: watchers.map(it => it.url) }) sendMessage({ type: 'querySessionsResponse', sessions: watchers.map(it => it.url) })
break break
} }
default: {
break
}
} }
} catch (error) { } catch (error) {
console.error('无效信息', data) console.error('无效信息', data)

View File

@ -11,7 +11,7 @@
"build-github-config": "ts-node ./.github-json/index.ts", "build-github-config": "ts-node ./.github-json/index.ts",
"lint": "eslint --quiet --fix . --ext .ts,.vue", "lint": "eslint --quiet --fix . --ext .ts,.vue",
"lint-check": "eslint . --ext .ts,.vue", "lint-check": "eslint . --ext .ts,.vue",
"type": "tsc -p tsconfig.type-check.json --noEmit" "type": "vue-tsc -p tsconfig.type-check.json --noEmit"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.20.12", "@babel/core": "7.20.12",
@ -30,21 +30,22 @@
"@types/serve-handler": "^6.1.1", "@types/serve-handler": "^6.1.1",
"@types/sortablejs": "^1.10.7", "@types/sortablejs": "^1.10.7",
"@types/streamsaver": "^2.0.1", "@types/streamsaver": "^2.0.1",
"@types/webpack-env": "^1.15.1", "@types/webpack-env": "^1.16.4",
"@types/ws": "^8.2.3", "@types/ws": "^8.2.3",
"@typescript-eslint/eslint-plugin": "^5.50.0", "@typescript-eslint/eslint-plugin": "^5.50.0",
"@typescript-eslint/parser": "^5.50.0", "@typescript-eslint/parser": "^5.50.0",
"@vue/tsconfig": "^0.1.3",
"async-exit-hook": "^2.0.1", "async-exit-hook": "^2.0.1",
"autoprefixer": "^10.0.1", "autoprefixer": "^10.0.1",
"babel-loader": "^8.1.0", "babel-loader": "^8.1.0",
"browserslist": "^4.21.4", "browserslist": "^4.21.4",
"css-loader": "^5.0.0", "css-loader": "^5.0.0",
"eslint": "^7.32.0", "eslint": "^8.36.0",
"eslint-config-airbnb-base": "^14.1.0", "eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^8.6.0", "eslint-config-prettier": "^8.6.0",
"eslint-plugin-import": "^2.20.1", "eslint-plugin-import": "^2.20.1",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "7.1.0", "eslint-plugin-vue": "9.9.0",
"fast-sass-loader": "^2.0.0", "fast-sass-loader": "^2.0.0",
"glob": "^10.2.6", "glob": "^10.2.6",
"postcss": "^8.1.0", "postcss": "^8.1.0",
@ -60,8 +61,8 @@
"to-string-loader": "^1.2.0", "to-string-loader": "^1.2.0",
"ts-node": "^10.7.0", "ts-node": "^10.7.0",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"vue-loader": "^15.8.3", "vue-loader": "^17.2.2",
"vue-template-compiler": "^2.6.11", "vue-tsc": "^1.8.1",
"webpack": "^5.31.2", "webpack": "^5.31.2",
"webpack-bundle-analyzer": "^4.5.0", "webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^4.6.0", "webpack-cli": "^4.6.0",
@ -72,17 +73,20 @@
"@popperjs/core": "^2.6.0", "@popperjs/core": "^2.6.0",
"color": "^3.1.2", "color": "^3.1.2",
"fuse.js": "^6.4.6", "fuse.js": "^6.4.6",
"jszip": "^3.7.1", "jszip": "3.10.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"marked": "^1.2.5", "marked": "^1.2.5",
"protobufjs": "^6.11.2", "protobufjs": "^6.11.2",
"streamsaver": "^2.0.6", "streamsaver": "^2.0.6",
"tippy.js": "^6.3.1", "tippy.js": "^6.3.1",
"vue": "^2.6.11" "vue": "^3.3.4"
}, },
"pnpm": { "pnpm": {
"overrides": { "overrides": {
"caniuse-lite": "^1.0.30001481" "caniuse-lite": "^1.0.30001481"
},
"patchedDependencies": {
"jszip@3.10.1": "patches/jszip@3.10.1.patch"
} }
}, },
"keywords": [ "keywords": [

176
patches/jszip@3.10.1.patch Normal file
View File

@ -0,0 +1,176 @@
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..10b731c518c79596ed8690544cfbc87a98cf7e36
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,5 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
diff --git a/.idea/76e50e522c18ecb7026f8d1c95ab7351.iml b/.idea/76e50e522c18ecb7026f8d1c95ab7351.iml
new file mode 100644
index 0000000000000000000000000000000000000000..0c8867d7e175f46d4bcd66698ac13f4ca00cf592
--- /dev/null
+++ b/.idea/76e50e522c18ecb7026f8d1c95ab7351.iml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+ <component name="NewModuleRootManager">
+ <content url="file://$MODULE_DIR$">
+ <excludeFolder url="file://$MODULE_DIR$/temp" />
+ <excludeFolder url="file://$MODULE_DIR$/.tmp" />
+ <excludeFolder url="file://$MODULE_DIR$/tmp" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000000000000000000000000000000000000..03d9549ea8e4ada36fb3ecbc30fef08175b7d728
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+ <profile version="1.0">
+ <option name="myName" value="Project Default" />
+ <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
+ </profile>
+</component>
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000000000000000000000000000000000000..86959bb4dfd3ede12f4d2769b074ab205b6cb759
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/.idea/76e50e522c18ecb7026f8d1c95ab7351.iml" filepath="$PROJECT_DIR$/.idea/76e50e522c18ecb7026f8d1c95ab7351.iml" />
+ </modules>
+ </component>
+</project>
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e1e646f4406f21f50f8aeb462daf20493f72c77e
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ChangeListManager">
+ <list default="true" id="61a15d74-703e-43e7-9226-85dcec01205b" name="变更" comment="" />
+ <option name="SHOW_DIALOG" value="false" />
+ <option name="HIGHLIGHT_CONFLICTS" value="true" />
+ <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
+ <option name="LAST_RESOLUTION" value="IGNORE" />
+ </component>
+ <component name="MarkdownSettingsMigration">
+ <option name="stateVersion" value="1" />
+ </component>
+ <component name="ProjectId" id="2L8WtYspVZmmp1sF8ITjwQ8QlvA" />
+ <component name="ProjectViewState">
+ <option name="hideEmptyMiddlePackages" value="true" />
+ <option name="showLibraryContents" value="true" />
+ </component>
+ <component name="PropertiesComponent"><![CDATA[{
+ "keyToString": {
+ "RunOnceActivity.OpenProjectViewOnStart": "true",
+ "RunOnceActivity.ShowReadmeOnStart": "true",
+ "WebServerToolWindowFactoryState": "false",
+ "last_opened_file_path": "C:/Users/zheyang_w/AppData/Local/Temp/76e50e522c18ecb7026f8d1c95ab7351",
+ "node.js.detected.package.eslint": "true",
+ "node.js.selected.package.eslint": "(autodetect)",
+ "vue.rearranger.settings.migration": "true"
+ }
+}]]></component>
+ <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="应用程序级" UseSingleDictionary="true" transferred="true" />
+ <component name="TaskManager">
+ <task active="true" id="Default" summary="默认任务">
+ <changelist id="61a15d74-703e-43e7-9226-85dcec01205b" name="变更" comment="" />
+ <created>1675253404526</created>
+ <option name="number" value="Default" />
+ <option name="presentableId" value="Default" />
+ <updated>1675253404526</updated>
+ <workItem from="1675253405601" duration="191000" />
+ </task>
+ <servers />
+ </component>
+ <component name="TypeScriptGeneratedFilesManager">
+ <option name="version" value="3" />
+ </component>
+</project>
\ No newline at end of file
diff --git a/index.d.ts b/index.d.ts
index b1c930821f256a3223c34b6aa3ca3493a6b86a96..4d480a5691f8727f6e7ee155697488440ae0bb86 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -4,13 +4,10 @@
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.3
-/// <reference types="node" />
-
interface JSZipSupport {
arraybuffer: boolean;
uint8array: boolean;
blob: boolean;
- nodebuffer: boolean;
}
type Compression = 'STORE' | 'DEFLATE';
@@ -32,7 +29,6 @@ interface InputByType {
uint8array: Uint8Array;
arraybuffer: ArrayBuffer;
blob: Blob;
- stream: NodeJS.ReadableStream;
}
interface OutputByType {
@@ -44,7 +40,6 @@ interface OutputByType {
uint8array: Uint8Array;
arraybuffer: ArrayBuffer;
blob: Blob;
- nodebuffer: Buffer;
}
// This private `_data` property on a JSZipObject uses this interface.
@@ -94,7 +89,6 @@ declare namespace JSZip {
* @return Promise the promise of the result.
*/
async<T extends OutputType>(type: T, onUpdate?: OnUpdateCallback): Promise<OutputByType[T]>;
- nodeStream(type?: 'nodebuffer', onUpdate?: OnUpdateCallback): NodeJS.ReadableStream;
}
interface JSZipFileOptions {
@@ -167,7 +161,7 @@ declare namespace JSZip {
checkCRC32?: boolean;
optimizedBinaryString?: boolean;
createFolders?: boolean;
- decodeFileName?: (bytes: string[] | Uint8Array | Buffer) => string;
+ decodeFileName?: (bytes: string[] | Uint8Array) => string;
}
type DataEventCallback<T> = (dataChunk: T, metadata: JSZipMetadata) => void
@@ -284,15 +278,6 @@ interface JSZip {
*/
generateAsync<T extends JSZip.OutputType>(options?: JSZip.JSZipGeneratorOptions<T>, onUpdate?: JSZip.OnUpdateCallback): Promise<OutputByType[T]>;
- /**
- * Generates a new archive asynchronously
- *
- * @param options Optional options for the generator
- * @param onUpdate The optional function called on each internal update with the metadata.
- * @return A Node.js `ReadableStream`
- */
- generateNodeStream(options?: JSZip.JSZipGeneratorOptions<'nodebuffer'>, onUpdate?: JSZip.OnUpdateCallback): NodeJS.ReadableStream;
-
/**
* Generates the complete zip file with the internal stream implementation
*

8119
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1 +0,0 @@
!function(e,a){"object"==typeof exports&&"object"==typeof module?module.exports=a():"function"==typeof define&&define.amd?define([],a):"object"==typeof exports?exports["style/clear-home"]=a():e["style/clear-home"]=a()}(globalThis,(()=>(()=>{"use strict";var e={d:(a,l)=>{for(var t in l)e.o(l,t)&&!e.o(a,t)&&Object.defineProperty(a,t,{enumerable:!0,get:l[t]})},o:(e,a)=>Object.prototype.hasOwnProperty.call(e,a)},a={};e.d(a,{component:()=>c});const l=coreApis.componentApis.define,t=coreApis.settings,s=[];let d=!1,o=!0;const i=(0,l.defineOptionsMetadata)({广告:{displayName:"广告",defaultValue:!0},番剧:{displayName:"番剧",defaultValue:!0},电影:{displayName:"电影",defaultValue:!0},国创:{displayName:"国创",defaultValue:!0},电视剧:{displayName:"电视剧",defaultValue:!0},综艺:{displayName:"综艺",defaultValue:!0},纪录片:{displayName:"纪录片",defaultValue:!0},动画:{displayName:"动画",defaultValue:!0},游戏:{displayName:"游戏",defaultValue:!0},鬼畜:{displayName:"鬼畜",defaultValue:!0},音乐:{displayName:"音乐",defaultValue:!0},舞蹈:{displayName:"舞蹈",defaultValue:!0},影视:{displayName:"影视",defaultValue:!0},娱乐:{displayName:"娱乐",defaultValue:!0},知识:{displayName:"知识",defaultValue:!0},科技:{displayName:"科技",defaultValue:!0},资讯:{displayName:"资讯",defaultValue:!0},美食:{displayName:"美食",defaultValue:!0},生活:{displayName:"生活",defaultValue:!0},汽车:{displayName:"汽车",defaultValue:!0},时尚:{displayName:"时尚",defaultValue:!0},运动:{displayName:"运动",defaultValue:!0},动物圈:{displayName:"动物圈",defaultValue:!0},VLOG:{displayName:"VLOG",defaultValue:!0},搞笑:{displayName:"搞笑",defaultValue:!0},单机游戏:{displayName:"单机游戏",defaultValue:!0},虚拟UP主:{displayName:"虚拟UP主",defaultValue:!0},公益:{displayName:"公益",defaultValue:!0},公开课:{displayName:"公开课",defaultValue:!0},专栏:{displayName:"专栏",defaultValue:!0},直播:{displayName:"直播",defaultValue:!0},赛事:{displayName:"赛事",defaultValue:!0},活动:{displayName:"活动",defaultValue:!0},课堂:{displayName:"课堂",defaultValue:!0},社区中心:{displayName:"社区中心",defaultValue:!0},新歌热榜:{displayName:"新歌热榜",defaultValue:!0},漫画:{displayName:"漫画",defaultValue:!0}});function u(e){console.log(e);for(const a of e)if(a.classList)if(a.classList.contains("floor-single-card")){const e=a.querySelector(".badge").textContent;e&&s.includes(e)&&(a.remove(),console.log("remove",e))}else a.classList.contains("bili-live-card")?d&&(a.remove(),console.log("remove live")):(a.classList.contains("bili-video-card")||a.classList.contains("feed-card"))&&o&&(a.querySelector(".bili-video-card__info--ad")||a.querySelector(".bili-video-card__info--creative-ad"))&&(a.remove(),console.log("remove ads"))}function n(e){const a=[];for(const l of e){const e=l.target;if(!(e.classList&&e.classList.contains("carousel-transform")||"SPAN"===e.tagName)&&l.addedNodes.length>0)for(const e of l.addedNodes){const l=e;"#text"===l.nodeName||l.classList&&l.classList.contains("bili-watch-later")||a.push(l)}}a.length>0&&u(a)}const c=(0,l.defineComponentMetadata)({name:"clear-home",author:{name:"RieN7",link:"https://github.com/rien7"},tags:[componentsTags.style],displayName:"首页净化",description:"删除首页特定类型的卡片",entry:async e=>{let{metadata:a,settings:l}=e;const i=document.querySelector("main > .feed2 > .recommended-container_floor-aside > .container");if(!i)return;new MutationObserver(n).observe(i,{childList:!0}),Object.keys(l.options).forEach((e=>{(0,t.addComponentListener)(`${a.name}.${e}`,(a=>{if(a){if("广告"===e)return void(o=!0);s.push(e),"直播"===e&&(d=!0)}}),!0)}));u(i.children)},options:i,commitHash:"1167f721dc805a6bfd3d4bcba753607eff4bacd5",coreVersion:"2.7.0"});return a=a.component})()));

View File

@ -1,5 +1,5 @@
import { defineComponentMetadata } from '@/components/define' import { defineComponentMetadata } from '@/components/define'
import { FeedsCard } from '@/components/feeds/api' import type { FeedsCard } from '@/components/feeds/api'
import { feedsUrls } from '@/core/utils/urls' import { feedsUrls } from '@/core/utils/urls'
const entry = async () => { const entry = async () => {

View File

@ -1,5 +1,6 @@
import { defineComponentMetadata } from '@/components/define' import { defineComponentMetadata } from '@/components/define'
import { FeedsCard, feedsCardTypes } from '@/components/feeds/api' import type { FeedsCard } from '@/components/feeds/api'
import { feedsCardTypes } from '@/components/feeds/api'
import { feedsUrls } from '@/core/utils/urls' import { feedsUrls } from '@/core/utils/urls'
let enabled = true let enabled = true

View File

@ -49,6 +49,7 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'
import { VIcon, TextBox, DpiImage, VEmpty, VLoading } from '@/ui' import { VIcon, TextBox, DpiImage, VEmpty, VLoading } from '@/ui'
import { getJsonWithCredentials, responsiveGetPages } from '@/core/ajax' import { getJsonWithCredentials, responsiveGetPages } from '@/core/ajax'
@ -64,7 +65,7 @@ interface LiveInfo {
link: string link: string
} }
export default Vue.extend({ export default defineComponent({
components: { components: {
VIcon, VIcon,
TextBox, TextBox,

View File

@ -35,9 +35,8 @@ const entry = async () => {
if (!container) { if (!container) {
console.error('container not found') console.error('container not found')
} }
const LiveList = await import('./LiveList.vue').then(m => m.default) const [el] = mountVueComponent(await import('./LiveList.vue'))
const liveList = mountVueComponent(LiveList) container.appendChild(el)
container.appendChild(liveList.$el)
} }
export const component = defineComponentMetadata({ export const component = defineComponentMetadata({

View File

@ -12,22 +12,17 @@
<div class="filter-patterns"> <div class="filter-patterns">
<div v-for="p of patterns" :key="p" class="pattern"> <div v-for="p of patterns" :key="p" class="pattern">
{{ p }} {{ p }}
<VIcon <VIcon title="删除" icon="mdi-trash-can-outline" :size="16" @click="deletePattern(p)" />
title="删除"
icon="mdi-trash-can-outline"
:size="16"
@click.native="deletePattern(p)"
/>
</div> </div>
</div> </div>
<div class="add-pattern"> <div class="add-pattern">
<TextBox <TextBox
v-model="newPattern" v-model:text="newPattern"
placeholder="支持正则表达式 /^xxx$/" placeholder="支持正则表达式 /^xxx$/"
type="text" type="text"
@keydown.enter="addPattern(newPattern)" @keydown.enter="addPattern(newPattern)"
/> />
<VButton type="transparent" @click.native="addPattern(newPattern)"> <VButton type="transparent" @click="addPattern(newPattern)">
<VIcon title="添加" icon="mdi-plus" :size="18" /> <VIcon title="添加" icon="mdi-plus" :size="18" />
</VButton> </VButton>
</div> </div>
@ -37,10 +32,12 @@
v-for="[id, type] of Object.entries(allSideCards)" v-for="[id, type] of Object.entries(allSideCards)"
:key="id" :key="id"
class="filter-side-card-switch feeds-filter-switch" class="filter-side-card-switch feeds-filter-switch"
@click="toggleBlockSide(id)" @click="toggleBlockSide(Number(id))"
> >
<label :class="{ disabled: sideDisabled(id) }"> <label :class="{ disabled: sideDisabled(Number(id)) }">
<span class="name" :class="{ disabled: sideDisabled(id) }">{{ type.displayName }}</span> <span class="name" :class="{ disabled: sideDisabled(Number(id)) }">{{
type.displayName
}}</span>
<VIcon :size="16" class="disabled" icon="mdi-cancel"></VIcon> <VIcon :size="16" class="disabled" icon="mdi-cancel"></VIcon>
<VIcon :size="16" icon="mdi-check"></VIcon> <VIcon :size="16" icon="mdi-check"></VIcon>
</label> </label>
@ -50,18 +47,21 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { import { defineAsyncComponent, defineComponent } from 'vue'
import type {
feedsCardsManager,
FeedsCard, FeedsCard,
FeedsCardType, FeedsCardType,
feedsCardTypes,
forEachFeedsCard,
RepostFeedsCard, RepostFeedsCard,
} from '@/components/feeds/api' } from '@/components/feeds/api'
import { feedsCardTypes, forEachFeedsCard } from '@/components/feeds/api'
import { attributes, attributesSubtree } from '@/core/observer'
import { getComponentSettings } from '@/core/settings' import { getComponentSettings } from '@/core/settings'
import { select } from '@/core/spin-query' import { select } from '@/core/spin-query'
import { attributes, attributesSubtree } from '@/core/observer' import { TextBox, VButton, VIcon } from '@/ui'
import { VIcon, TextBox, VButton } from '@/ui'
import { FeedsFilterOptions } from './options' import type { FeedsFilterOptions } from './options'
import { hasBlockedPattern } from './pattern' import { hasBlockedPattern } from './pattern'
const { options } = getComponentSettings<FeedsFilterOptions>('feedsFilter') const { options } = getComponentSettings<FeedsFilterOptions>('feedsFilter')
@ -99,12 +99,12 @@ const sideCards: { [id: number]: SideCardType } = {
displayName: '发布动态', displayName: '发布动态',
}, },
} }
let cardsManager: typeof import('@/components/feeds/api').feedsCardsManager let cardsManager: typeof feedsCardsManager
const sideBlock = 'feeds-filter-side-block-' const sideBlock = 'feeds-filter-side-block-'
export default Vue.extend({ export default defineComponent({
components: { components: {
FilterTypeSwitch: () => import('./FilterTypeSwitch.vue'), FilterTypeSwitch: defineAsyncComponent(() => import('./FilterTypeSwitch.vue')),
VIcon, VIcon,
TextBox, TextBox,
VButton, VButton,
@ -120,12 +120,15 @@ export default Vue.extend({
} }
}, },
watch: { watch: {
patterns() { patterns: {
handler() {
options.patterns = this.patterns options.patterns = this.patterns
if (cardsManager) { if (cardsManager) {
cardsManager.cards.forEach(card => this.updateCard(lodash.clone(card))) cardsManager.cards.forEach(card => this.updateCard(lodash.clone(card)))
} }
}, },
deep: true,
},
}, },
async mounted() { async mounted() {
this.updateBlockSide() this.updateBlockSide()
@ -197,7 +200,7 @@ export default Vue.extend({
updateBlockSide() { updateBlockSide() {
Object.entries(sideCards).forEach(([id, type]) => { Object.entries(sideCards).forEach(([id, type]) => {
const name = sideBlock + type.className const name = sideBlock + type.className
document.body.classList[this.blockSideCards.includes(id) ? 'add' : 'remove'](name) document.body.classList[this.blockSideCards.includes(Number(id)) ? 'add' : 'remove'](name)
}) })
}, },
toggleBlockSide(id: number) { toggleBlockSide(id: number) {

View File

@ -10,12 +10,15 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'
import type { PropType } from 'vue'
import type { FeedsCardType } from '@/components/feeds/api'
import { getComponentSettings } from '@/core/settings' import { getComponentSettings } from '@/core/settings'
import { VIcon } from '@/ui' import { VIcon } from '@/ui'
import { FeedsFilterOptions } from './options' import type { FeedsFilterOptions } from './options'
const { options } = getComponentSettings<FeedsFilterOptions>('feedsFilter') const { options } = getComponentSettings<FeedsFilterOptions>('feedsFilter')
export default Vue.extend({ export default defineComponent({
components: { components: {
VIcon, VIcon,
}, },
@ -25,12 +28,12 @@ export default Vue.extend({
required: true, required: true,
}, },
type: { type: {
type: Object, type: Object as PropType<FeedsCardType>,
required: true, required: true,
}, },
}, },
data() { data() {
const optionKey = this.type.id >= 0 ? 'types' : 'specialTypes' const optionKey = this.type.id >= 0 ? 'types' : ('specialTypes' as 'types' | 'specialTypes')
const disabled = options[optionKey].includes(this.type.id) const disabled = options[optionKey].includes(this.type.id)
return { return {
disabled, disabled,

View File

@ -1,5 +1,6 @@
import { defineComponentMetadata } from '@/components/define' import { defineComponentMetadata } from '@/components/define'
import { feedsCardsManager } from '@/components/feeds/api' import { feedsCardsManager } from '@/components/feeds/api'
import { feedsFilterPlugin } from './plugin' import { feedsFilterPlugin } from './plugin'
import { options } from './options' import { options } from './options'
@ -18,9 +19,11 @@ const entry = async () => {
if (leftPanel === null) { if (leftPanel === null) {
return return
} }
const FeedsFilterCard = await import('./FeedsFilterCard.vue')
const { mountVueComponent } = await import('@/core/utils') const { mountVueComponent } = await import('@/core/utils')
leftPanel.insertAdjacentElement('afterbegin', mountVueComponent(FeedsFilterCard).$el) leftPanel.insertAdjacentElement(
'afterbegin',
mountVueComponent(await import('./FeedsFilterCard.vue'))[0],
)
} }
export const component = defineComponentMetadata({ export const component = defineComponentMetadata({

View File

@ -1,4 +1,4 @@
import { OptionsOfMetadata, defineOptionsMetadata } from '@/components/define' import { type OptionsOfMetadata, defineOptionsMetadata } from '@/components/define'
export const options = defineOptionsMetadata({ export const options = defineOptionsMetadata({
types: { types: {

View File

@ -1,7 +1,9 @@
import { FeedsContentFilter } from '@/components/feeds/api' import type { FeedsContentFilter } from '@/components/feeds/api'
import { getComponentSettings } from '@/core/settings' import { getComponentSettings } from '@/core/settings'
import { PluginMetadata } from '@/plugins/plugin' import type { PluginMetadata } from '@/plugins/plugin'
import { BlockableCard, hasBlockedPattern } from './pattern'
import type { BlockableCard } from './pattern'
import { hasBlockedPattern } from './pattern'
const bangumiFields = { const bangumiFields = {
username: 'title', username: 'title',

View File

@ -1,9 +1,9 @@
import { defineComponentMetadata } from '@/components/define' import { defineComponentMetadata } from '@/components/define'
import { styledComponentEntry } from '@/components/styled-component'
import { feedsUrlsWithoutDetail } from '@/core/utils/urls'
import { feedsCardsManager } from '@/components/feeds/api' import { feedsCardsManager } from '@/components/feeds/api'
import { select } from '@/core/spin-query' import { styledComponentEntry } from '@/components/styled-component'
import { childListSubtree } from '@/core/observer' import { childListSubtree } from '@/core/observer'
import { select } from '@/core/spin-query'
import { feedsUrlsWithoutDetail } from '@/core/utils/urls'
const entry = async () => { const entry = async () => {
const { forEachFeedsCard } = await import('@/components/feeds/api') const { forEachFeedsCard } = await import('@/components/feeds/api')

View File

@ -2,9 +2,9 @@
<div class="multiple-widgets"> <div class="multiple-widgets">
<VPopup <VPopup
ref="medalPopup" ref="medalPopup"
v-model="medalOpen" v-model:open="medalOpen"
class="badge-popup widgets-popup medal" class="badge-popup widgets-popup medal"
:trigger-element="$refs.medalButton" :trigger-element="medalButton"
> >
<ul> <ul>
<li <li
@ -28,9 +28,9 @@
<VPopup <VPopup
ref="titlePopup" ref="titlePopup"
v-model="titleOpen" v-model:open="titleOpen"
class="badge-popup widgets-popup title" class="badge-popup widgets-popup title"
:trigger-element="$refs.titleButton" :trigger-element="titleButton"
> >
<ul> <ul>
<li <li
@ -51,21 +51,32 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref } from 'vue'
import type { Ref } from 'vue'
import { addComponentListener, getComponentSettings } from '@/core/settings' import { addComponentListener, getComponentSettings } from '@/core/settings'
import { descendingSort } from '@/core/utils/sort' import { descendingSort } from '@/core/utils/sort'
import { DefaultWidget, VPopup } from '@/ui' import { DefaultWidget, VPopup } from '@/ui'
import { Medal, Title, Badge, getMedalList, getTitleList } from './badge'
const { options } = getComponentSettings('badgeHelper') import type { Badge } from './badge'
export default Vue.extend({ import { getMedalList, getTitleList, Medal, Title } from './badge'
import type { Options } from './index'
const { options } = getComponentSettings<Options>('badgeHelper')
export default defineComponent({
components: { components: {
DefaultWidget, DefaultWidget,
VPopup, VPopup,
}, },
setup: () => ({
medalPopup: ref(null) as Ref<InstanceType<typeof VPopup> | null>,
titlePopup: ref(null) as Ref<InstanceType<typeof VPopup> | null>,
medalButton: ref(null) as Ref<InstanceType<typeof DefaultWidget> | null>,
titleButton: ref(null) as Ref<InstanceType<typeof DefaultWidget> | null>,
}),
data() { data() {
return { return {
medalList: [], medalList: [] as Medal[],
titleList: [], titleList: [] as Title[],
medalOpen: false, medalOpen: false,
titleOpen: false, titleOpen: false,
grayEffect: true, grayEffect: true,

View File

@ -1,23 +1,12 @@
import { defineComponentMetadata } from '@/components/define' import { defineAsyncComponent } from 'vue'
import type { OptionsOfMetadata } from '@/components/define'
import { defineComponentMetadata, defineOptionsMetadata } from '@/components/define'
import { getNumberValidator, getUID, none } from '@/core/utils' import { getNumberValidator, getUID, none } from '@/core/utils'
import { autoMatchMedal } from './auto-match' import { autoMatchMedal } from './auto-match'
export const component = defineComponentMetadata({ const options = defineOptionsMetadata({
name: 'badgeHelper',
displayName: '直播勋章快速更换',
description: {
'zh-CN':
'在直播区中, 可从功能面板中直接切换勋章和头衔. 默认显示 256 个 (同时也是上限), 可在选项中修改.',
},
entry: () => autoMatchMedal(),
reload: none,
unload: none,
tags: [componentsTags.live],
widget: {
component: () => import('./BadgeHelper.vue').then(m => m.default),
condition: () => Boolean(getUID()),
},
options: {
autoMatchMedal: { autoMatchMedal: {
defaultValue: true, defaultValue: true,
displayName: '自动佩戴当前直播间勋章', displayName: '自动佩戴当前直播间勋章',
@ -36,6 +25,25 @@ export const component = defineComponentMetadata({
displayName: '显示勋章的未点亮状态', displayName: '显示勋章的未点亮状态',
defaultValue: true, defaultValue: true,
}, },
})
export type Options = OptionsOfMetadata<typeof options>
export const component = defineComponentMetadata({
name: 'badgeHelper',
displayName: '直播勋章快速更换',
description: {
'zh-CN':
'在直播区中, 可从功能面板中直接切换勋章和头衔. 默认显示 256 个 (同时也是上限), 可在选项中修改.',
}, },
entry: () => autoMatchMedal(),
reload: none,
unload: none,
tags: [componentsTags.live],
widget: {
component: defineAsyncComponent(() => import('./BadgeHelper.vue')),
condition: () => Boolean(getUID()),
},
options,
urlInclude: ['//live.bilibili.com'], urlInclude: ['//live.bilibili.com'],
}) })

View File

@ -6,17 +6,19 @@
:value="value" :value="value"
maxlength="30" maxlength="30"
@keydown.enter="send()" @keydown.enter="send()"
@input="updateValue($event.target.value)" @input="updateValue(($event.target as HTMLInputElement).value)"
/> />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'
import { select } from '@/core/spin-query' import { select } from '@/core/spin-query'
import { raiseEvent } from '@/core/utils' import { raiseEvent } from '@/core/utils'
import { originalTextAreaSelector, sendButtonSelector } from './original-elements' import { originalTextAreaSelector, sendButtonSelector } from './original-elements'
let changeEventHook = false let changeEventHook = false
export default Vue.extend({ export default defineComponent({
data() { data() {
return { return {
originalTextArea: null, originalTextArea: null,
@ -52,7 +54,7 @@ export default Vue.extend({
changeEventHook = true changeEventHook = true
} }
}, },
beforeDestroy() { beforeUnmount() {
this.originalTextArea.removeEventListener('input', this.listenChange) this.originalTextArea.removeEventListener('input', this.listenChange)
this.originalTextArea.removeEventListener('change', this.listenChange) this.originalTextArea.removeEventListener('change', this.listenChange)
}, },

View File

@ -1,14 +1,15 @@
import { waitForControlBar } from '@/components/live/live-control-bar'
import { defineComponentMetadata } from '@/components/define' import { defineComponentMetadata } from '@/components/define'
import { waitForControlBar } from '@/components/live/live-control-bar'
import { getUID } from '@/core/utils' import { getUID } from '@/core/utils'
import { liveUrls } from '@/core/utils/urls' import { liveUrls } from '@/core/utils/urls'
import { leftControllerSelector } from './original-elements' import { leftControllerSelector } from './original-elements'
const entry = async () => { const entry = async () => {
if (!getUID()) { if (!getUID()) {
return return
} }
let danmakuSendBarElement: Element let danmakuSendBarElement: Element | undefined
waitForControlBar({ waitForControlBar({
callback: async controlBar => { callback: async controlBar => {
const leftController = dq(controlBar, leftControllerSelector) as HTMLDivElement const leftController = dq(controlBar, leftControllerSelector) as HTMLDivElement
@ -20,8 +21,7 @@ const entry = async () => {
} }
if (!danmakuSendBarElement) { if (!danmakuSendBarElement) {
const { mountVueComponent } = await import('@/core/utils') const { mountVueComponent } = await import('@/core/utils')
const DanmakuSendBar = await import('./DanmakuSendbar.vue') danmakuSendBarElement = mountVueComponent(await import('./DanmakuSendbar.vue'))[0]
danmakuSendBarElement = mountVueComponent(DanmakuSendBar).$el
} }
leftController.insertAdjacentElement('afterend', danmakuSendBarElement) leftController.insertAdjacentElement('afterend', danmakuSendBarElement)
}, },

View File

@ -1,8 +1,9 @@
import { waitForControlBar } from '@/components/live/live-control-bar'
import { defineComponentMetadata } from '@/components/define' import { defineComponentMetadata } from '@/components/define'
import { waitForControlBar } from '@/components/live/live-control-bar'
import { select as spinSelect } from '@/core/spin-query' import { select as spinSelect } from '@/core/spin-query'
import { addStyle, removeStyle } from '@/core/style' import { addStyle, removeStyle } from '@/core/style'
import { liveUrls } from '@/core/utils/urls' import { liveUrls } from '@/core/utils/urls'
import componentStyle from './gift-box.scss' import componentStyle from './gift-box.scss'
/** /**

View File

@ -4,9 +4,10 @@
</a> </a>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'
import { DefaultWidget } from '@/ui' import { DefaultWidget } from '@/ui'
export default Vue.extend({ export default defineComponent({
components: { components: {
DefaultWidget, DefaultWidget,
}, },

View File

@ -1,3 +1,5 @@
import { defineAsyncComponent } from 'vue'
import { defineComponentMetadata } from '@/components/define' import { defineComponentMetadata } from '@/components/define'
import { matchUrlPattern } from '@/core/utils' import { matchUrlPattern } from '@/core/utils'
@ -13,7 +15,7 @@ export const component = defineComponentMetadata({
/^https:\/\/live\.bilibili\.com\/[\d]+/, /^https:\/\/live\.bilibili\.com\/[\d]+/,
], ],
widget: { widget: {
component: () => import('./Widget.vue').then(m => m.default), component: defineAsyncComponent(() => import('./Widget.vue')),
condition: () => matchUrlPattern(/^https:\/\/live\.bilibili\.com\/([\d]+)/), condition: () => matchUrlPattern(/^https:\/\/live\.bilibili\.com\/([\d]+)/),
}, },
}) })

View File

@ -1,5 +1,5 @@
import { toggleStyle } from '@/components/styled-component'
import { defineComponentMetadata } from '@/components/define' import { defineComponentMetadata } from '@/components/define'
import { toggleStyle } from '@/components/styled-component'
export const component = defineComponentMetadata({ export const component = defineComponentMetadata({
...toggleStyle('alwaysShowDuration', () => import('./always-show-duration.scss')), ...toggleStyle('alwaysShowDuration', () => import('./always-show-duration.scss')),

View File

@ -1,9 +1,6 @@
import { import type { OptionsOfMetadata } from '@/components/define'
OptionsOfMetadata, import { defineComponentMetadata, defineOptionsMetadata } from '@/components/define'
defineComponentMetadata, import type { ComponentEntry } from '@/components/types'
defineOptionsMetadata,
} from '@/components/define'
import { ComponentEntry } from '@/components/types'
import { addComponentListener } from '@/core/settings' import { addComponentListener } from '@/core/settings'
type Options = OptionsOfMetadata<typeof options> type Options = OptionsOfMetadata<typeof options>

View File

@ -9,13 +9,15 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, reactive } from 'vue'
import { addComponentListener } from '@/core/settings' import { addComponentListener } from '@/core/settings'
import { getUID } from '@/core/utils' import { getUID } from '@/core/utils'
import { ascendingSort } from '@/core/utils/sort' import { ascendingSort } from '@/core/utils/sort'
import { registerAndGetData } from '@/plugins/data' import { registerAndGetData } from '@/plugins/data'
import { getBuiltInItems } from './built-in-items' import { getBuiltInItems } from './built-in-items'
import type { CustomNavbarItemInit } from './custom-navbar-item'
import { import {
CustomNavbarItemInit,
CustomNavbarItem, CustomNavbarItem,
CustomNavbarItems, CustomNavbarItems,
CustomNavbarRenderedItems, CustomNavbarRenderedItems,
@ -23,10 +25,13 @@ import {
import CustomNavbarItemComponent from './CustomNavbarItem.vue' import CustomNavbarItemComponent from './CustomNavbarItem.vue'
import { checkTransparentFill } from './transparent-fill' import { checkTransparentFill } from './transparent-fill'
const [initItems] = registerAndGetData(CustomNavbarItems, getBuiltInItems()) const [initItems] = registerAndGetData(CustomNavbarItems, reactive(getBuiltInItems()))
const [renderedItems] = registerAndGetData(CustomNavbarRenderedItems, { const [renderedItems] = registerAndGetData(
CustomNavbarRenderedItems,
reactive({
items: [] as CustomNavbarItem[], items: [] as CustomNavbarItem[],
}) }),
)
const getItems = () => { const getItems = () => {
const isLogin = Boolean(getUID()) const isLogin = Boolean(getUID())
const items = (initItems as CustomNavbarItemInit[]) const items = (initItems as CustomNavbarItemInit[])
@ -41,7 +46,7 @@ const getItems = () => {
renderedItems.items = items renderedItems.items = items
return items return items
} }
export default Vue.extend({ export default defineComponent({
components: { components: {
NavbarItem: CustomNavbarItemComponent, NavbarItem: CustomNavbarItemComponent,
}, },
@ -49,14 +54,17 @@ export default Vue.extend({
return { return {
initItems, initItems,
items: getItems(), items: getItems(),
styles: [], styles: [] as string[],
height: CustomNavbarItem.navbarOptions.height, height: CustomNavbarItem.navbarOptions.height,
} }
}, },
watch: { watch: {
initItems() { initItems: {
handler() {
this.items = getItems() this.items = getItems()
}, },
deep: true,
},
}, },
async mounted() { async mounted() {
addComponentListener( addComponentListener(

View File

@ -46,7 +46,7 @@
:is="item.popupContent" :is="item.popupContent"
v-if="item.requestedPopup" v-if="item.requestedPopup"
ref="popup" ref="popup"
:container="$refs.popupContainer" :container="popupContainer"
:item="item" :item="item"
></component> ></component>
</div> </div>
@ -56,9 +56,13 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import type { Ref } from 'vue'
import { defineComponent, ref } from 'vue'
import { addComponentListener, removeComponentListener } from '@/core/settings' import { addComponentListener, removeComponentListener } from '@/core/settings'
import CustomNavbarLink from './CustomNavbarLink.vue'
import type { PopupContentInstance } from './custom-navbar-item'
import { CustomNavbarItem } from './custom-navbar-item' import { CustomNavbarItem } from './custom-navbar-item'
import CustomNavbarLink from './CustomNavbarLink.vue'
const isOpenInNewTab = (item: CustomNavbarItem) => { const isOpenInNewTab = (item: CustomNavbarItem) => {
const { name } = item const { name } = item
@ -68,7 +72,23 @@ const isOpenInNewTab = (item: CustomNavbarItem) => {
} }
return options.openInNewTab return options.openInNewTab
} }
export default Vue.extend({ function trigger(this: InstanceType<typeof ThisComponent>, initialPopup: boolean) {
const { popup } = this
if (!popup) {
return
}
const allowRefresh =
CustomNavbarItem.navbarOptions.refreshOnPopup &&
popup.popupRefresh &&
typeof popup.popupRefresh === 'function'
if (!initialPopup && allowRefresh) {
popup.popupRefresh()
}
if (popup.popupShow && typeof popup.popupShow === 'function') {
popup.popupShow()
}
}
const ThisComponent = defineComponent({
components: { components: {
CustomNavbarLink, CustomNavbarLink,
}, },
@ -78,6 +98,10 @@ export default Vue.extend({
required: true, required: true,
}, },
}, },
setup: () => ({
popup: ref(null) as Ref<PopupContentInstance | null>,
popupContainer: ref(null) as Ref<HTMLDivElement | null>,
}),
data() { data() {
return { return {
newTab: isOpenInNewTab(this.item), newTab: isOpenInNewTab(this.item),
@ -98,7 +122,7 @@ export default Vue.extend({
removeComponentListener('customNavbar.openInNewTab', listener) removeComponentListener('customNavbar.openInNewTab', listener)
} }
}, },
beforeDestroy() { beforeUnmount() {
this.cancelListeners?.() this.cancelListeners?.()
}, },
methods: { methods: {
@ -119,22 +143,10 @@ export default Vue.extend({
'iframe-container': item.iframeName, 'iframe-container': item.iframeName,
} }
}, },
triggerPopupShow: lodash.debounce(function trigger(initialPopup: boolean) { triggerPopupShow: lodash.debounce(trigger, 300) as unknown as (
const { popup } = this.$refs this: any,
if (!popup) { initialPopup: boolean,
return ) => void,
}
const allowRefresh =
CustomNavbarItem.navbarOptions.refreshOnPopup &&
popup.popupRefresh &&
typeof popup.popupRefresh === 'function'
if (!initialPopup && allowRefresh) {
popup.popupRefresh()
}
if (popup.popupShow && typeof popup.popupShow === 'function') {
popup.popupShow()
}
}, 300),
async requestPopup() { async requestPopup() {
const { item } = this as { const { item } = this as {
item: CustomNavbarItem item: CustomNavbarItem
@ -165,6 +177,7 @@ export default Vue.extend({
// }, // },
}, },
}) })
export default ThisComponent
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -1,10 +1,12 @@
<template> <template>
<a v-bind="$attrs" :target="newTab ? '_blank' : null" v-on="$listeners"> <a :target="newTab ? '_blank' : null">
<slot /> <slot />
</a> </a>
</template> </template>
<script lang="ts"> <script lang="ts">
export default Vue.extend({ import { defineComponent } from 'vue'
export default defineComponent({
props: { props: {
newTab: { newTab: {
type: Boolean, type: Boolean,

View File

@ -1,4 +1,4 @@
import { CustomNavbarItemInit } from './custom-navbar-item' import type { CustomNavbarItemInit } from './custom-navbar-item'
import { messages } from './messages/messages' import { messages } from './messages/messages'
import { ranking } from './ranking/ranking' import { ranking } from './ranking/ranking'
import { userInfo } from './user-info/user-info' import { userInfo } from './user-info/user-info'

View File

@ -1,8 +1,20 @@
import { createPopper, Instance as Popper } from '@popperjs/core' import type { Instance as Popper } from '@popperjs/core'
import { VueModule, Executable } from '@/core/common-types' import { createPopper } from '@popperjs/core'
import { getComponentSettings, addComponentListener } from '@/core/settings'
import type { Component, ComponentPublicInstance } from 'vue'
import { addComponentListener, getComponentSettings } from '@/core/settings'
import type { CustomNavbarOptions } from '.' import type { CustomNavbarOptions } from '.'
export interface PopupContentInstance extends ComponentPublicInstance {
popupRefresh?(): void
popupShow(): void
}
export interface PopupContent {
new (): PopupContentInstance
}
export const CustomNavbarItems = 'customNavbar.items' export const CustomNavbarItems = 'customNavbar.items'
export const CustomNavbarRenderedItems = 'customNavbar.renderedItems' export const CustomNavbarRenderedItems = 'customNavbar.renderedItems'
/** /**
@ -13,8 +25,8 @@ export interface CustomNavbarItemInit {
name: string name: string
/** 显示名称 */ /** 显示名称 */
displayName: string displayName: string
/** 内容 */ /** 内容。被创建时传入属性item: CustomNavbarItem */
content: Executable<VueModule> | string content: Component | string
/** 设定CSS flex样式 (grow, shrink, basis) */ /** 设定CSS flex样式 (grow, shrink, basis) */
flexStyle?: string flexStyle?: string
@ -27,7 +39,7 @@ export interface CustomNavbarItemInit {
/** `content`指定的内容mount之后要执行的代码 */ /** `content`指定的内容mount之后要执行的代码 */
contentMounted?: (item: CustomNavbarItem) => Promise<void> | void contentMounted?: (item: CustomNavbarItem) => Promise<void> | void
/** 点击运行的代码段 */ /** 点击运行的代码段 */
clickAction?: Executable clickAction?: (event: MouseEvent) => void
/** 获取或设置提示数字, 将显示在顶部 */ /** 获取或设置提示数字, 将显示在顶部 */
notifyCount?: number notifyCount?: number
/** 是否在触屏状态下不响应点击 */ /** 是否在触屏状态下不响应点击 */
@ -35,8 +47,8 @@ export interface CustomNavbarItemInit {
/** 是否仅在登录后显示 */ /** 是否仅在登录后显示 */
loginRequired?: boolean loginRequired?: boolean
/** 弹窗内容 */ /** 弹窗内容。创建其实例时传入参数有container: HTMLElement, item: CustomNavbarItem */
popupContent?: Executable<VueModule> popupContent?: PopupContent | undefined
/** 设为大于0的值时, 表示预计的弹窗宽度, 将会用于边缘检测, 防止超出viewport */ /** 设为大于0的值时, 表示预计的弹窗宽度, 将会用于边缘检测, 防止超出viewport */
boundingWidth?: number boundingWidth?: number
/** 不使用默认的弹窗padding */ /** 不使用默认的弹窗padding */
@ -50,19 +62,19 @@ export interface CustomNavbarItemInit {
export class CustomNavbarItem implements Required<CustomNavbarItemInit> { export class CustomNavbarItem implements Required<CustomNavbarItemInit> {
name: string name: string
displayName: string displayName: string
content: Executable<VueModule> | string content: Component | string
flexStyle = '0 0 auto' flexStyle = '0 0 auto'
disabled = false disabled = false
href: string = null href: string = null
active = false active = false
clickAction: Executable = none clickAction: (event: MouseEvent) => void = none
contentMounted: (item: CustomNavbarItem) => Promise<void> | void = none contentMounted: (item: CustomNavbarItem) => Promise<void> | void = none
notifyCount = 0 notifyCount = 0
touch = false touch = false
loginRequired = false loginRequired = false
popupContent: Executable<VueModule> = null popupContent: PopupContent | undefined
popper: Popper = null popper: Popper = null
boundingWidth = 0 boundingWidth = 0
noPopupPadding = false noPopupPadding = false

View File

@ -1,4 +1,4 @@
import { ComponentEntry } from '@/components/types' import type { ComponentEntry } from '@/components/types'
import { addComponentListener } from '@/core/settings' import { addComponentListener } from '@/core/settings'
import { isIframe, isNotHtml, matchUrlPattern, mountVueComponent } from '@/core/utils' import { isIframe, isNotHtml, matchUrlPattern, mountVueComponent } from '@/core/utils'
import { setupNotifyStyle } from './notify-style' import { setupNotifyStyle } from './notify-style'
@ -42,14 +42,10 @@ export const entry: ComponentEntry = async ({ metadata: { name } }) => {
true, true,
) )
} }
const CustomNavbar = await import('./CustomNavbar.vue') const [el, vm] = mountVueComponent(await import('./CustomNavbar.vue'))
const customNavbar: Vue & { document.body.insertAdjacentElement('beforeend', el)
styles: string[]
toggleStyle: (value: boolean, style: string) => void
} = mountVueComponent(CustomNavbar)
document.body.insertAdjacentElement('beforeend', customNavbar.$el)
;['fill', 'shadow', 'blur'].forEach(style => { ;['fill', 'shadow', 'blur'].forEach(style => {
addComponentListener(`${name}.${style}`, value => customNavbar.toggleStyle(value, style), true) addComponentListener(`${name}.${style}`, value => vm.toggleStyle(value, style), true)
}) })
setupNotifyStyle() setupNotifyStyle()
} }

View File

@ -3,38 +3,39 @@
class="favorites-folder-select" class="favorites-folder-select"
round round
:items="folders" :items="folders"
:key-mapper="f => f.id" :key-mapper="f => (f as FavoritesFolder).id"
:value="folder" :value="folder"
@change="change($event)" @update:value="change($event)"
> >
<template #item="{ item }"> {{ item.name }} ({{ item.count }}) </template> <template #item="{ item }"> {{ item.name }} ({{ item.count }}) </template>
</VDropdown> </VDropdown>
</template> </template>
<script lang="ts"> <script lang="ts">
import { VDropdown } from '@/ui' import { defineComponent } from 'vue'
import { getUID } from '@/core/utils' import type { PropType } from 'vue'
import { getJsonWithCredentials } from '@/core/ajax' import { getJsonWithCredentials } from '@/core/ajax'
import { getComponentSettings } from '@/core/settings' import { getComponentSettings } from '@/core/settings'
import { FavoritesFolder, notSelectedFolder } from './favorites-folder' import { getUID } from '@/core/utils'
import { VDropdown } from '@/ui'
import type { FavoritesFolder } from './favorites-folder'
import { notSelectedFolder } from './favorites-folder'
const navbarOptions = getComponentSettings('customNavbar').options const navbarOptions = getComponentSettings('customNavbar').options
export default Vue.extend({ export default defineComponent({
components: { components: {
VDropdown, VDropdown,
}, },
model: {
prop: 'folder',
event: 'change',
},
props: { props: {
folder: { folder: {
type: Object, type: Object as PropType<FavoritesFolder>,
required: true, required: true,
}, },
}, },
emits: ['update:folder'],
data() { data() {
return { return {
folders: [], folders: [] as FavoritesFolder[],
} }
}, },
async created() { async created() {
@ -59,16 +60,16 @@ export default Vue.extend({
const { lastFavoriteFolder } = navbarOptions const { lastFavoriteFolder } = navbarOptions
const folder = this.folders.find((f: FavoritesFolder) => f.id === lastFavoriteFolder) const folder = this.folders.find((f: FavoritesFolder) => f.id === lastFavoriteFolder)
if (folder) { if (folder) {
this.$emit('change', folder) this.$emit('update:folder', folder)
} else { } else {
this.$emit('change', this.folders[0]) this.$emit('update:folder', this.folders[0])
} }
} }
}, },
methods: { methods: {
change(folder: FavoritesFolder) { change(folder: FavoritesFolder) {
navbarOptions.lastFavoriteFolder = folder.id navbarOptions.lastFavoriteFolder = folder.id
this.$emit('change', folder) this.$emit('update:folder', folder)
}, },
}, },
}) })

View File

@ -1,9 +1,9 @@
<template> <template>
<div class="favorites-list"> <div ref="el" class="favorites-list">
<div class="header"> <div class="header">
<FavoritesFolderSelect v-model="folder"></FavoritesFolderSelect> <FavoritesFolderSelect v-model:folder="folder"></FavoritesFolderSelect>
<div class="search"> <div class="search">
<TextBox v-model="search" linear placeholder="搜索"></TextBox> <TextBox v-model:text="search" linear placeholder="搜索"></TextBox>
</div> </div>
<a class="operation" :href="playLink" title="播放全部" target="_blank"> <a class="operation" :href="playLink" title="播放全部" target="_blank">
<VButton round class="play-all"> <VButton round class="play-all">
@ -65,16 +65,18 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { VLoading, VEmpty, VIcon, VButton, TextBox, DpiImage, ScrollTrigger } from '@/ui' import { defineComponent } from 'vue'
import { formatDate, formatDuration } from '@/core/utils/formatters' import type { VideoCard } from '@/components/feeds/video-card'
import { getUID } from '@/core/utils'
import { getJsonWithCredentials } from '@/core/ajax' import { getJsonWithCredentials } from '@/core/ajax'
import { logError } from '@/core/utils/log'
import { VideoCard } from '@/components/feeds/video-card'
import { getComponentSettings } from '@/core/settings' import { getComponentSettings } from '@/core/settings'
import { getUID } from '@/core/utils'
import { formatDate, formatDuration } from '@/core/utils/formatters'
import { logError } from '@/core/utils/log'
import { DpiImage, ScrollTrigger, TextBox, VButton, VEmpty, VIcon, VLoading } from '@/ui'
import { popupProps, usePopup } from '../mixins'
import { notSelectedFolder } from './favorites-folder' import { notSelectedFolder } from './favorites-folder'
import FavoritesFolderSelect from './FavoritesFolderSelect.vue' import FavoritesFolderSelect from './FavoritesFolderSelect.vue'
import { popperMixin } from '../mixins'
/* /*
新版收藏夹 API 新版收藏夹 API
@ -110,7 +112,7 @@ const favoriteItemMapper = (item: any): FavoritesItemInfo => ({
upFaceUrl: item.upper.face.replace('http:', 'https:'), upFaceUrl: item.upper.face.replace('http:', 'https:'),
upID: item.upper.mid, upID: item.upper.mid,
}) })
async function searchAllList() { async function searchAllList(this: InstanceType<typeof ThisComponent>) {
if (!this.searching) { if (!this.searching) {
return return
} }
@ -148,7 +150,7 @@ async function searchAllList() {
this.loading = false this.loading = false
} }
} }
export default Vue.extend({ const ThisComponent = defineComponent({
components: { components: {
FavoritesFolderSelect, FavoritesFolderSelect,
VLoading, VLoading,
@ -159,12 +161,13 @@ export default Vue.extend({
DpiImage, DpiImage,
ScrollTrigger, ScrollTrigger,
}, },
mixins: [popperMixin], props: popupProps,
setup: usePopup,
data() { data() {
return { return {
loading: true, loading: true,
cards: [], cards: [] as FavoritesItemInfo[],
filteredCards: [], filteredCards: [] as FavoritesItemInfo[],
page: 1, page: 1,
hasMorePage: true, hasMorePage: true,
searchPage: 1, searchPage: 1,
@ -174,24 +177,24 @@ export default Vue.extend({
} }
}, },
computed: { computed: {
searching() { searching(): boolean {
return this.search !== '' return this.search !== ''
}, },
moreLink() { moreLink(): string {
const { id } = this.folder const { id } = this.folder
if (id === 0) { if (id === 0) {
return `https://space.bilibili.com/${getUID()}/favlist` return `https://space.bilibili.com/${getUID()}/favlist`
} }
return `https://space.bilibili.com/${getUID()}/favlist?fid=${id}` return `https://space.bilibili.com/${getUID()}/favlist?fid=${id}`
}, },
playLink() { playLink(): string {
const { id } = this.folder const { id } = this.folder
if (id === 0) { if (id === 0) {
return undefined return undefined
} }
return `https://www.bilibili.com/medialist/play/ml${id}` return `https://www.bilibili.com/medialist/play/ml${id}`
}, },
canLoadMore() { canLoadMore(): boolean {
if (this.searching) { if (this.searching) {
return this.hasMoreSearchPage return this.hasMoreSearchPage
} }
@ -258,7 +261,7 @@ export default Vue.extend({
logError(error) logError(error)
} }
}, },
debounceSearchAllList: lodash.debounce(searchAllList, 200), debounceSearchAllList: lodash.debounce(searchAllList, 200) as unknown as () => Promise<void>,
scrollTrigger() { scrollTrigger() {
if (this.searching) { if (this.searching) {
this.debounceSearchAllList() this.debounceSearchAllList()
@ -268,6 +271,7 @@ export default Vue.extend({
}, },
}, },
}) })
export default ThisComponent
</script> </script>
<style lang="scss"> <style lang="scss">
@import 'common'; @import 'common';
@ -340,7 +344,7 @@ export default Vue.extend({
@include no-scrollbar(); @include no-scrollbar();
padding: 0 12px; padding: 0 12px;
padding-bottom: 12px; padding-bottom: 12px;
&-enter, &-enter-from,
&-leave-to { &-leave-to {
opacity: 0; opacity: 0;
transform: translateY(-16px) scale(0.9); transform: translateY(-16px) scale(0.9);

View File

@ -1,5 +1,7 @@
import { defineAsyncComponent } from 'vue'
import { getUID } from '@/core/utils' import { getUID } from '@/core/utils'
import { CustomNavbarItemInit } from '../custom-navbar-item'
import type { CustomNavbarItemInit } from '../custom-navbar-item'
const href = `https://space.bilibili.com/${getUID()}/favlist` const href = `https://space.bilibili.com/${getUID()}/favlist`
export const favorites: CustomNavbarItemInit = { export const favorites: CustomNavbarItemInit = {
@ -14,5 +16,5 @@ export const favorites: CustomNavbarItemInit = {
boundingWidth: 380, boundingWidth: 380,
noPopupPadding: true, noPopupPadding: true,
popupContent: () => import('./NavbarFavorites.vue').then(m => m.default), popupContent: defineAsyncComponent(() => import('./NavbarFavorites.vue')),
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="navbar-feeds"> <div ref="el" class="navbar-feeds">
<TabControl ref="tabControl" :tabs="tabs" more-link="https://t.bilibili.com/"> <TabControl ref="tabControl" :tabs="tabs" more-link="https://t.bilibili.com/">
<template #more-link> <template #more-link>
所有动态 所有动态
@ -9,18 +9,25 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { TabControl, VIcon } from '@/ui' import type { Ref } from 'vue'
import { defineComponent, ref } from 'vue'
import { feedsCardTypes } from '@/components/feeds/api' import { feedsCardTypes } from '@/components/feeds/api'
import { getNotifyCount } from '@/components/feeds/notify' import { getNotifyCount } from '@/components/feeds/notify'
import { popperMixin } from '../mixins' import { TabControl, VIcon } from '@/ui'
import { popupProps, usePopup } from '../mixins'
import { tabs } from './tabs/tabs' import { tabs } from './tabs/tabs'
export default Vue.extend({ export default defineComponent({
components: { components: {
TabControl, TabControl,
VIcon, VIcon,
}, },
mixins: [popperMixin], props: popupProps,
setup: props => ({
...usePopup(props),
tabControl: ref(null) as Ref<InstanceType<typeof TabControl> | null>,
}),
data() { data() {
return { return {
tabs, tabs,
@ -37,7 +44,7 @@ export default Vue.extend({
async refreshNotifyCount() { async refreshNotifyCount() {
// const totalJson = await getFeeds(navbarFeedsTypeList) // const totalJson = await getFeeds(navbarFeedsTypeList)
// this.item.notifyCount = lodash.get(totalJson, 'data.update_num', 0) // this.item.notifyCount = lodash.get(totalJson, 'data.update_num', 0)
const { tabControl } = this.$refs const { tabControl } = this
tabs.forEach(async tab => { tabs.forEach(async tab => {
if (tabControl.selectedTab === tab) { if (tabControl.selectedTab === tab) {
return return

View File

@ -1,4 +1,5 @@
import { CustomNavbarItemInit } from '../custom-navbar-item' import { defineAsyncComponent } from 'vue'
import type { CustomNavbarItemInit } from '../custom-navbar-item'
export const feeds: CustomNavbarItemInit = { export const feeds: CustomNavbarItemInit = {
name: 'feeds', name: 'feeds',
@ -20,7 +21,7 @@ export const feeds: CustomNavbarItemInit = {
}, },
loginRequired: true, loginRequired: true,
popupContent: () => import('./NavbarFeeds.vue').then(m => m.default), popupContent: defineAsyncComponent(() => import('./NavbarFeeds.vue')),
boundingWidth: 300, boundingWidth: 300,
noPopupPadding: true, noPopupPadding: true,
} }

View File

@ -1,3 +1,31 @@
<script setup lang="ts">
import { feedsCardTypes } from '@/components/feeds/api'
import type { BangumiCard as BangumiCardData } from '@/components/feeds/bangumi-card'
import { isNewID } from '@/components/feeds/notify'
import BangumiCard from '@/components/feeds/BangumiCard.vue'
import { ScrollTrigger, VEmpty, VLoading } from '@/ui'
import { useNextPage } from './next-page'
const { loading, cards, hasMorePage, nextPage } = useNextPage(
feedsCardTypes.bangumi,
(card: any): BangumiCardData & { new: boolean } => {
const cardJson = JSON.parse(card.card)
return {
id: card.desc.dynamic_id_str,
title: cardJson.apiSeasonInfo.title,
coverUrl: cardJson.apiSeasonInfo.cover,
epCoverUrl: cardJson.cover,
epTitle: cardJson.new_desc,
url: cardJson.url,
get new() {
return isNewID(this.id)
},
}
},
)
</script>
<template> <template>
<div class="bangumi-feeds"> <div class="bangumi-feeds">
<VLoading v-if="loading"></VLoading> <VLoading v-if="loading"></VLoading>
@ -10,35 +38,7 @@
</template> </template>
</div> </div>
</template> </template>
<script lang="ts">
import { feedsCardTypes } from '@/components/feeds/api'
import { isNewID } from '@/components/feeds/notify'
import { BangumiCard } from '@/components/feeds/bangumi-card'
import BangumiCardComponent from '@/components/feeds/BangumiCard.vue'
import { nextPageMixin } from './next-page'
export default Vue.extend({
components: {
BangumiCard: BangumiCardComponent,
},
mixins: [
nextPageMixin(feedsCardTypes.bangumi, (card: any) => {
const cardJson = JSON.parse(card.card)
return {
id: card.desc.dynamic_id_str,
title: cardJson.apiSeasonInfo.title,
coverUrl: cardJson.apiSeasonInfo.cover,
epCoverUrl: cardJson.cover,
epTitle: cardJson.new_desc,
url: cardJson.url,
get new() {
return isNewID(this.id)
},
} as BangumiCard
}),
],
})
</script>
<style lang="scss"> <style lang="scss">
.bangumi-feeds { .bangumi-feeds {
display: flex; display: flex;

View File

@ -1,28 +1,15 @@
<template> <script setup lang="ts">
<div class="column-feeds"> import { ScrollTrigger, VEmpty, VLoading } from '@/ui'
<VLoading v-if="loading"></VLoading>
<VEmpty v-else-if="!loading && cards.length === 0"></VEmpty>
<template v-else>
<div class="columns-feeds-content">
<ColumnCard v-for="c of cards" :key="c.id" :is-new="c.new" :data="c"></ColumnCard>
</div>
<ScrollTrigger v-if="hasMorePage" @trigger="nextPage()"></ScrollTrigger>
</template>
</div>
</template>
<script lang="ts">
import { feedsCardTypes } from '@/components/feeds/api' import { feedsCardTypes } from '@/components/feeds/api'
import type { ColumnCard as ColumnCardData } from '@/components/feeds/column-card'
import ColumnCard from '@/components/feeds/ColumnCard.vue'
import { isNewID } from '@/components/feeds/notify' import { isNewID } from '@/components/feeds/notify'
import { ColumnCard } from '@/components/feeds/column-card'
import ColumnCardComponent from '@/components/feeds/ColumnCard.vue'
import { nextPageMixin } from './next-page'
export default Vue.extend({ import { useNextPage } from './next-page'
components: {
ColumnCard: ColumnCardComponent, const { loading, cards, hasMorePage, nextPage } = useNextPage(
}, feedsCardTypes.column,
mixins: [ (card: any): ColumnCardData & { new: boolean } => {
nextPageMixin(feedsCardTypes.column, (card: any) => {
const cardJson = JSON.parse(card.card) const cardJson = JSON.parse(card.card)
return { return {
id: card.desc.dynamic_id_str, id: card.desc.dynamic_id_str,
@ -37,11 +24,24 @@ export default Vue.extend({
get new() { get new() {
return isNewID(this.id) return isNewID(this.id)
}, },
} as ColumnCard }
}), },
], )
})
</script> </script>
<template>
<div class="column-feeds">
<VLoading v-if="loading"></VLoading>
<VEmpty v-else-if="!loading && cards.length === 0"></VEmpty>
<template v-else>
<div class="columns-feeds-content">
<ColumnCard v-for="c of cards" :key="c.id" :is-new="c.new" :data="c"></ColumnCard>
</div>
<ScrollTrigger v-if="hasMorePage" @trigger="nextPage()"></ScrollTrigger>
</template>
</div>
</template>
<style lang="scss"> <style lang="scss">
.column-feeds { .column-feeds {
display: flex; display: flex;

View File

@ -16,11 +16,13 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { VLoading, VEmpty, DpiImage } from '@/ui' import { defineComponent } from 'vue'
import { responsiveGetPages, getJsonWithCredentials } from '@/core/ajax' import { getJsonWithCredentials, responsiveGetPages } from '@/core/ajax'
import { LiveFeedItem } from './live-feed-item' import { DpiImage, VEmpty, VLoading } from '@/ui'
export default Vue.extend({ import type { LiveFeedItem } from './live-feed-item'
export default defineComponent({
components: { components: {
VLoading, VLoading,
VEmpty, VEmpty,
@ -29,7 +31,7 @@ export default Vue.extend({
data() { data() {
return { return {
loading: true, loading: true,
rawItems: [], rawItems: [] as unknown[],
hasMorePage: true, hasMorePage: true,
} }
}, },
@ -42,7 +44,7 @@ export default Vue.extend({
upName: card.uname, upName: card.uname,
url: card.link, url: card.link,
}) })
return (this.rawItems as any[]).map(parseLiveCard) return this.rawItems.map(parseLiveCard)
}, },
}, },
async created() { async created() {
@ -66,7 +68,7 @@ export default Vue.extend({
@include v-center(); @include v-center();
.live-feeds-content { .live-feeds-content {
align-self: stretch; align-self: stretch;
&-enter, &-enter-from,
&-leave-to { &-leave-to {
opacity: 0; opacity: 0;
transform: translateY(-16px) scale(0.9); transform: translateY(-16px) scale(0.9);

View File

@ -1,3 +1,104 @@
<script setup lang="ts">
import { computed } from 'vue'
import { feedsCardTypes, groupVideoFeeds } from '@/components/feeds/api'
import { isNewID } from '@/components/feeds/notify'
import type { VideoCard as VideoCardData } from '@/components/feeds/video-card'
import VideoCard from '@/components/feeds/VideoCard.vue'
import { formatCount, formatDuration } from '@/core/utils/formatters'
import { VLoading, VEmpty, ScrollTrigger } from '@/ui'
import { useNextPage } from './next-page'
const formatPubTime = (pubTime: number) => {
const now = Number(new Date())
const pubDate = new Date(pubTime)
const time = [pubDate.getHours(), pubDate.getMinutes(), pubDate.getSeconds()]
.map(it => it.toString().padStart(2, '0'))
.join(':')
let date: number[]
if (new Date(now).getFullYear() !== pubDate.getFullYear()) {
date = [pubDate.getFullYear(), pubDate.getMonth() + 1, pubDate.getDate()]
} else {
date = [pubDate.getMonth() + 1, pubDate.getDate()]
}
return `${date.map(it => it.toString().padStart(2, '0')).join('-')} ${time}`
}
const formatPubTimeText = (pubTime: number) => {
const now = Number(new Date())
const oneDayBefore = now - 1000 * 3600 * 24
if (oneDayBefore < pubTime) {
const diffHours = Math.round((now - pubTime) / 1000 / 3600)
if (diffHours === 0) {
const diffMinutes = Math.round((now - pubTime) / 1000 / 60)
if (diffMinutes === 0) {
return '刚刚'
}
return `${diffMinutes}分钟前`
}
return `${diffHours}小时前`
}
const pubDate = new Date(pubTime)
let date: number[]
if (new Date(now).getFullYear() !== pubDate.getFullYear()) {
date = [pubDate.getFullYear(), pubDate.getMonth() + 1, pubDate.getDate()]
} else {
date = [pubDate.getMonth() + 1, pubDate.getDate()]
}
return `${date.map(it => it.toString().padStart(2, '0')).join('-')}`
}
const jsonMapper = (card: any): VideoCardData & { new: boolean } => {
const cardJson = JSON.parse(card.card)
return {
id: card.desc.dynamic_id_str,
aid: cardJson.aid,
bvid: card.desc.bvid,
videoUrl: `https://www.bilibili.com/${card.desc.bvid}`,
coverUrl: cardJson.pic,
title: cardJson.title,
duration: cardJson.duration,
durationText: formatDuration(cardJson.duration),
description: cardJson.desc,
pubTime: formatPubTime(cardJson.pubdate * 1000),
pubTimeText: formatPubTimeText(cardJson.pubdate * 1000),
upFaceUrl: card.desc.user_profile.info.face,
upName: card.desc.user_profile.info.uname,
upID: card.desc.user_profile.info.uid,
watchlater: true,
playCount: formatCount(cardJson.stat.view),
get new() {
return isNewID(this.id)
},
} as VideoCardData & { new: boolean }
}
const onCardsUpdate = (cards: (VideoCardData & { new: boolean })[]) => {
return groupVideoFeeds(cards)
}
const { loading, cards, hasMorePage, nextPage } = useNextPage<VideoCardData & { new: boolean }>(
feedsCardTypes.video,
jsonMapper,
onCardsUpdate,
)
const columnedCards = computed(
(): {
left: (VideoCardData & { new: boolean })[]
right: (VideoCardData & { new: boolean })[]
} => {
return {
left: cards.value.filter((_, index) => index % 2 === 0),
right: cards.value.filter((_, index) => index % 2 !== 0),
} as {
left: (VideoCardData & { new: boolean })[]
right: (VideoCardData & { new: boolean })[]
}
},
)
</script>
<template> <template>
<div class="video-feeds"> <div class="video-feeds">
<VLoading v-if="loading"></VLoading> <VLoading v-if="loading"></VLoading>
@ -29,97 +130,7 @@
</template> </template>
</div> </div>
</template> </template>
<script lang="ts">
import { VideoCard } from '@/components/feeds/video-card'
import { formatDuration, formatCount } from '@/core/utils/formatters'
import { isNewID } from '@/components/feeds/notify'
import { feedsCardTypes, groupVideoFeeds } from '@/components/feeds/api'
import VideoCardComponent from '@/components/feeds/VideoCard.vue'
import { nextPageMixin } from './next-page'
const formatPubTime = (pubTime: number) => {
const now = Number(new Date())
const pubDate = new Date(pubTime)
const time = [pubDate.getHours(), pubDate.getMinutes(), pubDate.getSeconds()]
.map(it => it.toString().padStart(2, '0'))
.join(':')
let date: number[]
if (new Date(now).getFullYear() !== pubDate.getFullYear()) {
date = [pubDate.getFullYear(), pubDate.getMonth() + 1, pubDate.getDate()]
} else {
date = [pubDate.getMonth() + 1, pubDate.getDate()]
}
return `${date.map(it => it.toString().padStart(2, '0')).join('-')} ${time}`
}
const formatPubTimeText = (pubTime: number) => {
const now = Number(new Date())
const oneDayBefore = now - 1000 * 3600 * 24
if (oneDayBefore < pubTime) {
const diffHours = Math.round((now - pubTime) / 1000 / 3600)
if (diffHours === 0) {
const diffMinutes = Math.round((now - pubTime) / 1000 / 60)
if (diffMinutes === 0) {
return '刚刚'
}
return `${diffMinutes}分钟前`
}
return `${diffHours}小时前`
}
const pubDate = new Date(pubTime)
let date: number[]
if (new Date(now).getFullYear() !== pubDate.getFullYear()) {
date = [pubDate.getFullYear(), pubDate.getMonth() + 1, pubDate.getDate()]
} else {
date = [pubDate.getMonth() + 1, pubDate.getDate()]
}
return `${date.map(it => it.toString().padStart(2, '0')).join('-')}`
}
export default Vue.extend({
components: {
VideoCard: VideoCardComponent,
},
mixins: [
nextPageMixin(feedsCardTypes.video, (card: any) => {
const cardJson = JSON.parse(card.card)
return {
id: card.desc.dynamic_id_str,
aid: cardJson.aid,
bvid: card.desc.bvid,
videoUrl: `https://www.bilibili.com/${card.desc.bvid}`,
coverUrl: cardJson.pic,
title: cardJson.title,
duration: cardJson.duration,
durationText: formatDuration(cardJson.duration),
description: cardJson.desc,
pubTime: formatPubTime(cardJson.pubdate * 1000),
pubTimeText: formatPubTimeText(cardJson.pubdate * 1000),
upFaceUrl: card.desc.user_profile.info.face,
upName: card.desc.user_profile.info.uname,
upID: card.desc.user_profile.info.uid,
watchlater: true,
playCount: formatCount(cardJson.stat.view),
get new() {
return isNewID(this.id)
},
} as VideoCard
}),
],
computed: {
columnedCards() {
const { cards } = this as { cards: VideoCard[] }
return {
left: cards.filter((_, index) => index % 2 === 0),
right: cards.filter((_, index) => index % 2 !== 0),
}
},
},
methods: {
onCardsUpdate(cards: VideoCard[]) {
return groupVideoFeeds(cards)
},
},
})
</script>
<style lang="scss" scoped> <style lang="scss" scoped>
.video-feeds { .video-feeds {
display: flex; display: flex;
@ -139,7 +150,7 @@ export default Vue.extend({
justify-content: space-between; justify-content: space-between;
width: 356px; width: 356px;
.cards { .cards {
&-enter, &-enter-from,
&-leave-to { &-leave-to {
opacity: 0; opacity: 0;
transform: translateY(-16px) scale(0.9); transform: translateY(-16px) scale(0.9);

View File

@ -1,86 +1,81 @@
import { import { computed, ref, type Ref, type ComputedRef } from 'vue'
getFeeds, import type { FeedsCardType } from '@/components/feeds/api'
FeedsCardType, import { applyContentFilter, getFeeds, isPreOrderedVideo } from '@/components/feeds/api'
applyContentFilter,
isPreOrderedVideo,
} from '@/components/feeds/api'
import { descendingStringSort } from '@/core/utils/sort'
import { logError } from '@/core/utils/log'
import { setLatestID } from '@/components/feeds/notify' import { setLatestID } from '@/components/feeds/notify'
import { VLoading, VEmpty, ScrollTrigger } from '@/ui' import { logError } from '@/core/utils/log'
import { descendingStringSort } from '@/core/utils/sort'
/** /**
* Vue Mixin *
* @param type * @param type
* @param jsonMapper JSON数据的映射函数 * @param jsonMapper JSON数据的映射函数
* @param onCardsUpdate
*/ */
export const nextPageMixin = <MappedItem extends { id: string }, RawItem>( export const useNextPage = <MappedItem extends { id: string } = { id: string }, RawItem = unknown>(
type: FeedsCardType, type: FeedsCardType,
jsonMapper: (obj: RawItem) => MappedItem, jsonMapper: (obj: RawItem) => MappedItem,
) => onCardsUpdate?: (cards: MappedItem[]) => MappedItem[],
Vue.extend({ ): {
components: { loading: Ref<boolean>
VLoading, cards: Ref<MappedItem[]>
VEmpty, hasMorePage: Ref<boolean>
ScrollTrigger, sortedCards: ComputedRef<MappedItem[]>
}, nextPage: () => Promise<void>
data() { } => {
return { const loading = ref(true)
loading: true, const cards: Ref<MappedItem[]> = ref([])
cards: [], const hasMorePage = ref(true)
hasMorePage: true,
} const sortedCards = computed(() => [...cards.value].sort(descendingStringSort(it => it.id)))
},
computed: { const nextPage = async () => {
sortedCards() {
return ([...this.cards] as MappedItem[]).sort(descendingStringSort(it => it.id))
},
},
async created() {
await this.nextPage()
const cards = this.sortedCards as MappedItem[]
if (cards.length > 0) {
setLatestID(cards[0].id)
// console.log('setLatestID', cards[0].id)
}
},
methods: {
async nextPage() {
try { try {
const cards: MappedItem[] = this.sortedCards const lastCardID = sortedCards.value[sortedCards.value.length - 1]?.id ?? 0
const lastCardID = cards[cards.length - 1]?.id ?? 0
const json = await getFeeds(type, lastCardID) const json = await getFeeds(type, lastCardID)
console.log(json) console.log(json)
if (json.code !== 0) { if (json.code !== 0) {
this.hasMorePage = false hasMorePage.value = false
throw new Error(json.message) throw new Error(json.message)
} }
const jsonCards = lodash.get(json, 'data.cards', []).map(jsonMapper) as MappedItem[] const jsonCards = lodash.get<RawItem[]>(json, 'data.cards', []).map(jsonMapper)
let concatCards = applyContentFilter( let concatCards = applyContentFilter(
cards sortedCards.value
.concat(jsonCards) .concat(jsonCards)
.sort(descendingStringSort(it => it.id)) .sort(descendingStringSort(it => it.id))
.filter(card => !isPreOrderedVideo(card)), .filter(card => !isPreOrderedVideo(card)),
) )
if (concatCards.length > 0 && this.onCardsUpdate) { if (concatCards.length > 0 && onCardsUpdate) {
concatCards = this.onCardsUpdate(concatCards) concatCards = onCardsUpdate(concatCards)
} }
console.log('nextPage get', concatCards) console.log('nextPage get', concatCards)
this.cards = concatCards cards.value = concatCards
if (this.cards.length === 0) { if (cards.value.length === 0) {
this.hasMorePage = false hasMorePage.value = false
return return
} }
this.hasMorePage = hasMorePage.value = lastCardID === 0 ? true : Boolean(lodash.get(json, 'data.has_more', true))
lastCardID === 0 ? true : Boolean(lodash.get(json, 'data.has_more', true))
} catch (error) { } catch (error) {
logError(error) logError(error)
} finally { } finally {
this.loading = false loading.value = false
}
}
nextPage().then(() => {
if (sortedCards.value.length > 0) {
setLatestID(sortedCards.value[0].id)
// console.log('setLatestID', sortedCards.value[0].id)
} }
},
},
}) })
return {
loading,
cards,
hasMorePage,
sortedCards,
nextPage,
}
}

View File

@ -1,31 +1,32 @@
import { TabMappings } from '@/ui/tab-mapping' import { defineAsyncComponent } from 'vue'
import type { TabMappings } from '@/ui/tab-mapping'
export const tabs: TabMappings = [ export const tabs: TabMappings = [
{ {
name: 'video', name: 'video',
displayName: '视频', displayName: '视频',
component: () => import('./VideoFeeds.vue').then(m => m.default), component: defineAsyncComponent(() => import('./VideoFeeds.vue')),
activeLink: 'https://t.bilibili.com/?tab=video', activeLink: 'https://t.bilibili.com/?tab=video',
count: 0, count: 0,
}, },
{ {
name: 'bangumi', name: 'bangumi',
displayName: '番剧', displayName: '番剧',
component: () => import('./BangumiFeeds.vue').then(m => m.default), component: defineAsyncComponent(() => import('./BangumiFeeds.vue')),
activeLink: 'https://t.bilibili.com/?tab=pgc', activeLink: 'https://t.bilibili.com/?tab=pgc',
count: 0, count: 0,
}, },
{ {
name: 'column', name: 'column',
displayName: '专栏', displayName: '专栏',
component: () => import('./ColumnFeeds.vue').then(m => m.default), component: defineAsyncComponent(() => import('./ColumnFeeds.vue')),
activeLink: 'https://t.bilibili.com/?tab=article', activeLink: 'https://t.bilibili.com/?tab=article',
count: 0, count: 0,
}, },
{ {
name: 'live', name: 'live',
displayName: '直播', displayName: '直播',
component: () => import('./LiveFeeds.vue').then(m => m.default), component: defineAsyncComponent(() => import('./LiveFeeds.vue')),
activeLink: 'https://link.bilibili.com/p/center/index#/user-center/follow/1', activeLink: 'https://link.bilibili.com/p/center/index#/user-center/follow/1',
count: 0, count: 0,
}, },

View File

@ -1,4 +1,4 @@
import { CustomNavbarItemInit } from '../custom-navbar-item' import type { CustomNavbarItemInit } from '../custom-navbar-item'
const count = 4 const count = 4
export const blanks: CustomNavbarItemInit[] = new Array(count).fill(0).map((_, index) => ({ export const blanks: CustomNavbarItemInit[] = new Array(count).fill(0).map((_, index) => ({

View File

@ -1,9 +1,9 @@
<template> <template>
<div class="custom-navbar-history-list"> <div ref="el" class="custom-navbar-history-list">
<div class="header"> <div class="header">
<div class="header-row"> <div class="header-row">
<div class="search"> <div class="search">
<TextBox v-model="search" placeholder="搜索" linear></TextBox> <TextBox v-model:text="search" placeholder="搜索" linear></TextBox>
</div> </div>
<div class="operations"> <div class="operations">
<div class="operation" @click="toggleHistoryPause"> <div class="operation" @click="toggleHistoryPause">
@ -30,7 +30,7 @@
:class="{ checked: t.checked }" :class="{ checked: t.checked }"
:checked="t.checked" :checked="t.checked"
:disabled="loading" :disabled="loading"
@change="toggleTypeFilter(t)" @update:checked="toggleTypeFilter(t)"
> >
{{ t.displayName }} {{ t.displayName }}
</RadioButton> </RadioButton>
@ -105,23 +105,29 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'
import { bilibiliApi, getJsonWithCredentials, postTextWithCredentials } from '@/core/ajax' import { bilibiliApi, getJsonWithCredentials, postTextWithCredentials } from '@/core/ajax'
import { formData, getCsrf } from '@/core/utils' import { formData, getCsrf } from '@/core/utils'
import { descendingSort } from '@/core/utils/sort' import { descendingSort } from '@/core/utils/sort'
import { import {
VButton,
VIcon,
RadioButton,
TextBox,
VLoading,
VEmpty,
ScrollTrigger,
DpiImage, DpiImage,
RadioButton,
ScrollTrigger,
TextBox,
VButton,
VEmpty,
VIcon,
VLoading,
} from '@/ui' } from '@/ui'
import { popperMixin } from '../mixins'
import { types, TypeFilter, HistoryItem, getHistoryItems, group, HistoryType } from './types'
export default Vue.extend({ import { popupProps, usePopup } from '../mixins'
import type { HistoryItem, TypeFilter } from './types'
import { getHistoryItems, group, HistoryType, types } from './types'
function search(this: InstanceType<typeof ThisComponent>) {
this.reloadHistoryItems()
}
const ThisComponent = defineComponent({
components: { components: {
VButton, VButton,
VIcon, VIcon,
@ -132,28 +138,27 @@ export default Vue.extend({
ScrollTrigger, ScrollTrigger,
DpiImage, DpiImage,
}, },
mixins: [popperMixin], props: popupProps,
setup: usePopup,
data() { data() {
return { return {
types, types,
search: '', search: '',
viewTime: 0, viewTime: 0,
cards: [], cards: [] as HistoryItem[],
groups: [], groups: [] as { name: string; items: HistoryItem[] }[],
loading: true, loading: true,
hasMorePage: true, hasMorePage: true,
paused: false, paused: false,
} }
}, },
computed: { computed: {
canNextPage() { canNextPage(): boolean {
return this.search === '' && !this.loading && this.hasMorePage return this.search === '' && !this.loading && this.hasMorePage
}, },
}, },
watch: { watch: {
search: lodash.debounce(function search() { search: lodash.debounce(search, 200) as unknown as () => void,
this.reloadHistoryItems()
}, 200),
}, },
async created() { async created() {
try { try {
@ -239,6 +244,7 @@ export default Vue.extend({
}, },
}, },
}) })
export default ThisComponent
</script> </script>
<style lang="scss"> <style lang="scss">
@import 'common'; @import 'common';
@ -253,7 +259,7 @@ export default Vue.extend({
@include v-stretch(); @include v-stretch();
justify-content: center; justify-content: center;
@mixin items-animation { @mixin items-animation {
&-enter, &-enter-from,
&-leave-to { &-leave-to {
opacity: 0; opacity: 0;
transform: translateY(-16px) scale(0.9); transform: translateY(-16px) scale(0.9);

View File

@ -1,4 +1,5 @@
import { CustomNavbarItemInit } from '../custom-navbar-item' import { defineAsyncComponent } from 'vue'
import type { CustomNavbarItemInit } from '../custom-navbar-item'
const href = 'https://www.bilibili.com/account/history' const href = 'https://www.bilibili.com/account/history'
export const history: CustomNavbarItemInit = { export const history: CustomNavbarItemInit = {
@ -13,5 +14,5 @@ export const history: CustomNavbarItemInit = {
boundingWidth: 400, boundingWidth: 400,
noPopupPadding: true, noPopupPadding: true,
popupContent: () => import('./NavbarHistory.vue').then(m => m.default), popupContent: defineAsyncComponent(() => import('./NavbarHistory.vue')),
} }

View File

@ -1,4 +1,4 @@
import { getJsonWithCredentials, bilibiliApi } from '@/core/ajax' import { bilibiliApi, getJsonWithCredentials } from '@/core/ajax'
import { fixed } from '@/core/utils' import { fixed } from '@/core/utils'
import { formatDuration } from '@/core/utils/formatters' import { formatDuration } from '@/core/utils/formatters'

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="home-popup" role="list"> <div ref="el" class="home-popup" role="list">
<div <div
v-for="[name, data] of Object.entries(categories)" v-for="[name, data] of Object.entries(categories)"
:key="name" :key="name"
@ -30,16 +30,20 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { categories, Category } from '@/components/utils/categories/data' import { defineComponent } from 'vue'
import { popperMixin } from '../mixins' import type { Category } from '@/components/utils/categories/data'
import { categories } from '@/components/utils/categories/data'
import { popupProps, usePopup } from '../mixins'
const clone = lodash.cloneDeep(categories) const clone = lodash.cloneDeep(categories)
Object.values(clone).forEach((data: any) => { Object.values(clone).forEach((data: any) => {
data.count = null data.count = null
}) })
let regionCountFetched = false let regionCountFetched = false
export default Vue.extend({ export default defineComponent({
mixins: [popperMixin], props: popupProps,
setup: usePopup,
data() { data() {
return { return {
categories: clone, categories: clone,

View File

@ -1,4 +1,5 @@
import { CustomNavbarItemInit } from '../custom-navbar-item' import { defineAsyncComponent } from 'vue'
import type { CustomNavbarItemInit } from '../custom-navbar-item'
export const home: CustomNavbarItemInit = { export const home: CustomNavbarItemInit = {
name: 'home', name: 'home',
@ -9,5 +10,5 @@ export const home: CustomNavbarItemInit = {
touch: true, touch: true,
boundingWidth: 366, boundingWidth: 366,
popupContent: () => import('./NavbarHome.vue').then(m => m.default), popupContent: defineAsyncComponent(() => import('./NavbarHome.vue')),
} }

View File

@ -1,21 +1,24 @@
<template> <script setup lang="ts">
<iframe :src="item.src" frameborder="0" :width="item.width" :height="item.height"></iframe>
</template>
<script lang="ts">
import type { PropType } from 'vue'
import type { NavbarIframeConfig } from './iframe' import type { NavbarIframeConfig } from './iframe'
import { popperMixin } from '../mixins' import type { CustomNavbarItem } from '../custom-navbar-item'
import { CustomNavbarItem } from '../custom-navbar-item' import { usePopup } from '../mixins'
export default Vue.extend({ const props = defineProps<{
name: 'IframePopup', item: CustomNavbarItem & NavbarIframeConfig
mixins: [popperMixin], container: HTMLElement
props: { }>()
item: {
type: CustomNavbarItem as unknown as PropType<CustomNavbarItem & NavbarIframeConfig>, const { el, popupShow } = usePopup(props)
required: true,
}, defineExpose({ popupShow })
},
})
</script> </script>
<template>
<iframe
ref="el"
:src="item.src"
frameborder="0"
:width="item.width"
:height="item.height"
></iframe>
</template>

View File

@ -1,4 +1,5 @@
import { CustomNavbarItemInit } from '../custom-navbar-item' import { defineAsyncComponent } from 'vue'
import type { CustomNavbarItemInit } from '../custom-navbar-item'
export interface NavbarIframeConfig { export interface NavbarIframeConfig {
src: string src: string
@ -16,7 +17,7 @@ const getIframeItem = (config: NavbarIframeConfig): CustomNavbarItemInit & Navba
touch: true, touch: true,
popupContent: () => import('./IframePopup.vue').then(m => m.default), popupContent: defineAsyncComponent(() => import('./IframePopup.vue')),
boundingWidth: config.width, boundingWidth: config.width,
noPopupPadding: true, noPopupPadding: true,
transparentPopup: true, transparentPopup: true,

View File

@ -1,14 +1,13 @@
import { import { defineAsyncComponent } from 'vue'
defineComponentMetadata, import type { OptionsOfMetadata } from '@/components/define'
defineOptionsMetadata, import { defineComponentMetadata, defineOptionsMetadata } from '@/components/define'
OptionsOfMetadata, import type { LaunchBarActionProvider } from '@/components/launch-bar/launch-bar-action'
} from '@/components/define'
import { LaunchBarActionProvider } from '@/components/launch-bar/launch-bar-action'
import { urlInclude, urlExclude } from './urls'
import { entry } from './entry'
import { getNumberValidator } from '@/core/utils' import { getNumberValidator } from '@/core/utils'
import { NavbarNotifyStyle } from './notify-style' import { NavbarNotifyStyle } from './notify-style'
import { entry } from './entry'
import { urlExclude, urlInclude } from './urls'
const styleID = 'custom-navbar-style' const styleID = 'custom-navbar-style'
const options = defineOptionsMetadata({ const options = defineOptionsMetadata({
hidden: { hidden: {
@ -127,7 +126,7 @@ export const component = defineComponentMetadata({
// const { addImportantStyle } = await import('@/core/style') // const { addImportantStyle } = await import('@/core/style')
// addImportantStyle(style, styleID) // addImportantStyle(style, styleID)
}, },
extraOptions: () => import('./settings/ExtraOptions.vue').then(m => m.default), extraOptions: defineAsyncComponent(() => import('./settings/ExtraOptions.vue')),
plugin: { plugin: {
displayName: '自定义顶栏 - 功能扩展', displayName: '自定义顶栏 - 功能扩展',
setup: ({ addData }) => { setup: ({ addData }) => {

View File

@ -10,11 +10,12 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { addComponentListener } from '@/core/settings' import { defineComponent } from 'vue'
import { getJson } from '@/core/ajax' import { getJson } from '@/core/ajax'
import { addComponentListener } from '@/core/settings'
import { VIcon } from '@/ui' import { VIcon } from '@/ui'
export default Vue.extend({ export default defineComponent({
name: 'NavbarLogo', name: 'NavbarLogo',
components: { components: {
VIcon, VIcon,

View File

@ -1,9 +1,10 @@
import { CustomNavbarItemInit } from '../custom-navbar-item' import { defineAsyncComponent } from 'vue'
import type { CustomNavbarItemInit } from '../custom-navbar-item'
export const logo: CustomNavbarItemInit = { export const logo: CustomNavbarItemInit = {
name: 'logo', name: 'logo',
displayName: 'Logo', displayName: 'Logo',
content: () => import('./NavbarLogo.vue').then(m => m.default), content: defineAsyncComponent(() => import('./NavbarLogo.vue')),
href: 'https://www.bilibili.com/', href: 'https://www.bilibili.com/',
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="messages-popup" role="list"> <div ref="el" class="messages-popup" role="list">
<div v-for="e of entries" :key="e.name" class="message-entry" role="listitem"> <div v-for="e of entries" :key="e.name" class="message-entry" role="listitem">
<a <a
:data-prop="e.prop" :data-prop="e.prop"
@ -14,8 +14,10 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'
import { getJsonWithCredentials } from '@/core/ajax' import { getJsonWithCredentials } from '@/core/ajax'
import { popperMixin } from '../mixins'
import { popupProps, usePopup } from '../mixins'
interface MessageEntry { interface MessageEntry {
prop?: string prop?: string
@ -58,9 +60,10 @@ const entries = [
name: '消息设置', name: '消息设置',
}, },
] as MessageEntry[] ] as MessageEntry[]
export default Vue.extend({ export default defineComponent({
name: 'MessagesPopup', name: 'MessagesPopup',
mixins: [popperMixin], props: popupProps,
setup: usePopup,
data() { data() {
return { return {
entries: entries.map(e => { entries: entries.map(e => {

View File

@ -1,4 +1,5 @@
import { CustomNavbarItemInit } from '../custom-navbar-item' import { defineAsyncComponent } from 'vue'
import type { CustomNavbarItemInit } from '../custom-navbar-item'
const messagesUrl = 'https://message.bilibili.com/' const messagesUrl = 'https://message.bilibili.com/'
export const messages: CustomNavbarItemInit = { export const messages: CustomNavbarItemInit = {
@ -11,6 +12,6 @@ export const messages: CustomNavbarItemInit = {
loginRequired: true, loginRequired: true,
touch: true, touch: true,
popupContent: () => import('./NavbarMessages.vue').then(m => m.default), popupContent: defineAsyncComponent(() => import('./NavbarMessages.vue')),
lazy: false, lazy: false,
} }

View File

@ -1,7 +1,7 @@
import { type Ref, ref, onMounted } from 'vue'
import { CustomNavbarItem } from './custom-navbar-item' import { CustomNavbarItem } from './custom-navbar-item'
export const popperMixin = Vue.extend({ export const popupProps = {
props: {
item: { item: {
type: CustomNavbarItem, type: CustomNavbarItem,
required: true, required: true,
@ -10,18 +10,26 @@ export const popperMixin = Vue.extend({
type: HTMLElement, type: HTMLElement,
required: true, required: true,
}, },
}, }
mounted() {
const navBarItem = this.item as CustomNavbarItem export const usePopup = (props: {
const containerElement = this.container as HTMLElement item: CustomNavbarItem
container: HTMLElement
}): {
el: Ref<HTMLElement | null>
popupShow: () => void
} => {
const el = ref<HTMLElement | null>(null)
onMounted(() => {
const navBarItem = props.item
const containerElement = props.container
if (containerElement) { if (containerElement) {
navBarItem?.usePopper(containerElement, this.$el.parentElement) navBarItem?.usePopper(containerElement, el.value.parentElement)
} }
}, })
methods: { const popupShow = (): void => {
popupShow() { const navBarItem = props.item
const navBarItem = this.item as CustomNavbarItem navBarItem?.popper?.update().then()
navBarItem?.popper?.update() }
}, return { el, popupShow }
}, }
})

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="ranking-popup" role="list"> <div ref="el" class="ranking-popup" role="list">
<div v-for="e of entries" :key="e.name" class="ranking-entry" role="listitem"> <div v-for="e of entries" :key="e.name" class="ranking-entry" role="listitem">
<a target="_blank" :href="e.href">{{ e.name }}</a> <a target="_blank" :href="e.href">{{ e.name }}</a>
</div> </div>
@ -7,7 +7,8 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { popperMixin } from '../mixins' import { defineComponent } from 'vue'
import { popupProps, usePopup } from '../mixins'
interface RankingEntry { interface RankingEntry {
href: string href: string
@ -39,9 +40,10 @@ const entries = [
name: '短剧榜', name: '短剧榜',
}, },
] as RankingEntry[] ] as RankingEntry[]
export default Vue.extend({ export default defineComponent({
name: 'RankingPopup', name: 'RankingPopup',
mixins: [popperMixin], props: popupProps,
setup: usePopup,
data() { data() {
return { return {
entries, entries,

View File

@ -1,4 +1,5 @@
import { CustomNavbarItemInit } from '../custom-navbar-item' import { defineAsyncComponent } from 'vue'
import type { CustomNavbarItemInit } from '../custom-navbar-item'
const rankingUrl = 'https://www.bilibili.com/v/popular/rank/' const rankingUrl = 'https://www.bilibili.com/v/popular/rank/'
export const ranking: CustomNavbarItemInit = { export const ranking: CustomNavbarItemInit = {
@ -10,5 +11,5 @@ export const ranking: CustomNavbarItemInit = {
active: document.URL.startsWith(rankingUrl), active: document.URL.startsWith(rankingUrl),
touch: true, touch: true,
popupContent: () => import('./NavbarRanking.vue').then(m => m.default), popupContent: defineAsyncComponent(() => import('./NavbarRanking.vue')),
} }

View File

@ -4,9 +4,10 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'
import LaunchBar from '@/components/launch-bar/LaunchBar.vue' import LaunchBar from '@/components/launch-bar/LaunchBar.vue'
export default Vue.extend({ export default defineComponent({
components: { components: {
LaunchBar, LaunchBar,
}, },

View File

@ -1,9 +1,10 @@
import { CustomNavbarItemInit } from '../custom-navbar-item' import { defineAsyncComponent } from 'vue'
import type { CustomNavbarItemInit } from '../custom-navbar-item'
export const search: CustomNavbarItemInit = { export const search: CustomNavbarItemInit = {
name: 'search', name: 'search',
displayName: '搜索', displayName: '搜索',
content: () => import('./NavbarSearch.vue').then(m => m.default), content: defineAsyncComponent(() => import('./NavbarSearch.vue')),
// 禁用元素本身的 hover 效果之类的, 作为 content 的 NavbarSearch 是依然能够响应的 // 禁用元素本身的 hover 效果之类的, 作为 content 的 NavbarSearch 是依然能够响应的
disabled: true, disabled: true,

View File

@ -11,15 +11,21 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { Ref } from 'vue'
import { defineComponent, ref } from 'vue'
import { getUID } from '@/core/utils' import { getUID } from '@/core/utils'
import { VIcon, VButton } from '@/ui' import { VButton, VIcon } from '@/ui'
import { setTriggerElement, loadNavbarSettings, toggleNavbarSettings } from './vm'
export default Vue.extend({ import { loadNavbarSettings, setTriggerElement, toggleNavbarSettings } from './vm'
export default defineComponent({
components: { components: {
VIcon, VIcon,
VButton, VButton,
}, },
setup: () => ({
button: ref(null) as Ref<InstanceType<typeof VButton> | null>,
}),
data() { data() {
return { return {
login: Boolean(getUID()), login: Boolean(getUID()),
@ -29,7 +35,7 @@ export default Vue.extend({
async loadNavbarSettings() { async loadNavbarSettings() {
const isFirstLoad = await loadNavbarSettings() const isFirstLoad = await loadNavbarSettings()
if (isFirstLoad) { if (isFirstLoad) {
const triggerButton = this.$refs.button.$el as HTMLElement const triggerButton = this.button.$el as HTMLElement
setTriggerElement(triggerButton) setTriggerElement(triggerButton)
} }
}, },

View File

@ -1,7 +1,7 @@
<template> <template>
<VPopup <VPopup
ref="popup" ref="popup"
v-model="open" v-model:open="open"
class="custom-navbar-settings" class="custom-navbar-settings"
fixed fixed
:lazy="false" :lazy="false"
@ -27,7 +27,7 @@
@mouseover="peekPadding(true)" @mouseover="peekPadding(true)"
@mouseout="peekPadding(false)" @mouseout="peekPadding(false)"
> >
<VSlider v-model="padding" :min="0" :max="40" :step="0.5"></VSlider> <VSlider v-model:value="padding" :min="0" :max="40" :step="0.5"></VSlider>
<div class="padding-value">{{ padding.toFixed(1) }}%</div> <div class="padding-value">{{ padding.toFixed(1) }}%</div>
</div> </div>
</div> </div>
@ -68,36 +68,42 @@
</VPopup> </VPopup>
</template> </template>
<script lang="ts"> <script lang="ts">
import { SortableEvent } from 'sortablejs' import type { Ref } from 'vue'
import { VPopup, VIcon, VSlider, VLoading } from '@/ui' import { defineComponent, ref } from 'vue'
import type { SortableEvent } from 'sortablejs'
import { SortableJSLibrary } from '@/core/runtime-library'
import { addComponentListener } from '@/core/settings' import { addComponentListener } from '@/core/settings'
import { dqa } from '@/core/utils' import { dqa } from '@/core/utils'
import { SortableJSLibrary } from '@/core/runtime-library'
import { getData } from '@/plugins/data' import { getData } from '@/plugins/data'
import { VIcon, VLoading, VPopup, VSlider } from '@/ui'
import { CustomNavbarItem, CustomNavbarRenderedItems } from '../custom-navbar-item' import { CustomNavbarItem, CustomNavbarRenderedItems } from '../custom-navbar-item'
import { checkSequentialOrder, sortItems } from './orders' import { checkSequentialOrder, sortItems } from './orders'
const { navbarOptions } = CustomNavbarItem const { navbarOptions } = CustomNavbarItem
function padding(this: InstanceType<typeof ThisComponent>, newValue: number) {
navbarOptions.padding = newValue
}
const [rendered] = getData(CustomNavbarRenderedItems) as [ const [rendered] = getData(CustomNavbarRenderedItems) as [
{ {
items: CustomNavbarItem[] items: CustomNavbarItem[]
}, },
] ]
export default Vue.extend({ const ThisComponent = defineComponent({
components: { components: {
VPopup, VPopup,
VIcon, VIcon,
VSlider, VSlider,
VLoading, VLoading,
}, },
props: { setup: () => ({
triggerElement: { popup: ref(null) as Ref<InstanceType<typeof VPopup> | null>,
type: HTMLElement, navbarSortList: ref(null) as Ref<HTMLDivElement | null>,
default: null, }),
},
},
data() { data() {
return { return {
triggerElement: null as HTMLElement | null,
open: false, open: false,
padding: navbarOptions.padding, padding: navbarOptions.padding,
rendered, rendered,
@ -106,9 +112,7 @@ export default Vue.extend({
} }
}, },
watch: { watch: {
padding: lodash.debounce((newValue: number) => { padding: lodash.debounce(padding, 200) as unknown as (newValue: number) => void,
navbarOptions.padding = newValue
}, 200),
}, },
async mounted() { async mounted() {
addComponentListener('customNavbar.padding', (newValue: number) => { addComponentListener('customNavbar.padding', (newValue: number) => {
@ -116,7 +120,7 @@ export default Vue.extend({
this.padding = newValue this.padding = newValue
} }
}) })
const list: HTMLElement = this.$refs.navbarSortList const list: HTMLElement = this.navbarSortList
const Sortable = await SortableJSLibrary const Sortable = await SortableJSLibrary
Sortable.create(list, { Sortable.create(list, {
delay: 100, delay: 100,
@ -134,7 +138,7 @@ export default Vue.extend({
}, },
methods: { methods: {
toggle() { toggle() {
this.$refs.popup.toggle() this.popup.toggle()
}, },
peekPadding(peek: boolean) { peekPadding(peek: boolean) {
dqa('.custom-navbar .padding').forEach(it => it.classList.toggle('peek', peek)) dqa('.custom-navbar .padding').forEach(it => it.classList.toggle('peek', peek))
@ -143,7 +147,7 @@ export default Vue.extend({
item.element?.classList.toggle('peek', peek) item.element?.classList.toggle('peek', peek)
}, },
onSort(e: SortableEvent) { onSort(e: SortableEvent) {
const container = this.$refs.navbarSortList as HTMLElement const container = this.navbarSortList as HTMLElement
const element = e.item const element = e.item
console.log(`${element.getAttribute('data-name')} ${e.oldIndex}->${e.newIndex}`) console.log(`${element.getAttribute('data-name')} ${e.oldIndex}->${e.newIndex}`)
const ordersMap = Object.fromEntries( const ordersMap = Object.fromEntries(
@ -164,6 +168,7 @@ export default Vue.extend({
}, },
}, },
}) })
export default ThisComponent
</script> </script>
<style lang="scss"> <style lang="scss">
@import 'common'; @import 'common';

View File

@ -1,9 +1,7 @@
import { mountVueComponent } from '@/core/utils' import { mountVueComponent } from '@/core/utils'
import type NavbarSettings from './NavbarSettings.vue'
let navbarSettingsVM: Vue & { let navbarSettingsVM: InstanceType<typeof NavbarSettings> | undefined
toggle: () => void
triggerElement: HTMLElement
}
export const setTriggerElement = (element: HTMLElement) => { export const setTriggerElement = (element: HTMLElement) => {
if (!navbarSettingsVM) { if (!navbarSettingsVM) {
return return
@ -14,9 +12,9 @@ export const loadNavbarSettings = async () => {
if (navbarSettingsVM) { if (navbarSettingsVM) {
return false return false
} }
const NavbarSettings = await import('./NavbarSettings.vue').then(m => m.default) const [el, vm] = mountVueComponent(await import('./NavbarSettings.vue'))
navbarSettingsVM = mountVueComponent(NavbarSettings) navbarSettingsVM = vm
document.body.insertAdjacentElement('beforeend', navbarSettingsVM.$el) document.body.insertAdjacentElement('beforeend', el)
return true return true
} }
export const toggleNavbarSettings = async () => { export const toggleNavbarSettings = async () => {

View File

@ -1,4 +1,4 @@
import { CustomNavbarItemInit } from '../custom-navbar-item' import type { CustomNavbarItemInit } from '../custom-navbar-item'
interface SimpleLinkConfig { interface SimpleLinkConfig {
name: string name: string

View File

@ -2,9 +2,10 @@
<SubscriptionsList type="bangumi" :filter="filter"></SubscriptionsList> <SubscriptionsList type="bangumi" :filter="filter"></SubscriptionsList>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'
import SubscriptionsList from './SubscriptionsList.vue' import SubscriptionsList from './SubscriptionsList.vue'
export default Vue.extend({ export default defineComponent({
components: { components: {
SubscriptionsList, SubscriptionsList,
}, },

View File

@ -2,9 +2,10 @@
<SubscriptionsList type="cinema" :filter="filter"></SubscriptionsList> <SubscriptionsList type="cinema" :filter="filter"></SubscriptionsList>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'
import SubscriptionsList from './SubscriptionsList.vue' import SubscriptionsList from './SubscriptionsList.vue'
export default Vue.extend({ export default defineComponent({
components: { components: {
SubscriptionsList, SubscriptionsList,
}, },

View File

@ -1,21 +1,25 @@
<template> <template>
<div class="navbar-subscriptions"> <div ref="el" class="navbar-subscriptions">
<TabControl ref="tabControl" :tabs="tabs" :more-link="moreLink"> <TabControl ref="tabControl" :tabs="tabs" :more-link="moreLink">
<template #header-item> <template #header-item>
<div class="navbar-subscriptions-filter"> <div class="navbar-subscriptions-filter">
<VDropdown v-model="selectedFilter" round :items="filterItems" /> <VDropdown v-model:value="selectedFilter" round :items="filterItems" />
</div> </div>
</template> </template>
</TabControl> </TabControl>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { TabControl, VDropdown } from '@/ui' import type { Ref } from 'vue'
import { TabMapping, TabMappings } from '@/ui/tab-mapping' import { defineComponent, defineAsyncComponent, ref } from 'vue'
import { getUID } from '@/core/utils' import { getUID } from '@/core/utils'
import { popperMixin } from '../mixins' import { TabControl, VDropdown } from '@/ui'
import type { TabMapping, TabMappings } from '@/ui/tab-mapping'
import { popupProps, usePopup } from '../mixins'
import { SubscriptionTypes } from './subscriptions' import { SubscriptionTypes } from './subscriptions'
import { SubscriptionStatus, SubscriptionStatusFilter } from './types' import type { SubscriptionStatusFilter } from './types'
import { SubscriptionStatus } from './types'
const filterItems: { const filterItems: {
name: string name: string
@ -55,12 +59,16 @@ const filterItems: {
displayName: '看过', displayName: '看过',
}, },
] ]
export default Vue.extend({ export default defineComponent({
components: { components: {
TabControl, TabControl,
VDropdown, VDropdown,
}, },
mixins: [popperMixin], props: popupProps,
setup: props => ({
...usePopup(props),
tabControl: ref(null) as Ref<InstanceType<typeof TabControl> | null>,
}),
data() { data() {
const uid = getUID() const uid = getUID()
return { return {
@ -81,7 +89,7 @@ export default Vue.extend({
name: SubscriptionTypes.Bangumi, name: SubscriptionTypes.Bangumi,
displayName: '追番', displayName: '追番',
activeLink: `https://space.bilibili.com/${this.uid}/bangumi`, activeLink: `https://space.bilibili.com/${this.uid}/bangumi`,
component: () => import('./BangumiSubscriptions.vue').then(m => m.default), component: defineAsyncComponent(() => import('./BangumiSubscriptions.vue')),
propsData: { propsData: {
filter: this.selectedFilter.value, filter: this.selectedFilter.value,
}, },
@ -90,7 +98,7 @@ export default Vue.extend({
name: SubscriptionTypes.Cinema, name: SubscriptionTypes.Cinema,
displayName: '追剧', displayName: '追剧',
activeLink: `https://space.bilibili.com/${this.uid}/cinema`, activeLink: `https://space.bilibili.com/${this.uid}/cinema`,
component: () => import('./CinemaSubscriptions.vue').then(m => m.default), component: defineAsyncComponent(() => import('./CinemaSubscriptions.vue')),
propsData: { propsData: {
filter: this.selectedFilter.value, filter: this.selectedFilter.value,
}, },

View File

@ -41,22 +41,24 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'
import { getJsonWithCredentials } from '@/core/ajax'
import { getUID } from '@/core/utils' import { getUID } from '@/core/utils'
import { logError } from '@/core/utils/log' import { logError } from '@/core/utils/log'
import { DpiImage, VLoading, VEmpty, VIcon, ScrollTrigger } from '@/ui' import { DpiImage, ScrollTrigger, VEmpty, VIcon, VLoading } from '@/ui'
import { getJsonWithCredentials } from '@/core/ajax'
import { SubscriptionTypes } from './subscriptions' import { SubscriptionTypes } from './subscriptions'
import { SubscriptionItem, SubscriptionStatus, SubscriptionStatusFilter } from './types' import { type SubscriptionItem, SubscriptionStatus, type SubscriptionStatusFilter } from './types'
const getStatusText = (status: SubscriptionStatus) => { const getStatusText = (status: SubscriptionStatus) => {
switch (status) { switch (status) {
case SubscriptionStatus.ToView: case SubscriptionStatus.ToView:
return '想看' return '想看'
case SubscriptionStatus.Viewed:
return '看过'
case SubscriptionStatus.Viewing: case SubscriptionStatus.Viewing:
default: default:
return '在看' return '在看'
case SubscriptionStatus.Viewed:
return '看过'
} }
} }
const subscriptionSorter = (a: SubscriptionItem, b: SubscriptionItem) => { const subscriptionSorter = (a: SubscriptionItem, b: SubscriptionItem) => {
@ -70,7 +72,7 @@ const subscriptionSorter = (a: SubscriptionItem, b: SubscriptionItem) => {
} }
return statusA - statusB return statusA - statusB
} }
export default Vue.extend({ export default defineComponent({
components: { components: {
DpiImage, DpiImage,
VLoading, VLoading,
@ -92,7 +94,7 @@ export default Vue.extend({
return { return {
loading: true, loading: true,
hasMorePage: true, hasMorePage: true,
cards: [], cards: [] as any[],
page: 1, page: 1,
} }
}, },
@ -114,7 +116,7 @@ export default Vue.extend({
const followStatus = filter.viewAll ? 0 : (filter.status as number) const followStatus = filter.viewAll ? 0 : (filter.status as number)
const params = new URLSearchParams({ const params = new URLSearchParams({
type: this.type !== SubscriptionTypes.Bangumi ? '2' : '1', type: this.type !== SubscriptionTypes.Bangumi ? '2' : '1',
pn: this.page, pn: this.page.toString(),
ps: '16', ps: '16',
vmid: getUID(), vmid: getUID(),
follow_status: followStatus.toString(), follow_status: followStatus.toString(),

View File

@ -1,5 +1,7 @@
import { defineAsyncComponent } from 'vue'
import { getUID } from '@/core/utils' import { getUID } from '@/core/utils'
import { CustomNavbarItemInit } from '../custom-navbar-item'
import type { CustomNavbarItemInit } from '../custom-navbar-item'
export enum SubscriptionTypes { export enum SubscriptionTypes {
Bangumi = 'bangumi', Bangumi = 'bangumi',
@ -22,5 +24,5 @@ export const subscriptions: CustomNavbarItemInit = {
boundingWidth: 380, boundingWidth: 380,
noPopupPadding: true, noPopupPadding: true,
popupContent: () => import('./NavbarSubscriptions.vue').then(m => m.default), popupContent: defineAsyncComponent(() => import('./NavbarSubscriptions.vue')),
} }

View File

@ -5,9 +5,10 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'
import { VIcon } from '@/ui' import { VIcon } from '@/ui'
export default Vue.extend({ export default defineComponent({
components: { components: {
VIcon, VIcon,
}, },

View File

@ -1,5 +1,5 @@
<template> <template>
<div role="list" class="upload-popup"> <div ref="el" role="list" class="upload-popup">
<div role="listitem"> <div role="listitem">
<a target="_blank" href="https://member.bilibili.com/platform/upload/text/apply">专栏投稿</a> <a target="_blank" href="https://member.bilibili.com/platform/upload/text/apply">专栏投稿</a>
</div> </div>
@ -23,10 +23,12 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { popperMixin } from '../mixins' import { defineComponent } from 'vue'
import { popupProps, usePopup } from '../mixins'
export default Vue.extend({ export default defineComponent({
mixins: [popperMixin], props: popupProps,
setup: usePopup,
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -1,12 +1,13 @@
import { CustomNavbarItemInit } from '../custom-navbar-item' import { defineAsyncComponent } from 'vue'
import type { CustomNavbarItemInit } from '../custom-navbar-item'
export const upload: CustomNavbarItemInit = { export const upload: CustomNavbarItemInit = {
name: 'upload', name: 'upload',
displayName: '投稿', displayName: '投稿',
content: () => import('./NavbarUpload.vue').then(m => m.default), content: defineAsyncComponent(() => import('./NavbarUpload.vue')),
touch: true, touch: true,
href: 'https://member.bilibili.com/platform/upload/video/frame', href: 'https://member.bilibili.com/platform/upload/video/frame',
popupContent: () => import('./UploadPopup.vue').then(m => m.default), popupContent: defineAsyncComponent(() => import('./UploadPopup.vue')),
} }

View File

@ -6,13 +6,14 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'
import { getUserInfo } from '@/core/user-info' import { getUserInfo } from '@/core/user-info'
import { getDpiSourceSet } from '@/core/utils' import { getDpiSourceSet } from '@/core/utils'
import { EmptyImageUrl } from '@/core/utils/constants' import { EmptyImageUrl } from '@/core/utils/constants'
const noFaceUrl = '//static.hdslb.com/images/member/noface.gif' const noFaceUrl = '//static.hdslb.com/images/member/noface.gif'
const notLoginFaceUrl = 'https://static.hdslb.com/images/akari.jpg' const notLoginFaceUrl = 'https://static.hdslb.com/images/akari.jpg'
export default Vue.extend({ export default defineComponent({
name: 'UserFace', name: 'UserFace',
data() { data() {
return { return {

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="user-info-panel"> <div ref="el" class="user-info-panel">
<div v-if="isLogin && userInfo.isLogin === true" class="logged-in"> <div v-if="isLogin && userInfo.isLogin === true" class="logged-in">
<a class="name" target="_blank" href="https://space.bilibili.com/">{{ userInfo.uname }}</a> <a class="name" target="_blank" href="https://space.bilibili.com/">{{ userInfo.uname }}</a>
<a class="type" target="_blank" href="https://account.bilibili.com/account/big">{{ <a class="type" target="_blank" href="https://account.bilibili.com/account/big">{{
@ -83,7 +83,7 @@
:href="'https://space.bilibili.com/' + userInfo.mid + '/fans/follow'" :href="'https://space.bilibili.com/' + userInfo.mid + '/fans/follow'"
target="_blank" target="_blank"
> >
<div class="stats-number">{{ stat.following | count }}</div> <div class="stats-number">{{ count(stat.following) }}</div>
关注 关注
</a> </a>
<a <a
@ -91,7 +91,7 @@
:href="'https://space.bilibili.com/' + userInfo.mid + '/fans/fans'" :href="'https://space.bilibili.com/' + userInfo.mid + '/fans/fans'"
target="_blank" target="_blank"
> >
<div class="stats-number">{{ stat.follower | count }}</div> <div class="stats-number">{{ count(stat.follower) }}</div>
粉丝 粉丝
</a> </a>
<a <a
@ -99,7 +99,7 @@
:href="'https://space.bilibili.com/' + userInfo.mid + '/dynamic'" :href="'https://space.bilibili.com/' + userInfo.mid + '/dynamic'"
target="_blank" target="_blank"
> >
<div class="stats-number">{{ stat.dynamic_count | count }}</div> <div class="stats-number">{{ count(stat.dynamic_count) }}</div>
动态 动态
</a> </a>
</div> </div>
@ -155,26 +155,26 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { getUID, getCsrf, formData } from '@/core/utils' import { defineComponent } from 'vue'
import { formatCount } from '@/core/utils/formatters'
import { logError } from '@/core/utils/log'
import { getJsonWithCredentials, postTextWithCredentials } from '@/core/ajax' import { getJsonWithCredentials, postTextWithCredentials } from '@/core/ajax'
import { getUserInfo } from '@/core/user-info' import { getUserInfo } from '@/core/user-info'
import { popperMixin } from '../mixins' import { formData, getCsrf, getUID } from '@/core/utils'
import { formatCount } from '@/core/utils/formatters'
import { logError } from '@/core/utils/log'
import { popupProps, usePopup } from '../mixins'
type PrivilegeType = 1 | 2 type PrivilegeType = 1 | 2
export default Vue.extend({ export default defineComponent({
components: { components: {
VIcon: coreApis.ui.VIcon, VIcon: coreApis.ui.VIcon,
}, },
filters: { props: popupProps,
count: formatCount, setup: usePopup,
},
mixins: [popperMixin],
data() { data() {
return { return {
userInfo: {}, userInfo: {} as any,
stat: {}, stat: {} as any,
isLogin: Boolean(getUID()), isLogin: Boolean(getUID()),
privileges: { privileges: {
bCoin: { bCoin: {
@ -189,7 +189,7 @@ export default Vue.extend({
} }
}, },
computed: { computed: {
level() { level(): { icon: string; colored?: boolean } {
const baseLevel = `lv${this.userInfo.level_info.current_level}` const baseLevel = `lv${this.userInfo.level_info.current_level}`
if (this.userInfo.is_senior_member) { if (this.userInfo.is_senior_member) {
return { return {
@ -201,7 +201,14 @@ export default Vue.extend({
icon: baseLevel, icon: baseLevel,
} }
}, },
userType() { userType():
| '未登录'
| '注册会员'
| '正式会员'
| '小会员'
| '大会员'
| '年度小会员'
| '年度大会员' {
if (!this.userInfo.isLogin) { if (!this.userInfo.isLogin) {
return '未登录' return '未登录'
} }
@ -218,7 +225,7 @@ export default Vue.extend({
} }
return '正式会员' return '正式会员'
}, },
levelProgressStyle() { levelProgressStyle(): Record<string, string> {
if (!this.userInfo.isLogin) { if (!this.userInfo.isLogin) {
return {} return {}
} }
@ -248,6 +255,7 @@ export default Vue.extend({
} }
}, },
methods: { methods: {
count: formatCount,
async privilegeReceive(type: PrivilegeType) { async privilegeReceive(type: PrivilegeType) {
const typeMapping = { const typeMapping = {
1: 'bCoin', 1: 'bCoin',

View File

@ -1,15 +1,17 @@
import { defineAsyncComponent } from 'vue'
import { getUID } from '@/core/utils' import { getUID } from '@/core/utils'
import { CustomNavbarItemInit } from '../custom-navbar-item'
import type { CustomNavbarItemInit } from '../custom-navbar-item'
export const userInfo: CustomNavbarItemInit = { export const userInfo: CustomNavbarItemInit = {
name: 'userInfo', name: 'userInfo',
displayName: '个人信息', displayName: '个人信息',
content: () => import('./UserFace.vue').then(m => m.default), content: defineAsyncComponent(() => import('./UserFace.vue')),
href: getUID() ? 'https://space.bilibili.com' : null, href: getUID() ? 'https://space.bilibili.com' : null,
touch: true, touch: true,
popupContent: () => import('./UserInfoPopup.vue').then(m => m.default), popupContent: defineAsyncComponent(() => import('./UserInfoPopup.vue')),
lazy: false, lazy: false,
noPopupPadding: true, noPopupPadding: true,
boundingWidth: 240, boundingWidth: 240,

View File

@ -1,9 +1,9 @@
<template> <template>
<div class="watchlater-list"> <div ref="el" class="watchlater-list">
<div class="header"> <div class="header">
<div class="watchlater-list-summary"> {{ filteredCards.length }} </div> <div class="watchlater-list-summary"> {{ filteredCards.length }} </div>
<div class="search"> <div class="search">
<TextBox v-model="search" linear placeholder="搜索"></TextBox> <TextBox v-model:text="search" linear placeholder="搜索"></TextBox>
</div> </div>
<a <a
class="operation" class="operation"
@ -51,16 +51,14 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'
import type { RawWatchlaterItem } from '@/components/video/watchlater'
import { getWatchlaterList, toggleWatchlater, watchlaterList } from '@/components/video/watchlater'
import { getComponentSettings } from '@/core/settings' import { getComponentSettings } from '@/core/settings'
import { formatDuration } from '@/core/utils/formatters' import { formatDuration } from '@/core/utils/formatters'
import { import { DpiImage, TextBox, VButton, VEmpty, VIcon, VLoading } from '@/ui'
watchlaterList,
getWatchlaterList, import { popupProps, usePopup } from '../mixins'
RawWatchlaterItem,
toggleWatchlater,
} from '@/components/video/watchlater'
import { VLoading, VEmpty, TextBox, VButton, VIcon, DpiImage } from '@/ui'
import { popperMixin } from '../mixins'
interface WatchlaterCard { interface WatchlaterCard {
aid: number aid: number
@ -74,7 +72,15 @@ interface WatchlaterCard {
upFaceUrl: string upFaceUrl: string
upID: number upID: number
} }
export default Vue.extend({ function updateFilteredCards(this: InstanceType<typeof ThisComponent>) {
const search = this.search.toLowerCase()
const cardsList = this.$el.querySelector('.watchlater-list-content') as HTMLElement
cardsList.scrollTo(0, 0)
this.filteredCards = (this.cards as WatchlaterCard[]).filter(
card => card.title.toLowerCase().includes(search) || card.upName.toLowerCase().includes(search),
)
}
const ThisComponent = defineComponent({
components: { components: {
VLoading, VLoading,
VEmpty, VEmpty,
@ -83,14 +89,15 @@ export default Vue.extend({
VIcon, VIcon,
DpiImage, DpiImage,
}, },
mixins: [popperMixin], props: popupProps,
setup: usePopup,
data() { data() {
const redirect = getComponentSettings('watchlaterRedirect') const redirect = getComponentSettings('watchlaterRedirect')
return { return {
watchlaterList, watchlaterList,
loading: true, loading: true,
cards: [], cards: [] as WatchlaterCard[],
filteredCards: [], filteredCards: [] as WatchlaterCard[],
search: '', search: '',
redirect: redirect.enabled && redirect.options.navbar, redirect: redirect.enabled && redirect.options.navbar,
} }
@ -160,17 +167,10 @@ export default Vue.extend({
this.cards.splice(index, 1) this.cards.splice(index, 1)
await this.toggleWatchlater(aid) await this.toggleWatchlater(aid)
}, },
updateFilteredCards: lodash.debounce(function updateFilteredCards() { updateFilteredCards: lodash.debounce(updateFilteredCards, 100) as unknown as () => void,
const search = this.search.toLowerCase()
const cardsList = this.$el.querySelector('.watchlater-list-content') as HTMLElement
cardsList.scrollTo(0, 0)
this.filteredCards = (this.cards as WatchlaterCard[]).filter(
card =>
card.title.toLowerCase().includes(search) || card.upName.toLowerCase().includes(search),
)
}, 100),
}, },
}) })
export default ThisComponent
</script> </script>
<style lang="scss"> <style lang="scss">
@import 'common'; @import 'common';
@ -250,7 +250,7 @@ export default Vue.extend({
padding: 0 12px; padding: 0 12px;
padding-bottom: 12px; padding-bottom: 12px;
.watchlater-card { .watchlater-card {
&.cards-enter, &.cards-enter-from,
&.cards-leave-to { &.cards-leave-to {
opacity: 0; opacity: 0;
transform: translateY(-16px) scale(0.9); transform: translateY(-16px) scale(0.9);

View File

@ -1,4 +1,5 @@
import { CustomNavbarItemInit } from '../custom-navbar-item' import { defineAsyncComponent } from 'vue'
import type { CustomNavbarItemInit } from '../custom-navbar-item'
export const watchlater: CustomNavbarItemInit = { export const watchlater: CustomNavbarItemInit = {
name: 'watchlater', name: 'watchlater',
@ -12,5 +13,5 @@ export const watchlater: CustomNavbarItemInit = {
boundingWidth: 380, boundingWidth: 380,
noPopupPadding: true, noPopupPadding: true,
popupContent: () => import('./NavbarWatchlater.vue').then(m => m.default), popupContent: defineAsyncComponent(() => import('./NavbarWatchlater.vue')),
} }

View File

@ -1,5 +1,6 @@
import { defineComponentMetadata } from '@/components/define' import { defineComponentMetadata } from '@/components/define'
import { LifeCycleEventTypes } from '@/core/life-cycle' import { LifeCycleEventTypes } from '@/core/life-cycle'
import { darkExcludes } from '../dark-urls' import { darkExcludes } from '../dark-urls'
export const component = defineComponentMetadata({ export const component = defineComponentMetadata({

View File

@ -1,4 +1,5 @@
import { defineComponentMetadata } from '@/components/define' import { defineComponentMetadata } from '@/components/define'
import { darkExcludes } from './dark-urls' import { darkExcludes } from './dark-urls'
const changeDelay = 200 const changeDelay = 200

View File

@ -1,11 +1,10 @@
import { import type { OptionsOfMetadata } from '@/components/define'
defineComponentMetadata, import { defineComponentMetadata, defineOptionsMetadata } from '@/components/define'
defineOptionsMetadata,
OptionsOfMetadata,
} from '@/components/define'
import { fullyLoaded } from '@/core/life-cycle' import { fullyLoaded } from '@/core/life-cycle'
import { ComponentSettings, getComponentSettings } from '@/core/settings' import type { ComponentSettings } from '@/core/settings'
import { Range } from '@/ui/range' import { getComponentSettings } from '@/core/settings'
import type { Range } from '@/ui/range'
import { darkExcludes } from '../dark-urls' import { darkExcludes } from '../dark-urls'
class ScheduleTime { class ScheduleTime {

View File

@ -4,7 +4,9 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
export default Vue.extend({}) import { defineComponent } from 'vue'
export default defineComponent({})
</script> </script>
<style lang="scss"> <style lang="scss">
@import 'common'; @import 'common';

View File

@ -68,11 +68,12 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref, type Ref } from 'vue'
import type { SortableEvent } from 'sortablejs' import type { SortableEvent } from 'sortablejs'
import { SortableJSLibrary } from '@/core/runtime-library' import { SortableJSLibrary } from '@/core/runtime-library'
import { ascendingSort } from '@/core/utils/sort' import { ascendingSort } from '@/core/utils/sort'
import { VLoading, VIcon, VButton } from '@/ui' import { VLoading, VIcon, VButton } from '@/ui'
import { FreshLayoutItem, FreshLayoutItemSettings } from './layouts/fresh-layout-item' import type { FreshLayoutItem, FreshLayoutItemSettings } from './layouts/fresh-layout-item'
import { layouts } from './layouts/layouts' import { layouts } from './layouts/layouts'
import { freshHomeOptions } from './options' import { freshHomeOptions } from './options'
@ -81,8 +82,11 @@ interface SortItem {
layoutSettings: FreshLayoutItemSettings layoutSettings: FreshLayoutItemSettings
} }
export default Vue.extend({ export default defineComponent({
components: { VLoading, VIcon, VButton }, components: { VLoading, VIcon, VButton },
setup: () => ({
sortList: ref(null) satisfies Ref<HTMLElement | null>,
}),
data() { data() {
return { return {
loaded: false, loaded: false,
@ -91,7 +95,7 @@ export default Vue.extend({
} }
}, },
async mounted() { async mounted() {
const list: HTMLElement = this.$refs.sortList const list = this.sortList
const Sortable = await SortableJSLibrary const Sortable = await SortableJSLibrary
console.log({ list }) console.log({ list })
Sortable.create(list, { Sortable.create(list, {

View File

@ -8,11 +8,12 @@
</HomeRedesignBase> </HomeRedesignBase>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'
import HomeRedesignBase from '../HomeRedesignBase.vue' import HomeRedesignBase from '../HomeRedesignBase.vue'
import FreshLayoutItem from './FreshLayoutItem.vue' import FreshLayoutItem from './FreshLayoutItem.vue'
import { layouts } from './layouts/layouts' import { layouts } from './layouts/layouts'
export default Vue.extend({ export default defineComponent({
components: { components: {
HomeRedesignBase, HomeRedesignBase,
FreshLayoutItem, FreshLayoutItem,

View File

@ -20,18 +20,23 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { PropType } from 'vue'
import { defineComponent } from 'vue'
import { freshHomeOptions } from './options' import { freshHomeOptions } from './options'
import type { FreshLayoutItem, FreshLayoutItemSettings } from './layouts/fresh-layout-item'
export default Vue.extend({ export default defineComponent({
props: { props: {
item: { item: {
required: true, required: true,
type: Object, type: Object as PropType<FreshLayoutItem>,
}, },
}, },
data() { data() {
return { return {
options: freshHomeOptions.layoutOptions[this.item.name] ?? {}, options: (freshHomeOptions.layoutOptions[this.item.name] ?? {
linebreak: false,
}) as FreshLayoutItemSettings | { linebreak: boolean; order?: number; hidden?: boolean },
} }
}, },
}) })

View File

@ -1,15 +1,22 @@
<template> <template>
<div class="fresh-home-video-card-wrapper"> <div class="fresh-home-video-card-wrapper">
<VideoCard v-bind="$attrs" orientation="vertical" /> <VideoCard v-bind="attrs" orientation="vertical" />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'
import VideoCard from '@/components/feeds/VideoCard.vue' import VideoCard from '@/components/feeds/VideoCard.vue'
export default Vue.extend({ export default defineComponent({
components: { components: {
VideoCard, VideoCard,
}, },
inheritAttrs: false,
computed: {
attrs(): any {
return this.$attrs
},
},
}) })
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -13,13 +13,18 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { VEmpty, VLoading } from '@/ui' import type { Ref, PropType } from 'vue'
import { defineComponent, ref } from 'vue'
import type { VideoCard } from '@/components/feeds/video-card'
import { enableHorizontalScroll } from '@/core/horizontal-scroll' import { enableHorizontalScroll } from '@/core/horizontal-scroll'
import { addComponentListener } from '@/core/settings' import { addComponentListener } from '@/core/settings'
import VideoCardWrapper from './VideoCardWrapper.vue' import { VEmpty, VLoading } from '@/ui'
import { setupScrollMask, cleanUpScrollMask } from './scroll-mask'
export default Vue.extend({ import { cleanUpScrollMask, setupScrollMask } from './scroll-mask'
import VideoCardWrapper from './VideoCardWrapper.vue'
export default defineComponent({
components: { components: {
VEmpty, VEmpty,
VLoading, VLoading,
@ -27,7 +32,7 @@ export default Vue.extend({
}, },
props: { props: {
videos: { videos: {
type: Array, type: Array as PropType<VideoCard[]>,
default: () => [], default: () => [],
}, },
loading: { loading: {
@ -35,21 +40,28 @@ export default Vue.extend({
default: true, default: true,
}, },
}, },
setup: () => ({
content: ref(null) as Ref<HTMLDivElement | null>,
cards: ref(null) as Ref<InstanceType<typeof VideoCardWrapper>[] | null>,
}),
watch: { watch: {
videos() { videos: {
handler() {
this.setupIntersection() this.setupIntersection()
}, },
loaded() { deep: true,
if (this.loaded) { },
loaded(value) {
if (value) {
this.setupIntersection() this.setupIntersection()
} }
}, },
}, },
beforeDestroy() { beforeUnmount() {
cleanUpScrollMask(this.$el) cleanUpScrollMask(this.$el)
}, },
mounted() { mounted() {
const container = this.$refs.content as HTMLElement const container = this.content as HTMLElement
let cancel: () => void let cancel: () => void
addComponentListener( addComponentListener(
'freshHome.horizontalWheelScroll', 'freshHome.horizontalWheelScroll',
@ -68,11 +80,11 @@ export default Vue.extend({
await this.$nextTick() await this.$nextTick()
setupScrollMask({ setupScrollMask({
container: this.$el, container: this.$el,
items: this.$refs.cards.map((c: Vue) => c.$el), items: this.cards.map(c => c.$el),
}) })
}, },
offsetPage(offset: number) { offsetPage(offset: number) {
const container = this.$refs.content as HTMLElement const container = this.content as HTMLElement
const style = getComputedStyle(container) const style = getComputedStyle(container)
const containerWidth = container.clientWidth const containerWidth = container.clientWidth
const wrapperWidth = const wrapperWidth =

View File

@ -19,9 +19,8 @@ export const component = defineComponentMetadata({
true, true,
) )
contentLoaded(async () => { contentLoaded(async () => {
const FreshHome = await import('./FreshHome.vue') const [el] = mountVueComponent(await import('./FreshHome.vue'))
const freshHome = mountVueComponent(FreshHome) document.body.appendChild(el)
document.body.appendChild(freshHome.$el)
}) })
}, },
options: freshHomeOptionsMetadata, options: freshHomeOptionsMetadata,

View File

@ -22,11 +22,13 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'
import { addData } from '@/plugins/data' import { addData } from '@/plugins/data'
import { VButton, VIcon } from '@/ui' import { VButton, VIcon } from '@/ui'
import BlackRoomColored from './black-room.svg'
import LiveColored from './live.svg' import LiveColored from './live.svg'
import TopicColored from './topic.svg' import TopicColored from './topic.svg'
import BlackRoomColored from './black-room.svg'
addData('ui.icons', (icons: Record<string, string>) => { addData('ui.icons', (icons: Record<string, string>) => {
icons['live-colored'] = LiveColored icons['live-colored'] = LiveColored
@ -54,7 +56,7 @@ const others = [
icon: 'black-room-colored', icon: 'black-room-colored',
}, },
] ]
export default Vue.extend({ export default defineComponent({
components: { components: {
VButton, VButton,
VIcon, VIcon,

View File

@ -1,7 +1,8 @@
import { FreshLayoutItem } from '../fresh-layout-item' import { defineAsyncComponent } from 'vue'
import type { FreshLayoutItem } from '../fresh-layout-item'
export const areas: FreshLayoutItem = { export const areas: FreshLayoutItem = {
name: 'areas', name: 'areas',
displayName: '栏目', displayName: '栏目',
component: () => import('./Areas.vue').then(m => m.default), component: defineAsyncComponent(() => import('./Areas.vue')),
} }

View File

@ -52,10 +52,12 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { VButton, VIcon, DpiImage } from '@/ui' import { defineComponent } from 'vue'
import { getBlackboards } from './api' import { DpiImage, VButton, VIcon } from '@/ui'
export default Vue.extend({ import { type Blackboard, getBlackboards } from './api'
export default defineComponent({
components: { components: {
VButton, VButton,
VIcon, VIcon,
@ -63,12 +65,12 @@ export default Vue.extend({
}, },
data() { data() {
return { return {
blackboards: [], blackboards: [] as Blackboard[],
timer: 0, timer: 0,
} }
}, },
computed: { computed: {
cardsContainer() { cardsContainer(): Element | null {
return this.$el.querySelector('.fresh-home-blackboard-cards') return this.$el.querySelector('.fresh-home-blackboard-cards')
}, },
}, },
@ -79,7 +81,7 @@ export default Vue.extend({
mounted() { mounted() {
this.createTimer() this.createTimer()
}, },
beforeDestroy() { beforeUnmount() {
this.destroyTimer() this.destroyTimer()
}, },
methods: { methods: {

View File

@ -1,7 +1,8 @@
import { FreshLayoutItem } from '../fresh-layout-item' import { defineAsyncComponent } from 'vue'
import type { FreshLayoutItem } from '../fresh-layout-item'
export const blackboard: FreshLayoutItem = { export const blackboard: FreshLayoutItem = {
name: 'blackboard', name: 'blackboard',
displayName: '活动', displayName: '活动',
component: () => import('./Blackboard.vue').then(m => m.default), component: defineAsyncComponent(() => import('./Blackboard.vue')),
} }

View File

@ -4,7 +4,7 @@
<div class="fresh-home-header-title">分区</div> <div class="fresh-home-header-title">分区</div>
<div class="fresh-home-header-center-area"> <div class="fresh-home-header-center-area">
<div class="fresh-home-header-tabs"> <div class="fresh-home-header-tabs">
<div ref="tabs" class="default-tabs"> <div ref="tabsRef" class="default-tabs">
<div <div
v-for="t of tabs" v-for="t of tabs"
:key="t.name" :key="t.name"
@ -35,13 +35,15 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { ArrayContent } from '@/core/common-types' import type { Ref } from 'vue'
import { defineComponent, ref } from 'vue'
import type { TabType } from './categories'
import { Reorder } from '@/core/reorder' import { Reorder } from '@/core/reorder'
import { ascendingSort } from '@/core/utils/sort' import { ascendingSort } from '@/core/utils/sort'
import { VButton, VIcon } from '@/ui' import { VButton, VIcon } from '@/ui'
import { freshHomeOptions } from '../../options' import { freshHomeOptions } from '../../options'
import { supportedCategories } from './filter'
import { getContent } from './content/content' import { getContent } from './content/content'
import { supportedCategories } from './filter'
const tabs = Object.entries(supportedCategories).map(([name, category]) => ({ const tabs = Object.entries(supportedCategories).map(([name, category]) => ({
id: category.code as number, id: category.code as number,
@ -51,12 +53,14 @@ const tabs = Object.entries(supportedCategories).map(([name, category]) => ({
href: category.link, href: category.link,
order: 0, order: 0,
})) }))
type TabType = ArrayContent<typeof tabs> export default defineComponent({
export default Vue.extend({
components: { components: {
VButton, VButton,
VIcon, VIcon,
}, },
setup: () => ({
tabsRef: ref(null) as Ref<HTMLDivElement | null>,
}),
data() { data() {
const orderMap = (freshHomeOptions.categoriesOrder ?? {}) as Record<string, number> const orderMap = (freshHomeOptions.categoriesOrder ?? {}) as Record<string, number>
const orderedTabs = [...tabs].sort(ascendingSort(t => orderMap[t.name])) const orderedTabs = [...tabs].sort(ascendingSort(t => orderMap[t.name]))
@ -69,7 +73,7 @@ export default Vue.extend({
} }
}, },
mounted() { mounted() {
const tabsContainer = this.$refs.tabs as HTMLElement const tabsContainer = this.tabsRef as HTMLElement
const reorder = new Reorder(tabsContainer) const reorder = new Reorder(tabsContainer)
reorder.addEventListener('reorder', ({ detail: items }) => { reorder.addEventListener('reorder', ({ detail: items }) => {
const newOrder = Object.fromEntries( const newOrder = Object.fromEntries(

Some files were not shown because too many files have changed in this diff Show More