update vue3.3

web编辑 修正筛选删除导出逻辑
This commit is contained in:
Xwite 2023-05-14 19:36:52 +08:00
parent 152dc4e3b8
commit 5efc367bc6
12 changed files with 130 additions and 100 deletions

View File

@ -4,7 +4,6 @@
"ComponentPublicInstance": true, "ComponentPublicInstance": true,
"ComputedRef": true, "ComputedRef": true,
"EffectScope": true, "EffectScope": true,
"ElLoading": true,
"ElMessage": true, "ElMessage": true,
"InjectionKey": true, "InjectionKey": true,
"PropType": true, "PropType": true,

View File

@ -15,26 +15,26 @@
"dependencies": { "dependencies": {
"@element-plus/icons-svg": "^2.1.0", "@element-plus/icons-svg": "^2.1.0",
"@element-plus/icons-vue": "^2.1.0", "@element-plus/icons-vue": "^2.1.0",
"@vueuse/shared": "^10.0.2", "@vueuse/shared": "^10.1.2",
"axios": "^1.3.5", "axios": "^1.4.0",
"element-plus": "^2.3.3", "element-plus": "^2.3.4",
"hotkeys-js": "^3.10.2", "hotkeys-js": "^3.10.2",
"pinia": "^2.0.34", "pinia": "^2.0.36",
"vue": "^3.2.47", "vue": "^3.3.2",
"vue-router": "^4.1.6", "vue-router": "^4.2.0",
"vue3-virtual-scroll-list": "^0.2.1" "vue3-virtual-scroll-list": "^0.2.1"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^4.1.0", "@vitejs/plugin-vue": "^4.2.3",
"eslint": "^8.38.0", "eslint": "^8.40.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.11.0", "eslint-plugin-vue": "^9.12.0",
"prettier": "^2.8.7", "prettier": "^2.8.8",
"sass": "^1.62.0", "sass": "^1.62.1",
"unplugin-auto-import": "^0.15.3", "unplugin-auto-import": "^0.15.3",
"unplugin-icons": "^0.16.1", "unplugin-icons": "^0.16.1",
"unplugin-vue-components": "^0.24.1", "unplugin-vue-components": "^0.24.1",
"vite": "^4.2.1" "vite": "^4.3.5"
} }
} }

View File

@ -64,7 +64,6 @@ const getCover = (coverUrl) => {
const subJustify = computed(() => const subJustify = computed(() =>
props.isSearch ? "space-between" : "flex-start" props.isSearch ? "space-between" : "flex-start"
); );
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -12,7 +12,12 @@
</div> </div>
</template> </template>
<script setup> <script setup>
const props = defineProps(["index", "source", "gotoChapter", "currentChapterIndex"]); const props = defineProps([
"index",
"source",
"gotoChapter",
"currentChapterIndex",
]);
const isSelected = (idx) => { const isSelected = (idx) => {
return idx == props.currentChapterIndex; return idx == props.currentChapterIndex;

View File

@ -27,9 +27,12 @@ const store = useSourceStore();
const printDebug = ref(""); const printDebug = ref("");
const searchKey = ref(""); const searchKey = ref("");
watch(() => store.isDebuging, () => { watch(
if (store.isDebuging) startDebug(); () => store.isDebuging,
}); () => {
if (store.isDebuging) startDebug();
}
);
const appendDebugMsg = (msg) => { const appendDebugMsg = (msg) => {
let debugDom = document.querySelector("#debug-text"); let debugDom = document.querySelector("#debug-text");

View File

@ -15,11 +15,12 @@
<script setup> <script setup>
import { Edit } from "@element-plus/icons-vue"; import { Edit } from "@element-plus/icons-vue";
import { getSourceUniqueKey } from "@/utils/souce";
const props = defineProps(["source"]); const props = defineProps(["source"]);
const store = useSourceStore(); const store = useSourceStore();
const { savedSourcesMap, currentSourceUrl, sourceUrlKey } = storeToRefs(store); const { savedSourcesMap, currentSourceUrl } = storeToRefs(store);
const sourceUrl = computed(() => props.source[sourceUrlKey.value]); const sourceUrl = computed(() => getSourceUniqueKey(props.source));
const handleSourceClick = (source) => { const handleSourceClick = (source) => {
store.changeCurrentSource(source); store.changeCurrentSource(source);
}; };

View File

@ -18,7 +18,7 @@
type="danger" type="danger"
:icon="Delete" :icon="Delete"
@click="deleteSelectSources" @click="deleteSelectSources"
:disabled="sourceUrlSelect.length === 0" :disabled="sourceSelect.length === 0"
>删除</el-button >删除</el-button
> >
<el-button <el-button
@ -43,7 +43,11 @@
<script setup> <script setup>
import API from "@api"; import API from "@api";
import { Folder, Delete, Download, Search } from "@element-plus/icons-vue"; import { Folder, Delete, Download, Search } from "@element-plus/icons-vue";
import { isSourceContains } from "@utils/souce"; import {
isSourceMatches,
getSourceUniqueKey,
convertSourcesToMap,
} from "@utils/souce";
import VirtualList from "vue3-virtual-scroll-list"; import VirtualList from "vue3-virtual-scroll-list";
import SourceItem from "./SourceItem.vue"; import SourceItem from "./SourceItem.vue";
@ -52,35 +56,50 @@ const sourceUrlSelect = ref([]);
const searchKey = ref(""); const searchKey = ref("");
const { sources, sourcesMap } = storeToRefs(store); const { sources, sourcesMap } = storeToRefs(store);
//
/** @type Ref<import('@/source').Source[]> */
const sourcesFiltered = computed(() => {
const key = searchKey.value;
if (key === "") return sources.value;
return (
sources.value
// @ts-ignore
.filter((source) => isSourceMatches(source, key))
);
});
//
/** @type Ref<import('@/source').Source[]> */
const sourceSelect = computed(() => { const sourceSelect = computed(() => {
const urls = sourceUrlSelect.value; const urls = sourceUrlSelect.value;
if (urls.length == 0) return []; if (urls.length == 0) return [];
return urls.map( const sourcesFilteredMap =
(sourceUrl) => sourcesMap.value.get(sourceUrl) ?? {} searchKey.value == ""
); ? sourcesMap.value
: convertSourcesToMap(sourcesFiltered.value);
return urls.reduce((sources, sourceUrl) => {
const source = sourcesFilteredMap.get(sourceUrl);
if (source) sources.push(source);
return sources;
}, []);
}); });
const deleteSelectSources = () => { const deleteSelectSources = () => {
const sourceSelectValue = sourceSelect.value; const sourceSelectValue = sourceSelect.value;
API.deleteSource(sourceSelectValue).then(({ data }) => { API.deleteSource(sourceSelectValue).then(({ data }) => {
if (!data.isSuccess) return ElMessage.error(data.errorMsg); if (!data.isSuccess) return ElMessage.error(data.errorMsg);
store.deleteSources(sourceSelectValue); store.deleteSources(sourceSelectValue);
sourceUrlSelect.value = []; const sourceUrlSelectRawValue = toRaw(sourceUrlSelect.value);
sourceSelectValue.forEach((source) => {
const index = sourceUrlSelectRawValue.indexOf(getSourceUniqueKey(source));
if (index > -1) sourceUrlSelectRawValue.splice(index, 1);
});
sourceUrlSelect.value = sourceUrlSelectRawValue;
}); });
}; };
const clearAllSources = () => { const clearAllSources = () => {
store.clearAllSource(); store.clearAllSource();
sourceUrlSelect.value = []; sourceUrlSelect.value = [];
}; };
//
const sourcesFiltered = computed(() => {
let key = searchKey.value;
if (key === "") return sources.value;
return (
sources.value
// @ts-ignore
.filter((source) => isSourceContains(source, key))
);
});
// //
const importSourceFile = () => { const importSourceFile = () => {

View File

@ -6,10 +6,10 @@
:name="tab[0]" :name="tab[0]"
:label="tab[1]" :label="tab[1]"
> >
<source-json v-show="index == 0" /> <source-json v-if="index == 0" />
<source-debug v-show="index == 1" /> <source-debug v-if="index == 1" />
<source-list v-show="index == 2" /> <source-list v-if="index == 2" />
<source-help v-show="index == 3" /> <source-help v-if="index == 3" />
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</template> </template>

View File

@ -1,5 +1,10 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { emptyBookSource, emptyRssSource } from "@utils/souce"; import {
emptyBookSource,
emptyRssSource,
getSourceUniqueKey,
convertSourcesToMap,
} from "@utils/souce";
const isBookSource = /bookSource/i.test(location.href); const isBookSource = /bookSource/i.test(location.href);
const emptySource = isBookSource ? emptyBookSource : emptyRssSource; const emptySource = isBookSource ? emptyBookSource : emptyRssSource;
@ -22,21 +27,9 @@ export const useSourceStore = defineStore("source", {
}, },
getters: { getters: {
sources: (state) => (isBookSource ? state.bookSources : state.rssSources), sources: (state) => (isBookSource ? state.bookSources : state.rssSources),
sourceUrlKey: () => (isBookSource ? "bookSourceUrl" : "sourceUrl"), // @ts-ignore
sourcesMap: (state) => { sourcesMap: (state) => convertSourcesToMap(state.sources),
let map = new Map(); savedSourcesMap: (state) => convertSourcesToMap(state.savedSources),
state.sources.forEach((source) =>
map.set(source[state.sourceUrlKey], source)
);
return map;
},
savedSourcesMap: (state) => {
let map = new Map();
state.savedSources.forEach((source) =>
map.set(source[state.sourceUrlKey], source)
);
return map;
},
currentSourceUrl: (state) => currentSourceUrl: (state) =>
isBookSource isBookSource
? state.currentSource.bookSourceUrl ? state.currentSource.bookSourceUrl
@ -79,10 +72,10 @@ export const useSourceStore = defineStore("source", {
saveCurrentSource() { saveCurrentSource() {
let source = this.currentSource, let source = this.currentSource,
map = this.sourcesMap; map = this.sourcesMap;
map.set(source[this.sourceUrlKey], source); map.set(getSourceUniqueKey(source), Object.create(source));
this.saveSources(Array.from(map.values())); this.saveSources(Array.from(map.values()));
}, },
// 更改当前编辑的源 // 更改当前编辑的源qq
changeCurrentSource(source) { changeCurrentSource(source) {
this.currentSource = JSON.parse(JSON.stringify(source)); this.currentSource = JSON.parse(JSON.stringify(source));
}, },

View File

@ -1,38 +1,49 @@
import { Source } from '../source' import { Source } from '../source'
const isNullOrBlank = (string: string | null | undefined | number) => string == null || (string as string).length === 0 || /^\s+$/.test(string as string) const isNullOrBlank = (string: string | null | undefined | number) => string == null || (string as string).length === 0 || /^\s+$/.test(string as string)
const isBookSource = (source: Source) => "bookSourceName" in source const isBookSource = (source: Source) => "bookSourceName" in source
export const isInvaildSource: (source: Source) => boolean = (source) => { export const isInvaildSource: (source: Source) => boolean = (source) => {
if (isBookSource(source)) { if (isBookSource(source)) {
return !isNullOrBlank(source.bookSourceName) && return !isNullOrBlank(source.bookSourceName) &&
!isNullOrBlank(source.bookSourceUrl) && !isNullOrBlank(source.bookSourceUrl) &&
!isNullOrBlank(source.bookSourceType) !isNullOrBlank(source.bookSourceType)
} }
return !isNullOrBlank(source.sourceName) && return !isNullOrBlank(source.sourceName) &&
!isNullOrBlank(source.sourceName) !isNullOrBlank(source.sourceName)
} }
export const isSourceContains: (source: Source, searchKey: string) => boolean = (source, searchKey) => { export const getSourceUniqueKey = (source: Source) => isBookSource(source) ? source.bookSourceUrl : source.sourceUrl;
if (isBookSource(source)) {
return (source.bookSourceName?.includes(searchKey) || export const isSourceMatches: (source: Source, searchKey: string) => boolean = (source, searchKey) => {
source.bookSourceUrl?.includes(searchKey) || // TODO: 正则和普通字符串识别 识别 * . \ [ ] <= <! != = ?: () \d\w\s\...
source.bookSourceGroup?.includes(searchKey) || if (isBookSource(source)) {
source.bookSourceComment?.includes(searchKey)) ?? false return (source.bookSourceName?.includes(searchKey) ||
} source.bookSourceUrl?.includes(searchKey) ||
return (source.sourceName?.includes(searchKey) || source.bookSourceGroup?.includes(searchKey) ||
source.sourceUrl?.includes(searchKey) || source.bookSourceComment?.includes(searchKey)) ?? false
source.sourceGroup?.includes(searchKey) || }
source.sourceComment?.includes(searchKey)) ?? false return (source.sourceName?.includes(searchKey) ||
} source.sourceUrl?.includes(searchKey) ||
source.sourceGroup?.includes(searchKey) ||
export const emptyBookSource = { source.sourceComment?.includes(searchKey)) ?? false
ruleSearch: {}, }
ruleBookInfo: {},
ruleToc: {}, export const convertSourcesToMap = (sources: Source[]): Map<string, Source> => {
ruleContent: {}, const map = new Map();
ruleReview: {}, sources.forEach((source) =>
ruleExplore: {} map.set(getSourceUniqueKey(source), source)
} );
export const emptyRssSource = {} return map;
}
export const emptyBookSource = {
ruleSearch: {},
ruleBookInfo: {},
ruleToc: {},
ruleContent: {},
ruleReview: {},
ruleExplore: {}
}
export const emptyRssSource = {}

View File

@ -318,7 +318,7 @@ const saveReadingBookProgressToBrowser = (index, pos) => {
* VisibilityChange https://developer.mozilla.org/zh-CN/docs/Web/API/Document/visibilitychange_event * VisibilityChange https://developer.mozilla.org/zh-CN/docs/Web/API/Document/visibilitychange_event
* 监听关闭页面 切换tab 返回桌面 等操作 * 监听关闭页面 切换tab 返回桌面 等操作
* 注意不用监听点击链接导航变化 不对Safari<14.5兼容处理 * 注意不用监听点击链接导航变化 不对Safari<14.5兼容处理
**/ **/
const onVisibilityChange = () => { const onVisibilityChange = () => {
if (document.visibilityState == "hidden") { if (document.visibilityState == "hidden") {
API.saveBookProgressWithBeacon(bookProgress.value); API.saveBookProgressWithBeacon(bookProgress.value);

View File

@ -10,13 +10,13 @@ import bookSourceConfig from "@/utils/bookSourceEditConfig.js";
import rssSourceConfig from "@/utils/rssSourceEditConfig.js"; import rssSourceConfig from "@/utils/rssSourceEditConfig.js";
import "@/assets/main.css"; import "@/assets/main.css";
const config = ref({}); let config;
if (/bookSource/i.test(location.href)) { if (/bookSource/i.test(location.href)) {
config.value = bookSourceConfig; config = bookSourceConfig;
document.title = "书源管理"; document.title = "书源管理";
} else { } else {
config.value = rssSourceConfig; config = rssSourceConfig;
document.title = "订阅源管理"; document.title = "订阅源管理";
} }
</script> </script>