feat(播放器): 添加视频音频播放器及相关功能

1.新增视频音频播放器页面及样式
2.添加视频音频缓存管理
3.在视频详情页添加播放入口
4.更新临时文件清理功能支持音频缓存
5.优化临时文件清理功能
This commit is contained in:
SarmonFish
2026-03-07 21:38:42 +08:00
parent 8ca8aa7c7b
commit 998dcf62e4
22 changed files with 2412 additions and 26 deletions
+63 -1
View File
@@ -55,4 +55,66 @@ export const BilibiliClientVideoMethods = {
global.logger.log(response);
return response.data.data;
},
};
async getVideoBestAudioUrlByBVID(this: any, bvid: string): Promise<string> {
const info = await this.getVideoInfoByBVID(bvid);
const cid = info.cid || info.pages?.[0]?.cid;
if (!cid) {
throw new Error("cid not found");
}
const url = `https://api.bilibili.com/x/player/wbi/playurl`;
const response = await this.getRequestWbi(url, {
bvid,
cid,
fnval: 4048,
fourk: 1,
platform: "pc"
});
const payload = response && response.data ? response.data : response;
if (!payload) {
global.logger.error("[getVideoBestAudioUrlByBVID] empty response");
throw new Error("empty response");
}
global.logger.log("[getVideoBestAudioUrlByBVID] response code:", payload.code, "message:", payload.message);
const dash = payload.data?.dash || payload.dash;
const audioTracks = dash?.audio || [];
global.logger.log("[getVideoBestAudioUrlByBVID] audioTracks length:", audioTracks.length);
if (!audioTracks.length) {
const keys = Object.keys(payload.data || payload || {});
global.logger.error("[getVideoBestAudioUrlByBVID] no audio tracks, payload keys:", keys);
throw new Error("audio stream not found");
}
audioTracks.sort((a: any, b: any) => (b.bandwidth || 0) - (a.bandwidth || 0));
const best = audioTracks[0] || {};
const candidates: Array<string> = [];
const baseUrl = best.baseUrl || best.base_url;
if (baseUrl) candidates.push(baseUrl);
const backupUrls = best.backupUrl || best.backup_url;
if (Array.isArray(backupUrls)) {
backupUrls.forEach((u: any) => {
if (typeof u === "string" && u.length > 0) candidates.push(u);
});
}
const uniqueCandidates = candidates.filter((u, idx) => candidates.indexOf(u) === idx);
uniqueCandidates.sort((a, b) => {
const score = (u: string) => {
let s = 0;
if (u.includes("mcdn")) s -= 10;
if (u.includes("upos")) s += 2;
return s;
};
return score(b) - score(a);
});
const selected = uniqueCandidates[0];
global.logger.log("[getVideoBestAudioUrlByBVID] selected url:", selected);
return selected;
},
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 801 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 938 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 633 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

+8 -2
View File
@@ -15,6 +15,9 @@
{
"name": "system.request"
},
{
"name": "system.audio"
},
{
"name": "system.file"
},
@@ -48,7 +51,7 @@
],
"config": {
"logLevel": "log",
"designWidth": 466,
"designWidth": "device-width",
"background": {
"features": [
"system.request",
@@ -74,6 +77,9 @@
"pages/video/videodetail": {
"component": "videodetail"
},
"pages/video/player": {
"component": "player"
},
"pages/app/arealist": {
"component": "arealist"
},
@@ -181,4 +187,4 @@
}
}
}
}
}
@@ -0,0 +1,44 @@
@import '@less/defs.less';
@import '@less/color.less';
.video-audio-item {
width: 90%;
height: 120px;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 15px;
margin-top: 15px;
flex-direction: row;
align-items: center;
padding: 15px;
}
.video-audio-cover {
width: 90px;
height: 90px;
border-radius: 10px;
background-color: #333;
}
.video-audio-info {
flex: 1;
margin-left: 15px;
flex-direction: column;
justify-content: center;
}
.video-audio-title {
font-size: 28px;
color: #ffffff;
lines: 1;
text-overflow: ellipsis;
}
.video-audio-author {
font-size: 22px;
color: #999999;
margin-top: 8px;
}
.bottom-spacer {
margin-top: 25px;
}
@@ -12,8 +12,17 @@
onclick="OpenArticle($item.id)" static>
<articleshow article="{{$item}}"></articleshow>
</div>
<div if="{{$item.type == 'videoAudio' ? true : false}}"
class="video-audio-item"
onclick="PlayVideoAudio($item)" static>
<image class="video-audio-cover" src="{{$item.coverUrl}}"></image>
<div class="video-audio-info">
<text class="video-audio-title">{{$item.title}}</text>
<text class="video-audio-author">{{$item.author}}</text>
</div>
</div>
</div>
<text style="margin-top: 25px" static></text>
<text class="bottom-spacer" static></text>
</scroll>
</div>
</template>
@@ -49,6 +58,18 @@ export default {
}
})
},
PlayVideoAudio(item) {
router.push({
uri: "pages/video/player",
params: {
bvid: item.bvid,
cachedAudioUri: item.fileUri,
cachedTitle: item.title,
cachedCoverUrl: item.coverUrl,
cachedAuthor: item.author
}
})
},
async LoadContent() {
const origin_content = await global.savedcontent.SavedContentManager.listAllContent()
const patched = this.PatchContent(origin_content)
@@ -62,4 +83,5 @@ export default {
<style lang="less">
@import "@less/global.less";
@import "./savedcontent.less";
</style>
@@ -36,12 +36,14 @@ export default {
clearControl: {
imgs: false,
logs: false,
sc: false
sc: false,
videoAudio: false
},
typeColors: {
imgs: "#434343",
logs: "#434343",
sc: "#434343",
videoAudio: "#434343",
others: "#222222"
},
scrclass: ""
@@ -51,12 +53,14 @@ export default {
this.clearControl = {
imgs: false,
logs: false,
sc: false
sc: false,
videoAudio: false
}
this.typeColors = {
imgs: "#434343",
logs: "#434343",
sc: "#434343",
videoAudio: "#434343",
others: "#222222"
}
},
@@ -88,6 +92,14 @@ export default {
})
}
if (this.clearControl.videoAudio) {
const contents = await global.savedcontent.SavedContentManager.listAllContent()
const videoAudioContents = contents.filter((ct) => ct.type === 'videoAudio')
videoAudioContents.forEach((ct) => {
tasks.push(global.savedcontent.SavedContentManager.deleteContent(ct.id))
})
}
await Promise.all(tasks)
prompt.showToast({
@@ -115,11 +127,11 @@ export default {
success: (data) => {
this.clearControl.sc = true
this.typeColors.sc = this.clearControl.sc ? "#FF7DA8" : "#434343"
// 由于这是异步函数,因此需要重新做cantClear检测
this.cantClear = !(
this.clearControl.imgs ||
this.clearControl.logs ||
this.clearControl.sc
this.clearControl.sc ||
this.clearControl.videoAudio
)
}
})
@@ -128,9 +140,30 @@ export default {
this.typeColors.sc = this.clearControl.sc ? "#FF7DA8" : "#434343"
}
break
case "videoAudio":
if (!this.clearControl.videoAudio) {
prompt.showDialog({
message:
"清除视频音频将删除所有已缓存的视频音频文件,且该操作不可逆。确定要继续吗?",
success: (data) => {
this.clearControl.videoAudio = true
this.typeColors.videoAudio = this.clearControl.videoAudio ? "#FF7DA8" : "#434343"
this.cantClear = !(
this.clearControl.imgs ||
this.clearControl.logs ||
this.clearControl.sc ||
this.clearControl.videoAudio
)
}
})
} else {
this.clearControl.videoAudio = false
this.typeColors.videoAudio = this.clearControl.videoAudio ? "#FF7DA8" : "#434343"
}
break
}
this.cantClear = !(this.clearControl.imgs || this.clearControl.logs || this.clearControl.sc)
this.cantClear = !(this.clearControl.imgs || this.clearControl.logs || this.clearControl.sc || this.clearControl.videoAudio)
global.logger.log("[ClearTmp.SelectHandler] cantClear: ", this.cantClear)
},
BytesToMB(bytes) {
@@ -141,17 +174,27 @@ export default {
const contents = await global.savedcontent.SavedContentManager.listAllContent()
const ret = await Promise.all(
contents.map(async (content) => {
const data = await asyncFile.get({uri: content.fileUri})
return {
uri: content.fileUri,
length: data.length,
_labelIsSavedContent: true
try {
const fileExists = await asyncFile.access({uri: content.fileUri})
if (!fileExists) {
return null
}
const data = await asyncFile.get({uri: content.fileUri})
return {
uri: content.fileUri,
length: data.length,
_labelIsSavedContent: true
}
} catch (e) {
global.logger.log("缓存文件不存在,跳过:" + content.fileUri)
return null
}
})
)
return ret
return ret.filter(item => item !== null)
} catch (e) {
global.logger.error("在扫描临时文件(统计缓存内容)时出错:", e.toString())
return []
}
},
async ScanTemps() {
@@ -161,6 +204,7 @@ export default {
images: {count: 0, size: 0, list: []},
logs: {count: 0, size: 0, list: []},
savedContent: {count: 0, size: 0},
videoAudio: {count: 0, size: 0, list: []},
others: {count: 0, size: 0}
}
@@ -184,6 +228,10 @@ export default {
totalStats.logs.count++
totalStats.logs.size += length
totalStats.logs.list.push(uri)
} else if (/\.m4s$/i.test(uri)) {
totalStats.videoAudio.count++
totalStats.videoAudio.size += length
totalStats.videoAudio.list.push(uri)
} else if (_labelIsSavedContent) {
totalStats.savedContent.count++
totalStats.savedContent.size += length
@@ -211,6 +259,12 @@ export default {
alias: "sc",
size: `${this.BytesToMB(totalStats.savedContent.size)} MB`
},
{
name: "视频音频",
alias: "videoAudio",
size: `${this.BytesToMB(totalStats.videoAudio.size)} MB`,
list: totalStats.videoAudio.list
},
{
name: "其它",
alias: "others",
+601
View File
@@ -0,0 +1,601 @@
@import '@less/defs.less';
@import '@less/color.less';
.player-container {
width: 100%;
height: 100%;
}
.demo-page {
width: 100%;
height: 100%;
flex-direction: column;
justify-content: space-around;
align-items: center;
background-color: #000;
padding: 0;
position: relative;
}
.background-image {
width: 432px;
height: 514px;
position: absolute;
top: 0;
left: 0;
object-fit: cover;
opacity: 0.5;
background-size: cover;
}
.song {
width: 320px;
flex-direction: column;
justify-content: center;
align-items: center;
}
.song-name {
width: 320px;
font-size: 36px;
color: #ffffff;
lines: 1;
text-overflow: ellipsis;
text-align: center;
}
.singer-name {
width: 300px;
font-size: 24px;
color: rgba(255, 255, 255, 0.7);
lines: 1;
text-overflow: ellipsis;
text-align: center;
}
.controls {
width: 360px;
height: 160px;
margin-top: 30px;
justify-content: space-between;
align-items: center;
}
.play-button {
width: 200px;
height: 200px;
justify-content: center;
align-items: center;
}
.progress-container {
position: absolute;
left: 0;
top: 0;
width: 200px;
height: 200px;
justify-content: center;
align-items: center;
}
.play-progress {
radius: 74px;
stroke-width: 14px;
start-angle: 4deg;
total-angle: 360deg;
color: #ffffff;
layer-color: rgba(255, 255, 255, 0.1);
}
.icon {
width: 48px;
height: 48px;
}
.control-icon {
width: 64px;
height: 64px;
}
.footer {
width: 360px;
height: 100px;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 0 20px;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 30px;
margin-bottom: 20px;
}
.new-player-page {
width: 100%;
height: 100%;
flex-direction: column;
justify-content: space-between;
align-items: center;
background-color: #000;
padding: 0;
position: relative;
}
.new-player-center {
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 40px;
}
.new-player-cover {
width: 230px;
height: 230px;
border-radius: 20px;
margin-bottom: 10px;
}
.new-player-song-name {
width: 380px;
font-size: 32px;
color: #ffffff;
font-weight: bold;
text-align: center;
lines: 2;
text-overflow: ellipsis;
}
.new-player-artist-name {
width: 380px;
font-size: 28px;
color: rgba(255, 255, 255, 0.7);
text-align: center;
lines: 1;
text-overflow: ellipsis;
margin-top: 8px;
}
.new-player-controls {
width: 360px;
height: 160px;
margin-bottom: 20px;
justify-content: space-between;
align-items: center;
}
.new-player-progress-container {
position: absolute;
left: 0;
top: 0;
width: 120px;
height: 120px;
justify-content: center;
align-items: center;
}
.new-player-play-progress {
radius: 44px;
stroke-width: 8px;
start-angle: 4deg;
total-angle: 360deg;
color: #ffffff;
layer-color: rgba(255, 255, 255, 0.1);
}
.new-player-sidebar {
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
width: 60px;
height: 400px;
flex-direction: column;
justify-content: space-between;
align-items: center;
}
.features-page {
width: 100%;
height: 100%;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #000;
padding: 0;
position: relative;
}
.features-sidebar {
position: absolute;
left: 20px;
top: 50%;
transform: translateY(-50%);
width: 60px;
height: 400px;
flex-direction: column;
justify-content: space-between;
align-items: center;
}
.feature-icon {
width: 48px;
height: 48px;
}
.swiper-container {
width: 100%;
height: 100%;
}
.swiper-page {
width: 100%;
height: 100%;
}
.lyric-page {
width: 100%;
height: 100%;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #000;
}
.lyric-scroll-container {
width: 100%;
height: 100%;
flex-direction: column;
justify-content: center;
align-items: center;
}
.adjust-progress-overlay {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
justify-content: center;
align-items: center;
position: absolute;
top: 0;
left: 0;
}
.adjust-progress-panel {
width: 85%;
background-color: rgba(26, 26, 26, 0.9);
border-radius: 20px;
padding: 30px 20px;
flex-direction: column;
align-items: center;
}
.adjust-progress-title {
font-size: 36px;
font-weight: 600;
color: #ffffff;
margin-bottom: 20px;
}
.adjust-progress-time {
font-size: 28px;
color: #cccccc;
margin-bottom: 25px;
}
.adjust-progress-slider {
width: 100%;
height: 60px;
margin-bottom: 25px;
block-color: #FF7DA8;
selected-color: #FF7DA8;
}
.adjust-progress-buttons {
width: 100%;
flex-direction: row;
justify-content: space-around;
margin-top: 10px;
}
.adjust-btn {
width: 140px;
height: 60px;
border-radius: 30px;
background-color: #333333;
justify-content: center;
align-items: center;
}
.adjust-btn-primary {
background-color: #FF7DA8;
}
.adjust-btn-text {
font-size: 28px;
font-weight: 600;
color: #ffffff;
}
.adjust-volume-overlay {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
justify-content: flex-end;
align-items: center;
position: absolute;
top: 0;
left: 0;
}
.adjust-volume-panel {
width: 100%;
background-color: rgba(26, 26, 26, 0.9);
border-radius: 20px 20px 0 0;
padding: 30px 20px 50px 20px;
flex-direction: column;
align-items: center;
}
.adjust-volume-title {
font-size: 32px;
font-weight: 600;
color: #ffffff;
margin-bottom: 15px;
}
.adjust-volume-value {
font-size: 48px;
font-weight: 700;
color: #FF7DA8;
margin-bottom: 20px;
}
.adjust-volume-slider {
width: 90%;
height: 60px;
block-color: #FF7DA8;
selected-color: #FF7DA8;
}
.cache-tips-page {
width: 100%;
height: 100%;
background-color: #000;
position: absolute;
top: 0;
left: 0;
}
.cache-tips-scroll {
width: 100%;
height: 100%;
flex-direction: column;
align-items: center;
}
.cache-tips-content {
width: 90%;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 80px 20px;
}
.cache-tips-title {
font-size: 48px;
font-weight: 600;
color: #ffffff;
margin-bottom: 30px;
text-align: center;
}
.cache-tips-description {
font-size: 28px;
color: #cccccc;
margin-bottom: 15px;
text-align: center;
}
.cache-tips-checkbox {
flex-direction: row;
justify-content: center;
align-items: center;
margin-top: 20px;
}
.cache-remember-checkbox {
width: 42px;
height: 42px;
background-color: #666666;
}
.cache-remember-text {
font-size: 24px;
color: #999999;
margin-left: 10px;
}
.cache-tips-buttons {
width: 100%;
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 40px;
}
.cache-btn {
width: 322px;
height: 80px;
border-radius: 90px;
justify-content: center;
align-items: center;
margin-top: 20px;
}
.cache-btn-primary {
background-color: #FF7DA8;
}
.cache-btn-secondary {
background-color: #262626;
}
.cache-btn-text {
font-size: 40px;
font-weight: 600;
color: #ffffff;
}
.applemusic-lyric-container {
width: 100%;
height: 100%;
flex-direction: column;
justify-content: center;
align-items: center;
}
.applemusic-lyric-scroll {
flex-direction: column;
flex: 1;
padding-top: 0;
width: 100%;
}
.applemusic-lyric-list {
flex-direction: column;
padding: 150px 40px;
}
.applemusic-lyric-normal {
width: 100%;
font-size: 32px;
color: #ffffff;
text-align: left;
padding: 16px 0;
height: auto;
min-height: 50px;
lines: 3;
text-overflow: ellipsis;
transition: all 0.3s ease;
font-weight: bold;
}
.applemusic-lyric-active {
width: 100%;
font-size: 38px;
color: #ffffff;
text-align: left;
padding: 16px 0;
height: auto;
min-height: 60px;
lines: 3;
text-overflow: ellipsis;
font-weight: bold;
transition: all 0.3s ease;
}
@media (min-width: 200px) and (max-width: 300px) {
.background-image {
width: 466px;
height: 466px;
}
.song {
width: 300px;
}
.song-name {
width: 300px;
font-size: 32px;
}
.singer-name {
width: 280px;
font-size: 22px;
}
.controls {
width: 340px;
height: 150px;
margin-top: 20px;
}
.play-button {
width: 180px;
height: 180px;
}
.progress-container {
width: 180px;
height: 180px;
}
.play-progress {
radius: 66px;
stroke-width: 12px;
}
.new-player-progress-container {
width: 120px;
height: 120px;
}
.new-player-play-progress {
radius: 44px;
stroke-width: 8px;
}
.footer {
width: 340px;
height: 90px;
margin-bottom: 15px;
}
.icon {
width: 44px;
height: 44px;
}
.control-icon {
width: 58px;
height: 58px;
}
.applemusic-lyric-list {
padding: 120px 40px;
}
.applemusic-lyric-normal {
font-size: 30px;
padding: 14px 0;
min-height: 48px;
}
.applemusic-lyric-active {
font-size: 36px;
padding: 14px 0;
min-height: 55px;
}
}
@media (min-width: 210px) and (max-width: 230px) {
.background-image {
width: 432px;
height: 514px;
}
.applemusic-lyric-list {
padding: 150px 40px;
}
.applemusic-lyric-normal {
font-size: 32px;
padding: 16px 0;
min-height: 50px;
}
.applemusic-lyric-active {
font-size: 38px;
padding: 16px 0;
min-height: 60px;
}
}
File diff suppressed because it is too large Load Diff
+32 -1
View File
@@ -22,6 +22,10 @@
</div>
</div>
<scroll scroll-x="true" class="vidtoolbar" style="margin-top: 25px" bounces="true">
<div class="vidtool" style="margin-left: 0px" @click="OpenPlayer()">
<image class="vidtoolimg" src="/common/vidtool_generatemanifest.png"></image>
<text class="vidtooltext">听视频</text>
</div>
<div class="vidtool" style="margin-left: 0px" @click="LikeClick()" @longpress="PowerIt()">
<image class="vidtoolimg" src="{{likesrc}}"></image>
<text class="vidtooltext">{{ stat_like }}</text>
@@ -99,6 +103,33 @@ export default {
message: "该功能仍在开发中"
})
},
async OpenPlayer() {
const exists = await global.savedcontent.SavedContentManager.checkVideoAudioExists(this.bvid)
if (exists) {
const contents = await global.savedcontent.SavedContentManager.listAllContent()
const cachedItem = contents.find((ct) => ct.bvid === this.bvid && ct.type === 'videoAudio')
if (cachedItem) {
prompt.showToast({ message: '正在加载缓存...', duration: 1000 })
router.push({
uri: "pages/video/player",
params: {
bvid: this.bvid,
cachedAudioUri: cachedItem.fileUri,
cachedTitle: cachedItem.title,
cachedCoverUrl: cachedItem.coverUrl,
cachedAuthor: cachedItem.author
}
})
return
}
}
router.push({
uri: "pages/video/player",
params: {
bvid: this.bvid
}
})
},
OpenOwnerHome() {
router.push({
uri: "pages/user",
@@ -217,4 +248,4 @@ export default {
<style lang="less">
@import "@less/global.less";
@import "./videodetail.less";
</style>
</style>
+42 -9
View File
@@ -1,19 +1,19 @@
import { asyncFile } from "./asyncapi/file";
// 定义文件存储的基本结构
interface StoredContent {
id: string;
title: string;
type: string;
fileUri: string;
coverUrl?: string;
author?: string;
bvid?: string;
}
// 模拟存储的内容列表
let storageIndex: StoredContent[] = [];
// 定义文件存储的基础路径
const baseUri = 'internal://files/bilisavedcontent/';
const indexFileUri = `${baseUri}index.json`; // 存储 storageIndex 的文件
const indexFileUri = `${baseUri}index.json`;
// 同步 storageIndex 到本地文件
async function saveStorageIndex(): Promise<void> {
@@ -66,10 +66,8 @@ export class SavedContentManager {
const id = generateUUID();
const fileUri = `${baseUri}${id}.txt`;
// 写入文件
await asyncFile.writeText({ uri: fileUri, text: data });
// 更新 storageIndex 并保存
storageIndex.push({ id, title, type, fileUri });
await saveStorageIndex();
@@ -79,6 +77,35 @@ export class SavedContentManager {
}
}
// 存储视频音频(直接引用已存在的文件)
static async storeVideoAudio(title: string, audioUri: string, coverUrl: string, author: string, bvid: string): Promise<string | void> {
try {
const id = generateUUID();
storageIndex.push({
id,
title,
type: 'videoAudio',
fileUri: audioUri,
coverUrl,
author,
bvid
});
await saveStorageIndex();
global.logger.log(`[SavedContentManager] 视频音频已保存: ${title}, id: ${id}`);
return id;
} catch (e) {
global.logger.error(`[SavedContentManager] storeVideoAudio Error: ${e.toString()}`);
}
}
// 检查视频音频是否已存在
static async checkVideoAudioExists(bvid: string): Promise<boolean> {
const exists = storageIndex.some(item => item.bvid === bvid && item.type === 'videoAudio');
return exists;
}
// 根据 id 或 title 读取内容
static async getContent(identifier: string): Promise<string | null> {
try {
@@ -87,7 +114,10 @@ export class SavedContentManager {
// 读取文件内容
const fileExists = await asyncFile.access({ uri: content.fileUri });
if (!fileExists) throw new Error(`File does not exist: ${content.fileUri}`);
if (!fileExists) {
global.logger.log(`[SavedContentManager] 文件不存在:${content.fileUri}`);
return null;
}
return await asyncFile.readText({ uri: content.fileUri });
} catch (e) {
@@ -107,9 +137,12 @@ export class SavedContentManager {
const contentIndex = storageIndex.findIndex(item => item.id === identifier || item.title === identifier);
if (contentIndex === -1) throw new Error('Content not found');
// 删除文件
// 删除文件(如果文件存在)
const fileUri = storageIndex[contentIndex].fileUri;
await asyncFile.delete({ uri: fileUri });
const fileExists = await asyncFile.access({ uri: fileUri });
if (fileExists) {
await asyncFile.delete({ uri: fileUri });
}
// 从 storageIndex 中删除记录并保存
storageIndex.splice(contentIndex, 1);