diff --git a/commons/colorLibrary/BuildProfile.ets b/commons/colorLibrary/BuildProfile.ets index 4396d0c7..3a501e5d 100644 --- a/commons/colorLibrary/BuildProfile.ets +++ b/commons/colorLibrary/BuildProfile.ets @@ -4,7 +4,7 @@ export const HAR_VERSION = '1.0.0'; export const BUILD_MODE_NAME = 'debug'; export const DEBUG = true; -export const TARGET_NAME = 'Legado'; +export const TARGET_NAME = 'default'; /** * BuildProfile Class is used only for compatibility purposes. diff --git a/entry/src/main/ets/common/constants/AppPattern.ets b/entry/src/main/ets/common/constants/AppPattern.ets new file mode 100644 index 00000000..1687b023 --- /dev/null +++ b/entry/src/main/ets/common/constants/AppPattern.ets @@ -0,0 +1,13 @@ +/** + * @author 2008 + * @datetime 2024/8/15 13:36 + * @className: AppPattern + */ +export default class AppPattern { + static readonly paramPattern: RegExp = /(?/; + static readonly jsPattern: RegExp = /(.*?)<\/js>|@js:(.*)/i; + static readonly EXP_PATTERN: RegExp = /\{\{([^\}]*?)\}\}/g; + static readonly imgPattern: RegExp = /]*src="([^"]*(?:"[^>]+\\})?)"[^>]*>/g; +} \ No newline at end of file diff --git a/entry/src/main/ets/common/model/SearchConfig.ets b/entry/src/main/ets/common/model/SearchConfig.ets new file mode 100644 index 00000000..28210513 --- /dev/null +++ b/entry/src/main/ets/common/model/SearchConfig.ets @@ -0,0 +1,59 @@ +/** + * @author 2008 + * @datetime 2024/8/15 0:06 + * @className: SearchConfig + */ +export class SearchConfig { + path: string; + method: string; + body: string; + charset: string; + url: string; + webView:boolean; + + constructor(path: string, method: string, body: string, charset: string, url: string, webView:boolean) { + this.path = path; + this.method = method.toUpperCase(); // 确保方法为大写 + this.body = body; + this.charset = charset??'UTF-8'; + this.url = url; + this.webView = webView + } + + // 替换 body 中的关键字 + setKeyword(keyword: string): void { + this.body = this.body.replace('{{key}}', encodeURIComponent(keyword)); + } +} + +interface SearchConfigData { + method: string; + body?: string; + charset?: string; + url: string; + webView?:boolean; +} + +export function parseSearchString(searchString: string): SearchConfig { + // 拆分字符串,获取路径与配置 + const splitIndex = searchString.indexOf(','); + const path = searchString.substring(0, splitIndex).trim(); + const configString = searchString.substring(splitIndex + 1).trim(); + + // 对 JSON 配置进行清理和格式化 + const cleanConfigString = configString + .replace(/(\w+)\s*:/g, '"$1":') // 给键加上引号 + .replace(/'/g, '"'); // 将单引号替换为双引号 + + // 解析 JSON 配置 + const config: SearchConfigData = JSON.parse(cleanConfigString); + + return new SearchConfig( + path, + config.method??'GET', + config.body??'', + config.charset??'UTF-8', + config.url??'', + config.webView??false + ); +} diff --git a/entry/src/main/ets/common/model/XmlAnalysis.ets b/entry/src/main/ets/common/model/XmlAnalysis.ets new file mode 100644 index 00000000..fd7a15ee --- /dev/null +++ b/entry/src/main/ets/common/model/XmlAnalysis.ets @@ -0,0 +1,80 @@ +import { SearchRule } from '../../database/entities/rule'; +import { SearchBook } from '../../database/entities/SearchBook'; +import { BookSource } from '../../database/entities/BookSource'; +import { ExploreQuery } from '../../database/types/BookSourceType'; +import axios, { AxiosError, AxiosResponse } from '@ohos/axios'; +import { isNetworkUrl } from '../utils/utils'; + + +export interface IXmlAnalysis { + baseUrl: string //搜索地址 + body:string //请求内容 + bookSource:BookSource//书源 + variable?:string + searchUrl:string +} +interface dataRule{ + body:string + rule:SearchRule + searchUrl:string +} +export class XmlAnalysisData { + baseUrl: string = '' //搜索地址 + body:string = ''//请求内容 + bookSource?:BookSource//书源 + variable?:string + searchUrl:string = '' + constructor(xmlDate?: IXmlAnalysis) { + this.baseUrl = xmlDate?.baseUrl || '' + this.body = xmlDate?.body || '' + this.bookSource = xmlDate?.bookSource + this.variable = xmlDate?.variable + this.searchUrl = xmlDate?.searchUrl || '' + } +} +export class XmlAnalysis { + xmlDate:XmlAnalysisData = new XmlAnalysisData() + url:string = '' + source?:SearchRule + searchUrl:string = '' + init(xmlDate:XmlAnalysisData){ + this.xmlDate = xmlDate + this.url = xmlDate.searchUrl + this.source = this.xmlDate.bookSource?.ruleSearch + this.searchUrl = this.xmlDate.searchUrl + } + + async getBookList(): Promise { + let bookList:SearchBook[] = [] + if (!this.source) return bookList + const data: dataRule = { + body: this.xmlDate.body, + rule: this.source, + searchUrl: this.searchUrl + } + let response: AxiosResponse = await axios.post('http://legado.wisdoms.xin/analysisBook', data) + if (response.data){ + bookList = (response.data as SearchBook[]).map(item => { + item.bookType = this.xmlDate.bookSource?.bookSourceType ?? 0 + item.source = this.xmlDate.bookSource + if (item.coverUrl && !isNetworkUrl(item.coverUrl)) { + item.coverUrl = this.xmlDate.bookSource?.bookSourceUrl + item.coverUrl + } + return item + }) + } + + if (bookList.length > 0){ + console.log('test') + } + return bookList + } + + //解析xml + async analysisXml(): Promise { + return await this.getBookList(); + } + +} +// const xmlAnalysis = new XmlAnalysis(); +// export default xmlAnalysis as XmlAnalysis; \ No newline at end of file diff --git a/entry/src/main/ets/common/model/analyzeRule/RuleAnalyzer.ets b/entry/src/main/ets/common/model/analyzeRule/RuleAnalyzer.ets new file mode 100644 index 00000000..1aee101d --- /dev/null +++ b/entry/src/main/ets/common/model/analyzeRule/RuleAnalyzer.ets @@ -0,0 +1,35 @@ +/** + * @author 2008 + * @datetime 2024/8/18 2:03 + * @className: AnalyzerRule + */ +import { BookSource } from '../../../database/entities/BookSource'; + +export interface IRuleAnalyzer { + body:string //请求内容 + bookSource:BookSource//书源 +} + +export class RuleAnalyzerData { + body:string = '' //请求内容 + bookSource?:BookSource//书源 +} + +export class RuleAnalyzer { + ruleAnalyzerData:RuleAnalyzerData = new RuleAnalyzerData() + init(ruleAnalyzerData:RuleAnalyzerData){ + this.ruleAnalyzerData = ruleAnalyzerData + } + + getElements(rule:string):ESObject{ + let result: ESObject = null + return [] + } + + /** + * 分解规则生成规则列表 + */ + splitSourceRule(ruleStr?:string,allInOne:boolean = false){ + + } +} diff --git a/entry/src/main/ets/common/model/taskSearchBook.ets b/entry/src/main/ets/common/model/taskSearchBook.ets new file mode 100644 index 00000000..a4c6bc1a --- /dev/null +++ b/entry/src/main/ets/common/model/taskSearchBook.ets @@ -0,0 +1,122 @@ +/** + * @author 2008 + * @datetime 2024/8/16 20:16 + * @className: taskSearchBook + */ +import taskPool from '@ohos.taskpool'; +import { BookSource } from '../../database/entities/BookSource'; +import bookSourceDao from '../../database/dao/BookSourceDao'; +import { showMessage } from '../../componets/common/promptShow'; +import searchUtil from '../utils/searchUtils'; +import { SearchBook } from '../../database/entities/SearchBook'; + + +class taskSearchBook{ + private bookSourceParts:BookSource[] = [] + MAX_THREADS:number = 5; + tasks:taskPool.Task[] = []; + taskSearchBook:SearchBook[] = [] + + async search(searchKey: string, returnBook: (newUrls: SearchBook[]) => void, cancel: () => void): Promise { + let dataStart = Date.now(); + this.tasks = []; + this.taskSearchBook = [] + this.bookSourceParts = [] + if (!searchKey) { + return; + } + + this.bookSourceParts = await bookSourceDao.getEnabledPartByGroup(); + if (!this.bookSourceParts || this.bookSourceParts.length === 0) { + showMessage('启用书源为空'); + cancel() + return; + } + + const numBookSources = this.bookSourceParts.length; + const numThreads = Math.min(Math.ceil(numBookSources / 20), this.MAX_THREADS); + // 创建一个回调函数 + const callback = (newUrls: SearchBook[]) => { + this.taskSearchBook.push(...newUrls) + returnBook(newUrls) + }; + + for (let i = 0; i < numThreads; i++) { + const sourcesSlice = this.bookSourceParts.slice( + (i * numBookSources) / numThreads, + ((i + 1) * numBookSources) / numThreads + ); + + const task = new taskPool.Task( + `搜索${i}`, + searchTask, + searchKey, sourcesSlice + ); + task.onReceiveData(callback) + this.tasks.push(task); + } + + try { + if (this.tasks.length > 0) { + const results = await Promise.all(this.tasks.map(async (task, index) => { + console.log(`任务执行中... ${task.name} ${task.isDone()}`); + try { + return await taskPool.execute(task, index % 3); + } catch (e) { + console.log(e.message); + return []; + } + })); + const searchBooks = results.flat(); + let dataEnd = Date.now(); + showMessage(`搜索中止,共搜索到${this.taskSearchBook.length}个结果` + `用时${(dataEnd - dataStart) / 1000}秒`); + cancel() + } + } catch (error) { + console.error('Error during search:', error); + cancel() + showMessage('搜索中止'); + } + } + + cancelAllTasks() { + if (this.tasks.length > 0) { + this.tasks.forEach((task) => { + console.log(`任务执行中 ${task.name} ${task.isDone()}`) + if (task.isDone()) { + return + } + taskPool.cancel(task) + console.log(`任务已取消 ${task.name} ${task.isDone()}`) + }) + } + this.tasks = []; + this.taskSearchBook = [] + this.bookSourceParts = [] + } + +} + +@Concurrent +async function searchTask(searchKey: string, bookSource: BookSource[]): Promise { + let searchBook:SearchBook[] = [] + try { + for (const source of bookSource) { + if (taskPool.Task.isCanceled()) { + console.log('任务已取消') + return [] + } + const newUrls = await searchUtil.searchData(searchKey, Date.now(), source); + if (newUrls.length > 0) { + searchBook.push(...newUrls) + } + taskPool.Task.sendData(newUrls) + } + return searchBook; + } catch (e) { + console.log(e.message); + return [] + } +} +const taskSearchBooks = new taskSearchBook(); +export default taskSearchBooks as taskSearchBook; \ No newline at end of file diff --git a/entry/src/main/ets/common/utils/NetworkUtils.ets b/entry/src/main/ets/common/utils/NetworkUtils.ets new file mode 100644 index 00000000..a88e5ba8 --- /dev/null +++ b/entry/src/main/ets/common/utils/NetworkUtils.ets @@ -0,0 +1,63 @@ +/** + * @author 2008 + * @datetime 2024/8/15 13:15 + * @className: NetworkUtils + */ +import { url } from '@kit.ArkTS'; +class NetworkUtils { + + getAbsoluteURL(baseUrl: string, searchUrl: string): string { + try { + const absoluteUrl = url.URL.parseURL(searchUrl, baseUrl); + return absoluteUrl.href; + } catch (error) { + console.error('Failed to resolve absolute URL:', error); + return searchUrl; + } + } + + async getBaseUrl(url: string): Promise { + if (!url) return null + if(url.startsWith('http://') || url.startsWith('https://')){ + let index = url.indexOf("/", 9) + if (index === -1) { + return url + } else { + return url.substring(0, index) + } + } + return null + } + + checkContentType(data:string){ + try { + // 尝试将数据解析为 JSON + if (typeof data === 'object') { + return true; + } + JSON.parse(data); + return true; + } catch (e) { + // 如果解析失败,检查是否存在 HTML 标签 + if (/<\/?[^>]+(>|$)/.test(data)) { + return false; + } else { + // 如果既不是有效的 JSON 也不是 HTML,则返回未知类型 + return null; + } + } + } + + //判断类型是string还是ArrayBuffer + isString(data:string|ArrayBuffer):boolean{ + return typeof data === 'string' + } + + cleanUrl(url:string){ + return url.replace(/^\s+|\s+$/g, '').replace(/[\r\n]+/g, ''); + } + + +} +const networkUtil = new NetworkUtils(); +export default networkUtil as NetworkUtils; \ No newline at end of file diff --git a/entry/src/main/ets/common/utils/ParseURLUtils.ets b/entry/src/main/ets/common/utils/ParseURLUtils.ets new file mode 100644 index 00000000..8ada4bab --- /dev/null +++ b/entry/src/main/ets/common/utils/ParseURLUtils.ets @@ -0,0 +1,118 @@ +import { BookSource } from '../../database/entities/BookSource'; +import AppPattern from '../constants/AppPattern'; +import { parseSearchString, SearchConfig } from '../model/SearchConfig'; +import networkUtil from './NetworkUtils'; + +export interface IParseURL { + baseUrl: string + searchUrl: string + page: number + keyword: string + removeCookie?: boolean + bookSource:BookSource + speakText?:string + speakSpeed?:number +} + +export class ParseURL { + baseUrl: string = ''; + searchUrl: string = ''; + page: number = 1; + keyword: string = ''; + removeCookie?: boolean = false; + speakText?:string + speakSpeed?:number + bookSource?:BookSource; + constructor(parseUrl?: IParseURL) { + this.baseUrl = parseUrl?.baseUrl || ''; + this.searchUrl = parseUrl?.searchUrl || ''; + this.page = parseUrl?.page || 1; + this.keyword = parseUrl?.keyword || ''; + this.removeCookie = parseUrl?.removeCookie; + this.bookSource = parseUrl?.bookSource; + this.speakText = parseUrl?.speakText + this.speakSpeed = parseUrl?.speakSpeed + } +} +export class ParseURLUtils { + parseURL: ParseURL = new ParseURL(); + ruleUrl:string = '' + url:string = '' + private init(){ + let urlMatcher = this.parseURL.baseUrl.match(AppPattern.pattern) + if (urlMatcher && urlMatcher.length > 0) { + this.parseURL.baseUrl = this.parseURL.baseUrl.substring(0, urlMatcher.index); + } + this.ruleUrl = networkUtil.cleanUrl(this.parseURL.searchUrl); + this.url = networkUtil.cleanUrl(this.parseURL.baseUrl); + } + + setParseURL(value: ParseURL) { + this.parseURL = value; + } + + extractAndReplace(ruleUrl: string): string { + if (ruleUrl.startsWith("{{")) { + const end = ruleUrl.indexOf("}}"); + if (end !== -1) { + const result = ruleUrl.replace(/{{[^}]*}}/, ''); + return result ?? ''; + } + } + return ruleUrl; + } + + replacePlaceholders(str: string, placeholder: string, replacement: string): string { + const regex = new RegExp(placeholder, 'g'); + return str.replace(regex, replacement); + } + + buildBaseUrl(baseUrl: string, path: string, body: string): string { + const ensureNoLeadingSlash = (str: string) => str.replace(/^\/+/, ''); + const ensureNoTrailingSlash = (str: string) => str.replace(/\/+$/, ''); + + if (path) { + if (!path.startsWith('http://') && !path.startsWith('https://')) { + path = ensureNoTrailingSlash(baseUrl) + '/' + ensureNoLeadingSlash(path); + } else { + path = ensureNoTrailingSlash(path); + } + } else { + path = ensureNoTrailingSlash(baseUrl); + } + + if (path && body) { + if (!path.endsWith('/') && !body.startsWith('/')) { + //需要校验path如果结尾是/就替换为? 如果没有/就添加? + if (path.endsWith('/')) { + path = path.slice(0, -1); + } + path += '?'; + } else if (path.endsWith('/') && body.startsWith('/')) { + body = ensureNoLeadingSlash(body); + } + } + return ensureNoTrailingSlash(path + body); + } + + processRuleUrl(): SearchConfig|string { + this.init() + let baseUrl = '' + let searchUrl = this.ruleUrl + searchUrl = this.extractAndReplace(searchUrl) + try { + let parseSearch = parseSearchString(searchUrl) + baseUrl = this.buildBaseUrl(this.url, parseSearch.path,parseSearch.body); + baseUrl = this.replacePlaceholders(baseUrl, '{{key}}', decodeURIComponent(this.parseURL.keyword)); + baseUrl = this.replacePlaceholders(baseUrl, '{{page}}', this.parseURL.page.toString()); + baseUrl = networkUtil.getAbsoluteURL(this.url, baseUrl) + parseSearch.url = baseUrl + return parseSearch; + } catch (e){ + baseUrl = this.buildBaseUrl(this.url, searchUrl, '') + baseUrl = this.replacePlaceholders(baseUrl, '{{key}}', decodeURIComponent(this.parseURL.keyword)); + baseUrl = this.replacePlaceholders(baseUrl, '{{page}}', this.parseURL.page.toString()); + return baseUrl + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/common/utils/requestUtils.ets b/entry/src/main/ets/common/utils/requestUtils.ets new file mode 100644 index 00000000..9ce123d4 --- /dev/null +++ b/entry/src/main/ets/common/utils/requestUtils.ets @@ -0,0 +1,90 @@ +/** + * @author 2008 + * @datetime 2024/8/16 11:49 + * @className: requestUtils + */ +import axios, { AxiosRequestConfig, AxiosResponse } from '@ohos/axios'; +import { SearchConfig } from '../model/SearchConfig'; +import { util } from '@kit.ArkTS'; + +class requestUtils{ + + async requestHtml(config: SearchConfig): Promise { + // 明确类型声明 + const requestOptions: AxiosRequestConfig = { + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36', + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', + 'Cookie': this.generateRandomCookie(), + 'Content-Encoding': 'gzip, deflate, br, zstd', + 'Accept-Language': 'zh-CN,zh;q=0.9' + }, + responseType: 'array_buffer', // 使用 arraybuffer 以便处理不同编码的文本 + timeout: 5000 + }; + + // 判断请求方法,如果没有指定则默认为POST + const requestMethod: string = config.method ? config.method.toUpperCase() : 'POST'; + + // 处理请求 + let response: AxiosResponse; + if (requestMethod === 'GET') { + try { + response = await axios.get(config.url, requestOptions); + } catch (e) { + response = await axios.post(config.url, config.body || '', requestOptions); + } + } else { + try { + response = await axios.post(config.url, config.body || '', requestOptions); + } catch (e) { + response = await axios.get(config.url, requestOptions); + } + } + let res = this.textDecoder(response.data,config.charset??'UTF-8') + // 处理响应数据 + return res + } + + + + async requestUrlHtml(url: string): Promise { + // 明确类型声明 + const requestOptions: AxiosRequestConfig = { + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36', + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', + 'Cookie': this.generateRandomCookie(), + 'Content-Encoding': 'gzip, deflate, br, zstd', + 'Accept-Language': 'zh-CN,zh;q=0.9' + }, + responseType: 'object', // 使用 arraybuffer 以便处理不同编码的文本 + timeout: 5000 + }; + // 处理请求 + let response: AxiosResponse; + + try { + response = await axios.get(url, requestOptions); + } catch (e) { + response = await axios.post(url, requestOptions); + } + // 处理响应数据 + return response.data + } + + private textDecoder(data:ArrayBuffer, encoding:string = 'UTF-8'):string{ + let textDecoder = util.TextDecoder.create(encoding, { ignoreBOM: true }); + const result = new Uint8Array(data); + let decodedText = textDecoder.decodeWithStream(result); + return decodedText + } + + private generateRandomCookie(){ + const randomValue = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); + return `random-cookie=${randomValue}`; + } +} + +const requestUtil = new requestUtils(); +export default requestUtil as requestUtils; \ No newline at end of file diff --git a/entry/src/main/ets/common/utils/searchUtils.ets b/entry/src/main/ets/common/utils/searchUtils.ets new file mode 100644 index 00000000..d5adfb80 --- /dev/null +++ b/entry/src/main/ets/common/utils/searchUtils.ets @@ -0,0 +1,98 @@ +import { showMessage } from '../../componets/common/promptShow'; +import bookSourceDao from '../../database/dao/BookSourceDao'; +import { BookSource } from '../../database/entities/BookSource'; +import { SearchBook } from '../../database/entities/SearchBook'; +import { XmlAnalysis, XmlAnalysisData } from '../model/XmlAnalysis'; +import networkUtil from './NetworkUtils'; +import { IParseURL, ParseURL, ParseURLUtils } from './ParseURLUtils'; +import requestUtil from './requestUtils'; + + +class searchUtils{ + searchBooks: SearchBook[] = [] + private bookSourceParts:BookSource[] = [] + private searchKey: string = '' + private searchPage:number = 1 + private mSearchId:number = 0 + + async search(searchKey: string) { + if (!searchKey) { + return; + } + this.searchKey = searchKey; + this.bookSourceParts = await bookSourceDao.getEnabledPartByGroup(); + if (!this.bookSourceParts || this.bookSourceParts.length === 0) { + showMessage('启用书源为空'); + return; + } + this.searchBooks = []; + for (const bookSource of this.bookSourceParts) { + this.searchData(this.searchKey,Date.now(), bookSource); + } + } + + async searchData(searchKey: string,searchId:number, bookSource:BookSource) { + this.searchKey = searchKey; + if (searchId != this.mSearchId) { + this.mSearchId = searchId + this.searchPage = 1 + } else { + this.searchPage++ + } + try { + let book = await this.searchBook(bookSource) + return book + } catch (e) { + console.log(e.message) + return [] + } + } + + async searchBook(bookSource:BookSource): Promise{ + if (!bookSource.searchUrl || bookSource.searchUrl === undefined) { + return [] + } + let books:SearchBook[] = [] + let urls = '' + let body = '' + + // 解析搜索url + const parseURLUtil = new ParseURLUtils(); + let xmlAnalysis = new XmlAnalysis(); + const parseURLData: IParseURL = { + page: this.searchPage, + keyword: this.searchKey, + baseUrl: bookSource.bookSourceUrl, + searchUrl: bookSource.searchUrl, + bookSource:bookSource + }; + parseURLUtil.setParseURL(new ParseURL(parseURLData)); + let parse = parseURLUtil.processRuleUrl(); + if (typeof parse !== 'string') { + urls = parse.url + // body = await requestUtil.requestHtml(parse) + } else { + urls = parse + // let res = await requestUtil.requestUrlHtml(parse) + // if (res === undefined) return [] + // if (networkUtil.checkContentType(res)) { + // console.log('Json数据') + // } + // body = res + } + if (bookSource.ruleSearch) { + xmlAnalysis.init(new XmlAnalysisData({ + baseUrl: parseURLData.baseUrl, + body: body, + bookSource:bookSource, + searchUrl: urls + })) + let searchBooks = await xmlAnalysis.analysisXml() + books.push(...searchBooks) + } + return books + } +} + +const searchUtil = new searchUtils(); +export default searchUtil as searchUtils; \ No newline at end of file diff --git a/entry/src/main/ets/common/utils/utils.ets b/entry/src/main/ets/common/utils/utils.ets index 20dc725c..41080a54 100644 --- a/entry/src/main/ets/common/utils/utils.ets +++ b/entry/src/main/ets/common/utils/utils.ets @@ -35,6 +35,24 @@ export const deduplicateObjects = (list: T[]): T[] => { return Array.from(uniqueSet).map(strItem => JSON.parse(strItem) as T); }; +// 判断是否是json +export const isJson = (json: string): boolean => { + try { + JSON.parse(json); + return true; + } catch (e) { + return false; + } +} +// 判断是否为空 +export const isNullOrBlank = (value: string | null | undefined): boolean => { + return value == null || value.trim().length === 0; +} +// 判断是否为网页链接 +export const isAbsUrl = (url:string): boolean => { + return url.startsWith('http://') || url.startsWith('https://'); +} + class Gson { toString(info?: T): string | undefined { if (!info) { diff --git a/entry/src/main/ets/componets/search/SearchBookItem.ets b/entry/src/main/ets/componets/search/SearchBookItem.ets index daff5e4a..fa18bf8e 100644 --- a/entry/src/main/ets/componets/search/SearchBookItem.ets +++ b/entry/src/main/ets/componets/search/SearchBookItem.ets @@ -1,11 +1,12 @@ -import { SearchBook } from '../dataList/SearchBook' +import { SearchBook } from '../../database/entities/SearchBook' +import { router } from '@kit.ArkUI' @Component export struct SearchBookItem { @Prop SearchValue:string - @Prop list:String[]=[] @Prop book:SearchBook @State fontColor:string="rgba(0, 0, 0, 0.45)" + @Prop index:number @Builder label(item:string){ Text(item) .fontSize(10) @@ -16,17 +17,16 @@ export struct SearchBookItem { } build() { Row({space:20}){ - Image($r('app.media.cover_list')).size({ + Image(this.book.coverUrl).size({ width:70, height:100 }) + .alt($r('app.media.icon_book_default_cover_five')) Column({space:5}){ //书名 Row(){ - ForEach(this.list,(item:string,index:number)=>{ - Text(item) - .fontColor(index==1?"rgba(255, 102, 0, 1)":"#000000") - }) + Text(this.book.name) + .fontColor(this.index<=3?"rgba(255, 102, 0, 1)":"#000000") } //作者及书源数量 Row({space:2}){ @@ -36,38 +36,48 @@ export struct SearchBookItem { Text("·") .fontSize(14) .fontColor(this.fontColor) - Text("共"+this.book.sourceNumber.toString()+"个源") + Text("共"+this.book.belongCount+"个源") .fontSize(14) .fontColor(this.fontColor) } //书籍描述 - Text(this.book.describe) + Text(this.book.intro) .size({width:200,height:40}) .fontSize(14) .maxLines(2) .fontColor(this.fontColor) .textOverflow({overflow:TextOverflow.Ellipsis}) Row({space:5}){ - ForEach(this.book.label,(item:string)=>{ - this.label(item) - }) - this.label(this.book.status?"完本":"连载中") - this.label(this.wordStatistics()) + // ForEach(this.book.label,(item:string)=>{ + // this.label(item) + // }) + this.label(this.book.kind??'') + this.label(this.book.wordCount??'') }.width("100%").height(20) }.alignItems(HorizontalAlign.Start) - }.size({ + } + .onClick(()=>{ + router.pushUrl({ + url: 'pages/view/BookDetailPage', + params: { + bookName: this.book.name, + coverUrl: this.book.coverUrl, + author: this.book.author, + intro: this.book.intro, + bookUrl:this.book.bookUrl, + wordCount:this.book.wordCount, + bookType:this.book.bookType, + origin:this.book.source?.bookSourceUrl, + originName:this.book.source?.bookSourceName + } + }) + }) + + .size({ width:"100%", height:120 }).margin({left:20,right:20}) } - wordStatistics(){ - let wordCount=this.book.wordCount.toString() - let wordCountLength=wordCount.length - if(wordCountLength>4){ - return wordCount.substring(0,wordCountLength-4)+"万字" - }else{ - return wordCount+"字" - } - } + } \ No newline at end of file diff --git a/entry/src/main/ets/componets/search/SearchBookList.ets b/entry/src/main/ets/componets/search/SearchBookList.ets index 62f491e6..e8108f47 100644 --- a/entry/src/main/ets/componets/search/SearchBookList.ets +++ b/entry/src/main/ets/componets/search/SearchBookList.ets @@ -1,43 +1,73 @@ -import { SearchBook } from '../dataList/SearchBook' + import { common } from '@kit.AbilityKit' import { util } from '@kit.ArkTS' +import { SearchBook } from '../../database/entities/SearchBook' import { SearchBookItem } from './SearchBookItem' @Component export struct SearchBookList { - + @State cachedCountNumber: number = 5; // 懒加载缓存数 + @State firstIndex: number = 0; // 当前显示在屏幕上的子组件索引值,用来控制下方按钮跳转 @Prop SearchValue:string @State titleLists:string[]=[] @State isRefreshing: boolean = false @State counter: number = 0 - - @State bookList:SearchBook[]=[ - new SearchBook("轮回乐园", - "机遇与危险共存,只要豁出性命,在轮回乐园中就能得到一切。  被轮回乐园选中成为‘猎杀者’的那一刻,苏晓就明白,他将与所有人为敌。  乐园虽然残酷,但无所不能……。\n", - "", - "那一只蚊子", - ["玄幻"], - 15, - 2731594, - false - ), - new SearchBook("轮回乐园", - " 机遇与危险共存,只要豁出性命,在轮回乐园中就能得到一切。被轮回乐园选中成为‘猎杀者’的那一刻,苏晓就明白,他将与所有人为敌。  乐园虽然残酷,但无所不能……。\n", - "", - "那一只蚊子", - [], - 8, - 1567932, - true - ) - ] - + @Prop typeIndex:number + @Prop booksList:SearchBook[] + listScroller: ListScroller = new ListScroller(); // scroller控制器 + historyOffset: number = 0; // 上次浏览到列表距离顶端的偏移量offset + readonly DEFAULT_OFFSET: number = 0; // 默认跳转位置,用于第一次进入页面的跳转位置 + readonly ANIMATION_DURATION: number = 500; // 动画持续时间 + readonly SWITCH_BUTTON: number = 5; // 当前显示在屏幕上的子组件Index到达该阈值时,将跳转到历史记录按钮切换成跳转到顶部 build() { - Column({space:10}){ - ForEach(this.bookList,(item:SearchBook)=>{ - SearchBookItem({book:item,SearchValue:this.SearchValue,list:this.subKeyword(this.SearchValue,item.title)}) + Column(){ + List({space:8,scroller:this.listScroller}){ + ForEach(this.booksList,(item:SearchBook,index)=>{ + if (item.bookType === this.typeIndex){ + ListItem(){ + SearchBookItem({book:item,SearchValue:this.SearchValue,index:index}) + } + } + }) + } + .cachedCount(this.cachedCountNumber) + .layoutWeight(1) + .chainAnimation(true) + .scrollBar(BarState.Off) + .onScrollIndex((first: number) => { + this.firstIndex = first; + }) + + Button(this.firstIndex < this.SWITCH_BUTTON ? '跳转到上次浏览历史': '跳转到顶部', { + type: ButtonType.Normal, + stateEffect: true }) - }.width("100%").padding({left:20,right:20}).height("100%") + .fontColor('#000') + .height(40) + .borderRadius(20) + .backgroundColor('#FFAEAEAE') + .opacity(0.8) + .width(200) + .position({ x: '90%', y: '90%'}) + .markAnchor({ x:'90%'}) + .onClick(() => { + // 当前显示的index值小于设定的SWITCH_BUTTON时,按钮为跳转到上次浏览记录,否则为跳转到顶部 + if (this.firstIndex < this.SWITCH_BUTTON) { + this.listScroller.scrollTo({ + xOffset: 0, + yOffset: this.historyOffset === 0 ? this.DEFAULT_OFFSET : this.historyOffset, + animation: { duration: this.ANIMATION_DURATION, curve: Curve.LinearOutSlowIn } + }); + } else { + this.historyOffset = this.listScroller.currentOffset().yOffset; + this.listScroller.scrollTo({ + xOffset: 0, + yOffset: 0, + animation: { duration: this.ANIMATION_DURATION, curve: Curve.LinearOutSlowIn } + }); + } + }) + } } @@ -66,7 +96,7 @@ export struct SearchBookList { } else { let decoder = util.TextDecoder.create('utf-8',{ignoreBOM : true }) values = decoder.decodeWithStream(value,{stream:false}); - this.bookList=JSON.parse(values) + // this.bookList=JSON.parse(values) } }); } diff --git a/entry/src/main/ets/componets/search/SearchDetails.ets b/entry/src/main/ets/componets/search/SearchDetails.ets index ec7440c6..07b3a833 100644 --- a/entry/src/main/ets/componets/search/SearchDetails.ets +++ b/entry/src/main/ets/componets/search/SearchDetails.ets @@ -1,7 +1,6 @@ -import { SearchBook } from '../dataList/SearchBook' + import { SearchBookList } from './SearchBookList' -import { common } from '@kit.AbilityKit' -import { util } from '@kit.ArkTS' +import { SearchBook } from '../../database/entities/SearchBook' @Component export struct SearchDetails{ @@ -11,7 +10,9 @@ export struct SearchDetails{ @State typeList: string[] = ["小说","漫画","有声书"] @State typeIndex:number=0 @Consume @Watch("refresh") SearchStatus:number - @State color:string="rgba(0, 0, 0, 0.45)" + @Consume('searchBookList') books:SearchBook[] + @Consume('isSearch') isSearch:boolean + @StorageLink('bottomRectHeight') bottomRectHeight: number = 0 refresh(){ if(this.SearchStatus>2){ this.isRefreshing=true @@ -21,7 +22,7 @@ export struct SearchDetails{ Stack(){ Row(){ LoadingProgress().color("#FF6600").height(26) - Text("共搜索到"+this.counter+"个相关结果").fontSize(14).margin({left:20}).fontColor(this.color) + Text("共搜索到"+this.books.length+"个相关结果").fontSize(14).margin({left:20}).fontColor($r('app.string.color_black_45')) }.alignItems(VerticalAlign.Center) }.width("100%").align(Alignment.Center) } @@ -33,7 +34,7 @@ export struct SearchDetails{ ForEach(this.typeList,(item:string,index:number)=>{ Text(item) .fontSize(16) - .fontColor(this.typeIndex==index?"#000000":this.color) + .fontColor(this.typeIndex==index?"#000000":$r('app.string.color_black_45')) .onClick(()=>{ this.typeIndex=index }) @@ -41,24 +42,32 @@ export struct SearchDetails{ }.width("60%").height("100%") //筛选 Row({space:5}){ - Image($r("app.media.screen")).width(20).height(20).fillColor(this.color) - Text("筛选").fontColor(this.color) + Image($r("app.media.screen")).width(20).height(20).fillColor($r('app.string.color_black_45')) + Text("筛选").fontColor($r('app.string.color_black_45')) }.width("40%").height("100%").justifyContent(FlexAlign.End) }.size({width:"100%",height:40}).padding({left:20,right:20}).margin(10) //书籍列表 Refresh({ refreshing: $$this.isRefreshing, offset: 120, friction: 100 ,builder:this.RefreshComponent}) { - SearchBookList({SearchValue:this.SearchValue}).backgroundColor("#ffffff") - }.onStateChange((refreshStatus: RefreshStatus) => { - if(refreshStatus=RefreshStatus.Refresh){ - this.counter++ - } + SearchBookList({SearchValue:this.SearchValue,booksList:this.books,typeIndex:this.typeIndex}).backgroundColor("#ffffff") + } + //给border添加加载动画 + .border({ + width: { top:this.isSearch?2:0 },color:$r('app.color.theme_color') + }) + .borderStyle({ + top:BorderStyle.Solid, + }) + .onStateChange((refreshStatus: RefreshStatus) => { }).onRefreshing(() => { setTimeout(() => { this.isRefreshing = false - this.counter=0 }, 5000) }).backgroundColor("rgba(0, 0, 0, 0.06)") } + .padding({ + bottom:this.bottomRectHeight + }) + .layoutWeight(1) } //初始化 aboutToAppear(): void { diff --git a/entry/src/main/ets/componets/search/SearchHead.ets b/entry/src/main/ets/componets/search/SearchHead.ets index 73b9f1c5..09efa267 100644 --- a/entry/src/main/ets/componets/search/SearchHead.ets +++ b/entry/src/main/ets/componets/search/SearchHead.ets @@ -1,10 +1,20 @@ import { router } from '@kit.ArkUI'; -import { Attribute } from '../../common/utils/Attribute'; +import taskSearchBooks from '../../common/model/taskSearchBook'; +import { SearchBook } from '../../database/entities/SearchBook'; +import { showMessage } from '../common/promptShow'; + @Component export struct SearchHead{ @Consume SearchValue: string @Consume SearchStatus: number + @Consume('searchBookList') books:SearchBook[] + @State lastSearch:string = '' + @Consume('isSearch') isSearch:boolean + testBook(){ + console.log(this.books.length.toString()) + console.log(this.books.toString()) + } build() { Flex({ alignItems:ItemAlign.End, @@ -16,13 +26,23 @@ export struct SearchHead{ .height(25) .onClick(()=>{ router.back() + taskSearchBooks.cancelAllTasks() }) Search({ value: this.SearchValue, placeholder: '搜索书名或作者', icon: "/common/images/search.svg" }) .width("70%") .height(40) .onChange(this.onChange) .onSubmit(this.onSubmit); - Text(this.SearchValue.length>0?"取消":"搜索").width("10%").height(40).fontWeight(600) + Text(this.isSearch?"取消":"搜索").width("10%").height(40).fontWeight(600) + .onClick(()=>{ + if (this.isSearch) { + taskSearchBooks.cancelAllTasks() + console.log(`取消搜索${this.books.length}`) + this.isSearch = false + } else { + this.onSubmit(this.SearchValue) + } + }) }.margin({left:5}).width("100%") } .borderRadius({ bottomRight:5 ,bottomLeft:5}) @@ -32,12 +52,98 @@ export struct SearchHead{ } onChange=(value: string)=> { + this.lastSearch = this.SearchValue this.SearchValue=value; } - onSubmit=(value: string)=>{ + onSubmit= async (value: string)=>{ this.SearchValue=value; - this.SearchStatus=1; - console.log(this.SearchStatus.toString()); + if (!this.SearchValue) { + showMessage('搜索内容不能为空') + return + } + if (this.books.length === 0 || this.SearchValue !== this.lastSearch) { + this.SearchStatus = 1 + } + this.isSearch = true + this.books = [] + taskSearchBooks.search(this.SearchValue, (newUrls: SearchBook[]) => { + this.setBookList(newUrls) + },()=>{ + this.SearchStatus = 2 + this.isSearch = false + }) + } + + setBookList(books: SearchBook[]) { + books.forEach((book: SearchBook) => { + let found = false; + + for (let item of this.books) { + if (item.name === book.name && item.author === book.author) { + item.belongCount++; + //源来的books的source源 id和 books的source源 id相同不添加 + if (item.source?.bookSourceUrl !== book.source?.bookSourceUrl && book.source) { + item.sourceList.push(book.source) + } + found = true; + break; + } + } + + + if (!found) { + book.belongCount = 1 + this.books.push(book); + } + }); + this.sortBooksBySearchValue() + } + + sortBooksBySearchValue(){ + this.books.sort((a: SearchBook, b: SearchBook) => { + const aNameSimilarity = this.getSimilarity(a.name.toLowerCase(), this.SearchValue.toLowerCase()); + const bNameSimilarity = this.getSimilarity(b.name.toLowerCase(), this.SearchValue.toLowerCase()); + + if (aNameSimilarity !== bNameSimilarity) { + return bNameSimilarity - aNameSimilarity; + } + + return b.belongCount - a.belongCount; + }); + } + + getSimilarity(str1: string, str2: string){ + const lengthDifference = Math.abs(str1.length - str2.length); + const commonLength = Math.min(str1.length, str2.length); + let similarity = 0; + + for (let i = 0; i < commonLength; i++) { + if (str1[i] === str2[i]) { + similarity++; + } + } + + return similarity - lengthDifference; + } + + + countMatchingChars(last:string, current:string){ + // 创建一个对象来存储字符串 a 中的字符及其出现次数 + const charsInA: Record = {}; + // 填充对象 + for (const char of last) { + charsInA[char] = (charsInA[char] || 0) + 1; + } + // 初始化计数器 + let count = 0; + // 遍历字符串 b 中的每个字符 + for (const char of current) { + // 如果字符存在于 a 中,则计数器加一 + if (charsInA[char]) { + count++; + } + } + return count; } aboutToAppear(): void { console.log(this.SearchStatus.toString()); diff --git a/entry/src/main/ets/database/dao/BookSourceDao.ets b/entry/src/main/ets/database/dao/BookSourceDao.ets index d153774e..3bee72c5 100644 --- a/entry/src/main/ets/database/dao/BookSourceDao.ets +++ b/entry/src/main/ets/database/dao/BookSourceDao.ets @@ -57,6 +57,19 @@ class BookSourceDao { } } + async getEnabledPartByGroup() { + try { + const column: ColumnInfo[] = AppDatabaseUtil.getColumn(this.TABLE_NAME); + const sql = `SELECT * FROM book_sources WHERE enabled = 1 AND enabledExplore = 1 ORDER BY customOrder ASC` + const bookSourceDbList = await DbUtil.querySqlForList(sql, column); + const bookSourceList = bookSourceDbList.map((item): BookSource => ToBookSource(item)); + return bookSourceList + } catch (e){ + console.log("TagInfo", JSON.stringify(e)) + return [] + } + } + async flowSearch(searchParams?: BookSourceSearchParams): Promise { const searchKey = searchParams?.searchKey ?? ''; const type = searchParams?.type; diff --git a/entry/src/main/ets/database/entities/SearchBook.ets b/entry/src/main/ets/database/entities/SearchBook.ets new file mode 100644 index 00000000..a8b9112c --- /dev/null +++ b/entry/src/main/ets/database/entities/SearchBook.ets @@ -0,0 +1,36 @@ +/** + * @author 2008 + * @datetime 2024/8/14 20:56 + * @className: SearchBook + */ +import { BookSource } from './BookSource' + +@Observed +export class SearchBook { + bookUrl: string = "" + /** 书源 */ + origin: string = "" + originName: string = "" + /** BookType */ + type: string = "" + name: string = "" + author: string = "" + kind?:string + coverUrl?: string + intro?: string + wordCount?:string + latestChapterTitle:string = "" + /** 目录页Url (toc=table of Contents) */ + tocUrl?:string = "" + time:number = Date.now() + variable?:string + originOrder?:number = 0 + chapterWordCountText?:string + chapterWordCount?: number = -1 + respondTime?: number = -1 + //数量 + belongCount:number = 1 + source?:BookSource + sourceList:BookSource[] = [] + bookType:number = 0 +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/search/SearchBook.ets b/entry/src/main/ets/pages/search/SearchBook.ets deleted file mode 100644 index 777a0349..00000000 --- a/entry/src/main/ets/pages/search/SearchBook.ets +++ /dev/null @@ -1,28 +0,0 @@ -import { SearchDetails } from '../../componets/search/SearchDetails'; -import { SearchHead } from '../../componets/search/SearchHead'; -import { SearchHistory } from '../../componets/search/SearchHistory'; -import { SearchRefresh } from '../../componets/search/SearchRefresh'; - -@Entry -@Component -struct SearchBook { - @Provide SearchValue: string ="1" - @Provide SearchStatus: number=0; - - - - build() { - Column() { - //顶部搜索框 - SearchHead(); - if(this.SearchStatus==0){ - SearchHistory(); - }else if(this.SearchStatus==1){ - SearchRefresh() - }else { - SearchDetails({SearchValue:this.SearchValue}); - } - } - } -} - diff --git a/entry/src/main/ets/pages/search/Search_Ranking.ets b/entry/src/main/ets/pages/search/Search_Ranking.ets deleted file mode 100644 index 518e728a..00000000 --- a/entry/src/main/ets/pages/search/Search_Ranking.ets +++ /dev/null @@ -1,29 +0,0 @@ - -/** - * 废弃组件 搜索页排行榜组件 - */ -@Component -export struct Ranking{ - rankList:string[]=["我在精神病院学斩神1111111111111111","暮年修仙,我成长寿道尊","全家流放:我搬空国库去逃荒!","上班摸鱼被抓,反手拉女总裁","我的26岁女房客","我在精神病院学斩神","我在精神病院学斩神","我在精神病院学斩神","我在精神病院学斩神","我在精神病院学斩神"]; - build(){ - Column(){ - Column(){ - Image("/common/images/ranking_1.png").width(260).height(50).objectFit(ImageFit.Auto) - }.margin({bottom:20}) - ForEach(this.rankList,(item :string,index:number)=>{ - Row({space:20}){ - Text((index+1).toString()).fontColor((index+1)>3?"rgb(191, 191, 191)":"#EF4444").fontWeight(600).margin({bottom:20}) - Text(item).width(150).margin({bottom:20}).textOverflow({overflow:TextOverflow.Ellipsis}).maxLines(1) - } - }) - Row(){ - Text("查看更多").fontColor("rgba(0, 0, 0, 0.45)") - Image("/common/images/search_launch.svg") - .width(24) - .height(24) - .fillColor($r("app.color.search_launch")) - .rotate({angle: 270}) - }.width("100%").justifyContent(FlexAlign.Center) - }.border({width:1,color:"rgb(255, 234, 221)",radius:10}).width(260).height("85%").clip(true) - } -} \ No newline at end of file diff --git a/entry/src/main/ets/pages/view/myCenter/MyCenter.ets b/entry/src/main/ets/pages/view/myCenter/MyCenter.ets index 850178cd..07292acc 100644 --- a/entry/src/main/ets/pages/view/myCenter/MyCenter.ets +++ b/entry/src/main/ets/pages/view/myCenter/MyCenter.ets @@ -34,7 +34,7 @@ export default struct MyCenter { Column = new ColumnModifier() build() { Column(){ - Blank(this.topRectHeight-20) + // Blank(this.topRectHeight-20) //右上角模式图标 Row({space:10}){ //设置图标 @@ -117,23 +117,29 @@ export default struct MyCenter { //帮助 关于 清理缓存 设置 Column(){ - ForEach(this.end_list,(item:dataItem)=>{ - Row({space:10}){ - Image(item.icon).size({width:20,height:20}).interpolation(ImageInterpolation.High) - Text(item.title).fontSize(14).width("75%" ).layoutWeight(1) - Image($r("app.media.right")).size({width:24,height:24}) - }.width("100%") - .height(48) - .justifyContent(FlexAlign.SpaceBetween) - .padding({top:12,bottom:12,left:20,right:20}) - .onClick(()=>this.endOnclick(item.value)) - }) - }.padding({top:5,bottom:5}) + Scroll(){ + Column(){ + ForEach(this.end_list,(item:dataItem)=>{ + Row({space:10}){ + Image(item.icon).size({width:20,height:20}).interpolation(ImageInterpolation.High) + Text(item.title).fontSize(14).width("75%" ).layoutWeight(1) + Image($r("app.media.right")).size({width:24,height:24}) + } + .width("100%") + .height(45) + .justifyContent(FlexAlign.SpaceBetween) + .padding({top:12,bottom:12,left:20,right:20}) + .onClick(()=>this.endOnclick(item.value)) + }) + } + } + .scrollBar(BarState.Off) + } + .layoutWeight(1) + .padding({top:5,bottom:5}) .backgroundColor(PublicConstants.THEME_COLOR_WHITE) .borderRadius(10) .justifyContent(FlexAlign.SpaceBetween) - .size({width:"100%",height:210}) - Blank(200) } .attributeModifier(this.Column) .linearGradient({ diff --git a/entry/src/main/ets/pages/view/myCenter/MySetPage.ets b/entry/src/main/ets/pages/view/myCenter/MySetPage.ets index 1be16646..b15b25d5 100644 --- a/entry/src/main/ets/pages/view/myCenter/MySetPage.ets +++ b/entry/src/main/ets/pages/view/myCenter/MySetPage.ets @@ -26,7 +26,8 @@ struct MySetPage{ private fruits: string[] = getRandomStringArray() @State languageList: string[] = ['跟随系统','简体中文','繁体中文','英文'] - + @StorageLink('bottomRectHeight') bottomRectHeight: number = 0 + @StorageLink('topRectHeight') topRectHeight: number = 0 @State bookSetList: SetList[]=[ @@ -77,9 +78,6 @@ struct MySetPage{ build() { Column(){ - - Blank(40) - SetHead({titleName:'我的设置'}).height(48).padding({left:20}) List(){ @@ -113,7 +111,12 @@ struct MySetPage{ .height('100%') .layoutWeight(1) .scrollBar(BarState.Off) - }.backgroundColor('#F5F5F5') + } + .padding({ + top:this.topRectHeight, + bottom: this.bottomRectHeight, + }) + .backgroundColor('#F5F5F5') .bindSheet($$this.isShowLanguage, this.languageSheet(), { height: 288, dragBar: false, diff --git a/entry/src/main/ets/pages/view/search/SearchBook.ets b/entry/src/main/ets/pages/view/search/SearchBook.ets index 1070656b..d565dfd6 100644 --- a/entry/src/main/ets/pages/view/search/SearchBook.ets +++ b/entry/src/main/ets/pages/view/search/SearchBook.ets @@ -3,26 +3,37 @@ import { SearchHead } from '../../../componets/search/SearchHead' import { SearchHistory } from '../../../componets/search/SearchHistory' import { SearchDetails } from '../../../componets/search/SearchDetails' import { SearchRefresh } from '../../../componets/search/SearchRefresh' +import taskSearchBooks from '../../../common/model/taskSearchBook' +import { showMessage } from '../../../componets/common/promptShow' @Entry @Component struct SearchBook { - @Provide SearchValue: string ="1" + @Provide SearchValue: string ="遮天" @Provide SearchStatus: number=0; + @Provide('searchBookList') books:SearchBook[] = [] + @StorageLink('bottomRectHeight') bottomRectHeight: number = 0 + @Provide('isSearch') isSearch:boolean = false - - + onBackPress(): boolean | void { + taskSearchBooks.cancelAllTasks() + return false + } build() { Column() { //顶部搜索框 SearchHead(); - if(this.SearchStatus==0){ + if(!this.isSearch && this.books.length <= 0){ SearchHistory(); - }else if(this.SearchStatus==1){ + }else if(this.isSearch && this.books.length <= 0){ SearchRefresh() }else { - SearchDetails({SearchValue:this.SearchValue}); + SearchDetails({SearchValue:this.SearchValue}).layoutWeight(1) } } + .layoutWeight(1) + .padding({ + bottom:this.bottomRectHeight + }) } }