import type { PopsPanelContentConfig } from "@whitesev/pops/dist/types/src/components/panel/types/index.js"; import Qmsg from "qmsg"; import { DOMUtils, log, pops, utils } from "../env.base"; import { PanelUISize } from "../setting/panel-ui-size"; import { RuleEditView } from "./RuleEditView"; import type { RuleViewSearchExternalOption, RuleViewSearchRuleValueOption } from "./RuleView"; /** * 规则订阅配置 */ type RuleSubscribeOption = { /** 唯一id */ uuid: string; /** 订阅内的数据 */ subscribeData: { /** 订阅标题 */ title: string; /** 版本 */ version: number; /** 订阅的最后更新时间 */ lastModified: number; /** 订阅的主页地址 */ homePage?: string; /** 规则数据 */ ruleData: T[]; }; /** 操作这个订阅的数据 */ data: { /** 是否启用 */ enable: boolean; /** 订阅地址 */ url: string; /** 订阅标题 */ title?: string; /** 最后更新该订阅的时间 */ latestUpdateTime: number; /** 最后更新该订阅失败的时间 */ updateFailedTime: number | null; }; }; /** * 规则视图按钮配置 */ type RulePanelBtnControlsOption = { /** * 添加按钮 */ add?: { enable: boolean; /** * 点击回调 * @returns * + false 不执行显示添加弹窗 */ callback?: ( this: (typeof RulePanelView)["prototype"], option: { event: MouseEvent | PointerEvent; $section: HTMLElement; } ) => IPromise; }; /** * 过滤按钮 */ filter?: { /** @default false */ enable: boolean; /** 搜索配置项1 */ option: RuleViewSearchExternalOption[]; /** 输入框的前置选项 */ inputOption: RuleViewSearchRuleValueOption[]; /** * 执行过滤完毕后的回调 */ execFilterCallBack?: () => IPromise; }; /** * 清空所有按钮 */ clearAll?: { enable: boolean; /** * 点击清空按钮弹出弹窗后点击确定的回调函数 * * 在里面执行清空操作 * @returns * + false 阻止默认行为 */ callback?: () => IPromise; }; /** * 导入按钮 */ import?: { enable: boolean; /** * 点击按钮的回调函数 * @returns * + false 阻止默认行为 */ callback?: ( /** 更新视图 */ updateView: () => void ) => IPromise; }; /** * 导出按钮 */ export?: { enable: boolean; /** * 点击回调 * @returns * + false 阻止默认行为 */ callback?: (option: { event: MouseEvent | PointerEvent }) => IPromise; }; /** * 规则的开启/关闭按钮 */ ruleEnable?: { enable: boolean; /** * 获取单条规则的启用状态 * @param data */ getEnable: (data: T) => IPromise; /** * 启用状态回调 * @param enable */ callback: (data: T, enable: boolean) => IPromise; }; /** * 规则的编辑按钮 */ ruleEdit?: { enable: boolean; /** * 点击编辑按钮的回调 * @returns * + false 不执行显示编辑弹窗 */ callback?: (option: { /** this环境 */ context: (typeof RulePanelView)["prototype"]; /** 点击事件 */ event: MouseEvent | PointerEvent; /** 配置 */ option: RulePanelAnyOption; /** 订阅配置 */ subscribeOption: RulePanelSubscribeOption | undefined; /** 当前编辑的数据 */ ruleData: T & RuleSubscribeOption; /** 当前的容器元素 */ $section: HTMLElement; /** 当前的规则元素 */ $ruleItem: HTMLElement; enterDeepMenu: (option: { /** * 标题文字 */ headerTitle?: string; /** * 获取所有数据 */ data: () => IPromise; /** * 获取单个规则当前的数据 */ getData: (data: T2) => IPromise; /** * 获取单个数据的名字,用户显示在列表中,可是html */ getDataItemName: (data: T2) => IPromise; /** * 新增单个规则 */ addData?: (data: T2) => IPromise; /** * 更新单个规则 */ updateData: (data: T2) => IPromise; /** * 删除单个规则,用于删除getAddData创建时的数据 */ deleteData: (data: T2) => IPromise; /** * 按钮控制 */ btnControls?: RulePanelBtnControlsOption; }) => IPromise; }) => boolean | void; } & { enable: boolean; /** * 编辑/添加视图的宽度(注意带单位,px或%) */ viewWidth?: () => string; /** * 编辑/添加视图的高度(注意带单位,px或%) */ viewHeight?: () => string; /** * 获取编辑/添加视图的html */ getView?: (data: T, isEdit: boolean) => IPromise; /** * 当编辑/添加视图的提交表单时触发的回调函数 */ onsubmit /** * @returns * + true 校验通过 * + false 校验失败 */?: ( /** */ $form: HTMLFormElement, /** 是否是编辑状态 */ isEdit: boolean, /** 如果isEdit为true,该data存在 */ data?: T ) => IPromise<{ success: boolean; data: T; }>; /** * 自定义的style */ style?: string; }; /** * 规则的删除按钮 */ ruleDelete?: { enable: boolean; /** * 删除的回调函数 * @param data */ deleteCallBack: (data: T) => IPromise; }; }; /** * 规则视图的内容配置 */ type RulePanelContentOption = Omit & { /** * 订阅配置 */ subscribe?: RulePanelSubscribeOption; /** * 规则配置 */ ruleOption: RulePanelRuleOption; }; /** * 规则视图配置 */ type RulePanelOption = { /** 面版标题 */ title: string | (() => string); /** 面版内容配置 */ contentConfig: RulePanelContentOption[]; /** 面版的className */ className?: string; /** 自定义样式 */ style?: string; /** * 是否使用深度菜单切换动画 * * 如果浏览器不支持`document.startViewTransition`函数,那么即使使用`useDeepMenuSwtichAnimation`为true,那么不会使用动画 * @default true */ useDeepMenuSwtichAnimation?: boolean; }; /** * 规则面版的各种订阅的配置 */ type RulePanelSubscribeOption = { /** * 是否启用 */ enable: boolean; /** * 按钮/标题的文字 * @default "订阅" */ title?: string; /** * 标题的文字 * @default "订阅" */ headerTitle?: string; /** * 点击订阅按钮的点击事件 */ callback?: () => IPromise; /** * 获取订阅的所有数据 */ data: () => IPromise[]>; /** * 获取单个规则当前的数据 */ getData: (data: RuleSubscribeOption) => IPromise>; /** * 获取单个数据的名字,用户显示在列表中,可是html */ getDataItemName: (data: RuleSubscribeOption) => IPromise; /** * 新增单个规则 */ addData: (data: RuleSubscribeOption) => IPromise; /** * 更新单个规则 */ updateData: (data: RuleSubscribeOption) => IPromise; /** * 删除单个规则,用于删除getAddData创建时的数据 */ deleteData: (data: RuleSubscribeOption) => IPromise; /** * 按钮控制 */ btnControls?: RulePanelBtnControlsOption>; /** * 获取订阅链接的数据信息 */ getSubscribeInfo: (url: string) => Promise<{ /** * 订阅的数据 */ data: RuleSubscribeOption | null; /** * 错误信息,如果data为空,则msg有错误信息 */ msg: string; }>; }; /** * 规则面版的配置 */ type RulePanelRuleOption = { /** * 获取所有数据 */ data: () => IPromise; /** * 获取添加的数据 */ getAddData: () => IPromise; /** * 获取单个规则当前的数据 * @param data */ getData: (data: T) => IPromise; /** * 获取单个数据的名字,用户显示在列表中 * @param data */ getDataItemName: (data: T) => IPromise; /** * 更新单个规则 * @param data */ updateData: (data: T) => IPromise; /** * 删除单个规则,用于删除getAddData创建时的数据 * @param data */ deleteData: (data: T) => IPromise; /** * 按钮控制 */ btnControls?: RulePanelBtnControlsOption; }; /** * 规则面版的各种规则配置 */ type RulePanelAnyOption = RulePanelRuleOption | RulePanelSubscribeOption; class RulePanelView { option: RulePanelOption; constructor(option: RulePanelOption) { this.option = option; } /** * 显示视图 * @param filterCallBack 返回值为false隐藏,true则不隐藏(不处理) */ async showView(filterCallBack?: (data: T | RuleSubscribeOption) => boolean) { const that = this; const contentConfigList = this.option.contentConfig; contentConfigList.forEach((config) => { (config as any as PopsPanelContentConfig).views = []; config.headerTitle = config.headerTitle || config.title; if (config.subscribe?.enable) { // 存在订阅按钮 config.headerTitle = config.headerTitle + /*html*/ ` `; } let originCallBack = config.clickCallback; config.clickCallback = async (event, $panelRightHeader, $panelRightContainer) => { if (typeof originCallBack === "function") { originCallBack(event, $panelRightContainer, $panelRightContainer); } if (config.subscribe && config.subscribe.enable) { // 存在订阅按钮 let $subscribe = $panelRightHeader.querySelector(".subscribe-btn")!; const subscribeOption = config.subscribe!; DOMUtils.on($subscribe, "click", async (event) => { DOMUtils.preventEvent(event); // 订阅 await subscribeOption?.callback?.(); // 进入订阅内部菜单 await this.enterDeepMenu( $panelRightContainer, subscribeOption?.headerTitle || subscribeOption?.title || "订阅", async ($elInfo) => { /** 订阅容器 */ const $subscribeRightContainer = $elInfo.$rightRuleContainer; const subscribeCreateViewElementInfo = await this.createButtonControls( $subscribeRightContainer, $subscribeRightContainer, subscribeOption, async () => { let $prompt = pops.prompt({ title: { text: "添加订阅", position: "center", }, content: { text: "", focus: true, placeholder: "输入URL", }, btn: { cancel: { enable: false, }, ok: { enable: true, text: "下一步", async callback(eventDetails) { let subscribeUrl = DOMUtils.val($promptInput).trim(); if (subscribeUrl === "") { return; } log.info(`订阅:` + subscribeUrl); let $loading = Qmsg.loading("正在获取订阅信息..."); try { let subscribeInfoResult = await subscribeOption?.getSubscribeInfo(subscribeUrl); if (subscribeInfoResult.data) { eventDetails.close(); let subscribeInfo = subscribeInfoResult.data; let title = subscribeInfo.data.title || subscribeInfo.subscribeData.title || subscribeInfo.data.url; let $subscribeNetworkAddDialog = pops.alert({ title: { text: "添加订阅", position: "center", }, content: { text: /*html*/ ` `, html: true, }, btn: { ok: { text: "添加", type: "subscribe", callback: async (eventDetails) => { let addFlag = await subscribeOption.addData(subscribeInfo); if (!addFlag) { Qmsg.error("该订阅已存在", { consoleLogContent: true, }); } that.updateRuleContaienrElement( subscribeOption, subscribeOption, $elInfo.$section ); eventDetails.close(); }, }, }, drag: true, mask: { enable: true, }, width: PanelUISize.setting.width, height: "auto", style: /*css*/ ` .pops button[data-type="subscribe"]{ --button-color: #ffffff; --button-bd-color: #67b279; --button-bg-color: #67b279; } .pops button[data-type="subscribe"]:hover{ --button-color: #ffffff; --button-bd-color:rgb(91, 159, 107);; --button-bg-color:rgb(91, 159, 107);; } .pops-alert-content{ display: flex; flex-direction: column; gap: 4px; padding: 20px; } .pops-alert-content a { color: #3d3d3d; } .pops-alert-content > div{ display: flex; } .pops-alert-content > div > span:first-child{ white-space: nowrap; } .subscribe-network-title input{ width: 100%; flex: 1 1 auto; line-height: 1.3; outline: none; text-overflow: ellipsis; border-radius: 8px; border: 1px solid #e4e4e4; background-color: #f6f6f6; padding: 16px 16px 16px 16px; font-size: 16px; } .subscribe-network-title input:focus{ } `, }); const $subscribeNetworkAddDialog_title_input = $subscribeNetworkAddDialog.$shadowRoot.querySelector( ".subscribe-network-title input" )!; const $subscribeNetworkAddDialog_count = $subscribeNetworkAddDialog.$shadowRoot.querySelector( ".subscribe-network-data-count" )!; const $subscribeNetworkAddDialog_homeUrl = $subscribeNetworkAddDialog.$shadowRoot.querySelector( ".subscribe-network-home-url" )!; const $subscribeNetworkAddDialog_url = $subscribeNetworkAddDialog.$shadowRoot.querySelector( ".subscribe-network-url" )!; const $subscribeNetworkAddDialog_version = $subscribeNetworkAddDialog.$shadowRoot.querySelector( ".subscribe-network-version" )!; const $subscribeNetworkAddDialog_lastModified = $subscribeNetworkAddDialog.$shadowRoot.querySelector( ".subscribe-network-last-modified" )!; DOMUtils.val($subscribeNetworkAddDialog_title_input, title); DOMUtils.on($subscribeNetworkAddDialog_title_input, ["input", "propertychange"], () => { const inputValue = DOMUtils.val($subscribeNetworkAddDialog_title_input); subscribeInfo.data.title = inputValue === "" ? void 0 : inputValue; }); DOMUtils.html( $subscribeNetworkAddDialog_count, /*html*/ ` 规则数量: ${subscribeInfo.subscribeData.ruleData.length} ` ); // 主页地址 if (typeof subscribeInfo.subscribeData.homePage === "string") { DOMUtils.html( $subscribeNetworkAddDialog_homeUrl, /*html*/ ` 主页: ${subscribeInfo.subscribeData.homePage} ` ); } else { $subscribeNetworkAddDialog_homeUrl.remove(); } // 链接 DOMUtils.html( $subscribeNetworkAddDialog_url, /*html*/ ` URL: ${subscribeInfo.data.url} ` ); // 版本 if (subscribeInfo.subscribeData.version != null) { DOMUtils.html( $subscribeNetworkAddDialog_version, /*html*/ ` 版本: ${subscribeInfo.subscribeData.version} ` ); } else { $subscribeNetworkAddDialog_version.remove(); } if (subscribeInfo.subscribeData.lastModified != null) { DOMUtils.html( $subscribeNetworkAddDialog_lastModified, /*html*/ ` 更新时间: ${utils.formatTime(subscribeInfo.subscribeData.lastModified)} ` ); } else { $subscribeNetworkAddDialog_lastModified.remove(); } } else { Qmsg.error(subscribeInfoResult.msg, { consoleLogContent: true, }); } } catch (error: any) { Qmsg.error(error.toString(), { consoleLogContent: true, }); } finally { $loading.close(); } }, }, }, drag: true, mask: { enable: true, }, width: PanelUISize.info.width, height: "auto", }); let $promptInput = $prompt.$shadowRoot.querySelector("input")!; let $promptOk = $prompt.$shadowRoot.querySelector(".pops-prompt-btn-ok ")!; // 添加输入监听 DOMUtils.on($promptInput, ["input", "propertychange"], () => { let promptValue = DOMUtils.val($promptInput); if (promptValue === "") { DOMUtils.attr($promptOk, "disabled", "true"); } else { DOMUtils.removeAttr($promptOk, "disabled"); } }); DOMUtils.onKeyboard($promptInput, "keydown", (keyName, keyValue, otherCodeList, event) => { if (keyName === "Enter" && otherCodeList.length === 0) { DOMUtils.preventEvent(event); // 添加 DOMUtils.emit($promptOk, "click"); } }); // 触发input事件 DOMUtils.emit($promptInput, "input"); } ); // 渲染订阅列表 subscribeCreateViewElementInfo.execFilter(true); }, () => { // 触发渲染更新 this.updateRuleContaienrElement(config.ruleOption, subscribeOption, $panelRightContainer); } ); }); } const ruleCreateViewElementInfo = await this.createButtonControls( $panelRightContainer, $panelRightContainer, config.ruleOption, async () => { this.showEditView( config.ruleOption, void 0, false, await config.ruleOption.getAddData(), $panelRightContainer ); } ); await ruleCreateViewElementInfo.execFilter(true); }; }); pops.panel({ title: { text: typeof this.option.title === "function" ? this.option.title() : this.option.title, position: "center", }, content: contentConfigList as any as PopsPanelContentConfig[], btn: { close: { enable: true, callback(evtConfig) { evtConfig.close(); }, }, }, drag: true, mask: { enable: true, clickEvent: { toClose: false, }, }, class: this.option.className || "rule-panel-view", width: PanelUISize.settingBig.width, height: PanelUISize.settingBig.height, style: /*css*/ ` ${this.option.style || ""} .pops button[data-type="subscribe"]{ --button-color: #ffffff; --button-bd-color: #67b279; --button-bg-color: #67b279; } .pops button[data-type="subscribe"]:hover{ --button-color: #ffffff; --button-bd-color:rgb(91, 159, 107);; --button-bg-color:rgb(91, 159, 107);; } section.pops-panel-container .pops-panel-container-header-ul li:has(.subscribe-btn){ justify-content: space-between !important; } section.pops-panel-container ul li.rule-controls{ justify-content: flex-start; overflow-x: auto; } section.pops-panel-container ul:has(>.rule-view-container){ overflow: hidden; display: flex; flex-direction: column; margin: var(--pops-panel-forms-container-item-margin-top-bottom) var(--pops-panel-forms-margin-left-right); gap: var(--pops-panel-forms-container-item-margin-top-bottom); } .rule-view-container{ margin: 0; margin-top: 0; overflow: auto; background: #ffffff; border-radius: var(--pops-panel-forms-container-item-border-radius); padding: 5px 10px; position: relative; flex: 1; } .rule-view-container:empty{ display: none; } .rule-item{ display: flex; align-items: center; line-height: normal; font-size: 16px; padding: 4px 8px; gap: 8px; } .rule-name{ flex: 1; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; } .rule-controls{ display: flex; align-items: center; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; gap: 8px; padding: 0px; } .rule-controls button{ margin: 0; } .rule-controls-enable{ } .rule-controls-edit{ } .rule-controls-delete{ } .rule-controls-edit, .rule-controls-delete{ width: 16px; height: 16px; cursor: pointer; } section.pops-panel-container > ul li:not(.pops-panel-forms-container-item){ margin: 0; } .rule-view-search-container{ display: flex; align-items: center; gap: 4px; padding: 4px 8px; } .rule-view-search-container .pops-panel-select{ min-width: fit-content; max-width: 60px; } .rule-view-search-container .pops-panel-select{ flex: 0; } .rule-view-search-container .pops-panel-input{ flex: 1; } .rule-view-search-container .pops-panel-select select{ width: 100%; min-width: auto; } .rule-view-search-container .pops-panel-input{ width: 100%; } @media screen and (max-width: 600px) { .pops[type-value="panel"] section.pops-panel-container .rule-view-search-container .pops-panel-select select{ min-width: auto !important; max-width: 50px !important; } } `, darkStyle: /*css*/ ` .rule-view-container{ background: #262626; } `, }); } /** * 进入深层菜单 * * 隐藏上一层的
,添加本层的
* @param $el 当前菜单的元素或
* @param headerTitle 标题 * @param quiteDeepMenuCallBack 返回上一层回调,一般用于触发外部的渲染更新 */ async enterDeepMenu( $el: HTMLElement, headerTitle: string, enterRender: ($elInfo: { $section: HTMLElement; $headerContainer: HTMLElement; $arrowLeft: HTMLElement; $rightRuleContainer: HTMLElement; }) => IPromise, quiteDeepMenuCallBack: () => void ) { // 动画配置 /** 前一个菜单元素 */ const $currentSection = $el.closest("section")!; const $deepMenuSection = DOMUtils.createElement("section", { className: "pops-panel-container pops-panel-deepMenu-container", innerHTML: /*html*/ `
  • ${pops.config.iconSVG.arrowLeft}

    ${headerTitle}

    `, }); /** 标题容器 */ const $headerContainer = $deepMenuSection.querySelector(".pops-panel-deepMenu-container-header-ul")!; /** 返回上一层按钮 */ const $arrowLeft = $deepMenuSection.querySelector(".pops-panel-deepMenu-container-left-arrow-icon")!; /** 右侧规则容器 */ const $rightRuleContainer = $deepMenuSection.querySelector(".pops-panel-container-main-ul")!; const elInfo = { $section: $deepMenuSection, $headerContainer, $arrowLeft, $rightRuleContainer, }; const switchAnim = pops.fn.Animation.createSwitchElementWithAnimation($currentSection, $deepMenuSection, { enterToAddElementCallback: async () => { // 添加新的 DOMUtils.after($currentSection, $deepMenuSection); DOMUtils.on($arrowLeft, "click", async (event) => { DOMUtils.preventEvent(event); // 回退 // 返回动画 await switchAnim.exit(); }); await enterRender(elInfo); }, exitToRemoveElementCallback() { quiteDeepMenuCallBack(); }, }); await switchAnim.enter(); return { $el: elInfo, switchAnim, }; } /** * 创建各个按钮元素 * @param $controlsParent 控制按钮的父元素 * @param $rightContainer 右侧容器 * @param option 配置 * @param addButtonOnClickCallBack 点击添加按钮的回调 */ async createButtonControls( $controlsParent: HTMLElement, $rightContainer: HTMLElement, option: RulePanelAnyOption, addButtonOnClickCallBack?: () => IPromise ) { const btnControlsOption = option.btnControls; /** 控制按钮 */ const $ruleControls = DOMUtils.createElement("li", { className: "rule-controls", }); DOMUtils.append($controlsParent, $ruleControls); /** 添加按钮 */ let $ruleControlAdd: HTMLElement | null = null; if (btnControlsOption?.add?.enable) { $ruleControlAdd = DOMUtils.createElement( "button", { className: "rule-control-add", innerHTML: /*html*/ `添加`, }, { type: "button", "data-type": "primary", "data-has-icon": "false", "data-righticon": "false", } ); DOMUtils.on($ruleControlAdd, "click", async (event) => { DOMUtils.preventEvent(event); // 添加 const result = await option.btnControls?.add?.callback?.call(this, { event, $section: $rightContainer, }); if (typeof result === "boolean" && !result) { return; } await addButtonOnClickCallBack?.(); }); DOMUtils.append($ruleControls, $ruleControlAdd); } /** 清空所有按钮 */ let $ruleControlClearAll: HTMLElement | null = null; if (btnControlsOption?.clearAll?.enable) { $ruleControlClearAll = DOMUtils.createElement( "button", { className: "rule-control-clear-all", innerHTML: /*html*/ `清空所有`, }, { type: "button", "data-type": "xiaomi-primary", "data-has-icon": "false", "data-righticon": "false", } ); DOMUtils.on($ruleControlClearAll, "click", (event) => { DOMUtils.preventEvent(event); // 清空所有 let $askDialog = pops.confirm({ title: { text: "提示", position: "center", }, content: { text: "确定清空所有的数据?", html: false, }, btn: { ok: { enable: true, callback: async () => { log.success("清空所有"); let result = await btnControlsOption?.clearAll?.callback?.(); if (typeof result === "boolean" && !result) { return; } let data = await option?.data(); if (!data || data.length) { Qmsg.error("清理失败"); return; } else { Qmsg.success("清理成功"); } await this.updateDeleteAllBtnText(option, $ruleControls); this.clearContent($rightContainer); $askDialog.close(); }, }, cancel: { text: "取消", enable: true, }, }, drag: true, mask: { enable: true, }, width: "300px", height: "200px", }); }); DOMUtils.append($ruleControls, $ruleControlClearAll); } /** 导入按钮 */ let $ruleControlImport: HTMLElement | null = null; if (btnControlsOption?.import?.enable) { $ruleControlImport = DOMUtils.createElement( "button", { className: "rule-control-import", innerHTML: /*html*/ `导入`, }, { type: "button", "data-type": "default", "data-has-icon": "false", "data-righticon": "false", } ); DOMUtils.on($ruleControlImport, "click", async (event) => { DOMUtils.preventEvent(event); // 导入 let result = await btnControlsOption?.import?.callback?.(() => { this.updateRuleContaienrElement(option, void 0, $rightContainer); }); if (typeof result === "boolean" && !result) { return; } }); DOMUtils.append($ruleControls, $ruleControlImport); } /** 导出按钮 */ let $ruleControlExport: HTMLElement | null = null; if (btnControlsOption?.export?.enable) { $ruleControlExport = DOMUtils.createElement( "button", { className: "rule-control-export", innerHTML: /*html*/ `导出`, }, { type: "button", "data-type": "default", "data-has-icon": "false", "data-righticon": "false", } ); DOMUtils.on($ruleControlExport, "click", async (event) => { DOMUtils.preventEvent(event); // 导出 let result = await btnControlsOption?.export?.callback?.({ event, }); if (typeof result === "boolean" && !result) { return; } }); DOMUtils.append($ruleControls, $ruleControlExport); } /** 规则容器 */ const $ruleContainer = DOMUtils.createElement("div", { className: "rule-view-container", innerHTML: /*html*/ ``, }); // 搜索容器 const $searchContainer = DOMUtils.createElement("div", { className: "rule-view-search-container", innerHTML: /*html*/ `
    `, }); const $externalSelect = $searchContainer.querySelector( ".pops-panel-select .select-rule-status" )!; const $ruleValueSelect = $searchContainer.querySelector( ".pops-panel-select .select-rule-value" )!; const $searchInput = $searchContainer.querySelector(".pops-panel-input input")!; // 规则前置条件选项 let externalSelectInfo: RuleViewSearchExternalOption | null = null; // 规则搜索条件选项 let ruleValueSelectInfo: RuleViewSearchRuleValueOption | null = null; if (Array.isArray(option.btnControls?.filter?.option)) { let defaultSelectedIndex = 0; DOMUtils.append( $externalSelect, option?.btnControls?.filter?.option.map((option, index) => { const $option = DOMUtils.createElement("option", { innerText: option.name, }); if (option.isDefaultSelected) { defaultSelectedIndex = index; } Reflect.set($option, "data-value", option); return $option; }) ); $externalSelect.selectedIndex = defaultSelectedIndex; } if (Array.isArray(option.btnControls?.filter?.inputOption)) { let defaultSelectedIndex = 0; DOMUtils.append( $ruleValueSelect, option.btnControls?.filter?.inputOption.map((option, index) => { const $option = DOMUtils.createElement("option", { innerText: option.name, }); if (option.isDefaultSelected) { defaultSelectedIndex = index; } Reflect.set($option, "data-value", option); return $option; }) ); $ruleValueSelect.selectedIndex = defaultSelectedIndex; } DOMUtils.on($externalSelect, "change", async () => { const $isSelectedElement = $externalSelect[$externalSelect.selectedIndex] as HTMLOptionElement; const selectInfo = Reflect.get($isSelectedElement, "data-value") as RuleViewSearchExternalOption; if (typeof selectInfo?.selectedCallBack === "function") { selectInfo.selectedCallBack(selectInfo); } externalSelectInfo = selectInfo; await execFilter(true); }); DOMUtils.on($ruleValueSelect, "change", async () => { const $isSelectedElement = $ruleValueSelect[$ruleValueSelect.selectedIndex] as HTMLOptionElement; const selectInfo = Reflect.get($isSelectedElement, "data-value") as RuleViewSearchRuleValueOption; if (typeof selectInfo?.selectedCallBack === "function") { selectInfo.selectedCallBack(selectInfo); } ruleValueSelectInfo = selectInfo; await execFilter(true); }); DOMUtils.onInput( $searchInput, utils.debounce(async () => { await execFilter(false); }) ); /** * 更新前置条件:选中的选项数据 */ const updateSelectData = () => { // 规则的启用、未启用状态选项 const $externalSelected = $externalSelect[$externalSelect.selectedIndex] as HTMLOptionElement | null; if ($externalSelected) { externalSelectInfo = Reflect.get($externalSelected, "data-value"); } // 搜索条件选项 const $ruleValueSelected = $ruleValueSelect[$ruleValueSelect.selectedIndex] as HTMLOptionElement | null; if ($ruleValueSelected) { ruleValueSelectInfo = Reflect.get($ruleValueSelected, "data-value"); } // 更新placeholder let conditionPlaceHolder: string[] = []; // if (typeof externalSelectInfo?.name === "string" && externalSelectInfo.name.trim() !== "") { // if (typeof externalSelectInfo.value === "string" && externalSelectInfo.value.trim() === "") { // // 选项值为空 // } else { // conditionPlaceHolder.push(externalSelectInfo.name); // } // } if (typeof ruleValueSelectInfo?.name === "string" && ruleValueSelectInfo.name.trim() !== "") { conditionPlaceHolder.push(ruleValueSelectInfo.name); } $searchInput.placeholder = `请输入 ${conditionPlaceHolder.join("/")} 进行搜索`; }; /** * @param isUpdateSelectData 更新选中的选项数据 */ const execFilter = async (isUpdateSelectData: boolean) => { // 清空旧的 this.clearContent($rightContainer); // 更新选项 if (isUpdateSelectData) { updateSelectData(); } const allData = await option.data(); const filteredData: T[] = []; const searchText = DOMUtils.val($searchInput); for (let index = 0; index < allData.length; index++) { const item: any = allData[index]; // 先进行前置条件过滤 if (externalSelectInfo) { const externalFilterResult = await externalSelectInfo?.filterCallBack?.(item); if (typeof externalFilterResult === "boolean" && !externalFilterResult) { // 不需要 continue; } } if (ruleValueSelectInfo) { let flag = true; if (searchText === "") { // 空,需要,不过滤 flag = true; } else { flag = false; } if (!flag) { flag = await ruleValueSelectInfo?.filterCallBack?.(item, searchText); } if (!flag) { // 不需要 continue; } } filteredData.push(item); } // 添加已被过滤的 await this.addRuleElement(option, void 0, $rightContainer, filteredData); }; DOMUtils.append($rightContainer, $searchContainer, $ruleContainer); // 更新选项选中 updateSelectData(); return { $ruleContainer, $ruleControls, $ruleControlAdd, $ruleControlClearAll, $ruleControlImport, $ruleControlExport, execFilter, updateSelectData, }; } /** * 解析弹窗内的各个元素 * @param $el */ parseViewElement($el: ShadowRoot | HTMLElement) { let $container = $el.querySelector(".rule-view-container")!; let $deleteBtn = $el.querySelector(".rule-control-clear-all")!; return { /** 容器 */ $container: $container, /** 左下角的清空按钮 */ $deleteBtn: $deleteBtn, }; } /** * 解析规则元素 * @param $ruleItem 规则元素 */ parseRuleElement($ruleItem: ShadowRoot | HTMLElement) { let $enable = $ruleItem.querySelector(".rule-controls-enable")!; let $enableSwitch = $enable.querySelector(".pops-panel-switch")!; let $enableSwitchInput = $enable.querySelector(".pops-panel-switch__input")!; let $enableSwitchCore = $enable.querySelector(".pops-panel-switch__core"); /** 编辑按钮 */ let $edit = $ruleItem.querySelector(".rule-controls-edit")!; /** 删除按钮 */ let $delete = $ruleItem.querySelector(".rule-controls-delete")!; return { /** 启用开关 */ $enable, /** 启用开关的container */ $enableSwitch: $enableSwitch, /** 启用开关的input */ $enableSwitchInput: $enableSwitchInput, /** 启用开关的core */ $enableSwitchCore: $enableSwitchCore, /** 编辑按钮 */ $edit: $edit, /** 删除按钮 */ $delete: $delete, /** 存储在元素上的数据 */ data: Reflect.get($ruleItem, "data-rule") as T, }; } /** * 创建规则元素 * @param option 规则配置 * @param subscribeOption 订阅配置 * @param data 规则数据 * @param $el 元素 */ async createRuleElement( option: RulePanelAnyOption, subscribeOption: RulePanelSubscribeOption | undefined, data: T | RuleSubscribeOption, $el: ShadowRoot | HTMLElement ) { let ruleData = data as T & RuleSubscribeOption; let name = await option.getDataItemName(ruleData); let $ruleItem = DOMUtils.createElement("div", { className: "rule-item", innerHTML: /*html*/ `
    ${name}
    ${pops.config.iconSVG.edit}
    ${pops.config.iconSVG.delete}
    `, }); Reflect.set($ruleItem, "data-rule", ruleData); /** 开关切换的className */ let switchCheckedClassName = "pops-panel-switch-is-checked"; const { $enable, $enableSwitch, $enableSwitchCore, $enableSwitchInput, $delete, $edit } = this.parseRuleElement($ruleItem); if (option.btnControls?.ruleEnable?.enable) { // 给switch添加点击事件 DOMUtils.on($enableSwitchCore, "click", async () => { let isChecked = false; if ($enableSwitch.classList.contains(switchCheckedClassName)) { // 关 $enableSwitch.classList.remove(switchCheckedClassName); isChecked = false; } else { // 开 $enableSwitch.classList.add(switchCheckedClassName); isChecked = true; } $enableSwitchInput.checked = isChecked; await option?.btnControls?.ruleEnable?.callback(ruleData, isChecked); // 开启按钮自动执行更新订阅 if (isChecked && subscribeOption) { // 更新订阅 let subscribeData = data as RuleSubscribeOption; let subscribeInfo = await subscribeOption.getSubscribeInfo(subscribeData.data.url)!; if (subscribeInfo.data) { let subscribeNewItem = subscribeInfo.data; // 同步uuid subscribeNewItem.uuid = subscribeData.uuid; // 同步data subscribeNewItem.data = subscribeData.data; // 同步更新时间 subscribeNewItem.data.latestUpdateTime = Date.now(); // 更新规则 await subscribeOption.updateData(subscribeNewItem); } else { subscribeData.data.updateFailedTime = Date.now(); // 更新规则 await subscribeOption.updateData(subscribeData); log.error(subscribeData); Qmsg.error(subscribeInfo.msg, { consoleLogContent: true }); } // 更新容器信息 await this.updateRuleContaienrElement(option, subscribeOption, $el); } }); if (await option?.btnControls?.ruleEnable?.getEnable(ruleData)) { // 开 $enableSwitch.classList.add(switchCheckedClassName); } } else { $enable.remove(); } if (option?.btnControls?.ruleEdit?.enable) { // 给编辑按钮添加点击事件 DOMUtils.on($edit, "click", (event) => { DOMUtils.preventEvent(event); if (typeof option.btnControls?.ruleEdit?.callback === "function") { let result = option.btnControls?.ruleEdit?.callback({ context: this, event, // @ts-ignore option, // @ts-ignore subscribeOption, // @ts-ignore ruleData, $section: $el as HTMLElement, $ruleItem: $ruleItem, enterDeepMenu: async (deepMenuOption) => { await this.enterDeepMenu( $el as HTMLElement, deepMenuOption.headerTitle || "", async ($elInfo) => { // 二级菜单容器 const $deepMenuRightContainer = $elInfo.$rightRuleContainer; const deepMenuCreateViewElementInfo = await this.createButtonControls( $deepMenuRightContainer, $elInfo.$rightRuleContainer, // @ts-expect-error deepMenuOption, void 0 ); // 渲染规则列表 await deepMenuCreateViewElementInfo.execFilter(true); }, () => { // 触发渲染更新 this.updateRuleContaienrElement(option, subscribeOption, $el); } ); }, }); if (typeof result === "boolean" && !result) { return; } } this.showEditView(option, subscribeOption, true, ruleData, $el, $ruleItem, (newData) => { // @ts-ignore ruleData = null; // @ts-ignore ruleData = newData; }); }); } else { $edit.remove(); } if (option?.btnControls?.ruleDelete?.enable) { // 给删除按钮添加点击事件 DOMUtils.on($delete, "click", (event) => { DOMUtils.preventEvent(event); let $askDialog = pops.confirm({ title: { text: "提示", position: "center", }, content: { text: "确定删除该条数据?", html: false, }, btn: { ok: { enable: true, callback: async () => { log.success("删除数据"); let flag = await option?.btnControls?.ruleDelete?.deleteCallBack(ruleData); if (flag) { Qmsg.success("成功删除该数据"); // 移除该条元素 $ruleItem.remove(); // 更新左下角的删除按钮文字 await this.updateDeleteAllBtnText(option, $el); $askDialog.close(); } else { Qmsg.error("删除该数据失败"); } }, }, cancel: { text: "取消", enable: true, }, }, drag: true, mask: { enable: true, }, width: "300px", height: "200px", }); }); } else { $delete.remove(); } return $ruleItem; } /** * 添加一个规则元素 * @param option 配置 * @param subscribeOption 订阅的配置,如果有那么这就是渲染订阅的数据页面 * @param $el 弹窗的元素 * @param data 规则的数据 * @param addCallBack 添加元素后的回调 * @returns 返回添加的元素 */ async addRuleElement( option: RulePanelAnyOption, subscribeOption: RulePanelSubscribeOption | undefined, $el: ShadowRoot | HTMLElement, data: T | T[] | RuleSubscribeOption | RuleSubscribeOption[], addCallBack?: (data: T | RuleSubscribeOption, $rule: HTMLElement) => void ) { const { $container } = this.parseViewElement($el); const $ruleItemList: HTMLElement[] = []; // 添加到页面中 const iteratorData = Array.isArray(data) ? data : [data]; for (let index = 0; index < iteratorData.length; index++) { const item = iteratorData[index]; const $item = await this.createRuleElement(option, subscribeOption, item, $el); addCallBack?.(item, $item); $ruleItemList.push($item); } DOMUtils.append($container, $ruleItemList); await this.updateDeleteAllBtnText(option, $el); return $ruleItemList; } /** * 更新弹窗内容的元素 * @param option 规则的配置 * @param subscribeOption 订阅的配置 * @param $el 弹窗的元素 */ async updateRuleContaienrElement( option: RulePanelAnyOption, subscribeOption: RulePanelSubscribeOption | undefined, $el: ShadowRoot | HTMLElement ) { this.clearContent($el); const data = await option.data(); await this.addRuleElement(option, subscribeOption, $el, data); await this.updateDeleteAllBtnText(option, $el); } /** * 更新规则元素 * @param option 规则的配置 * @param subscribeOption 订阅的配置 * @param data 规则的数据 * @param $oldRule 旧的规则元素 * @param $el 弹窗的元素 */ async updateRuleItemElement( option: RulePanelAnyOption, subscribeOption: RulePanelSubscribeOption | undefined, data: T | RuleSubscribeOption, $oldRule: HTMLElement, $el: ShadowRoot | HTMLElement ) { const $newRule = await this.createRuleElement(option, subscribeOption, data, $el); $oldRule.after($newRule); $oldRule.remove(); return $newRule; } /** * 清空内容 * @param $el 弹窗的元素 */ clearContent($el: ShadowRoot | HTMLElement) { const { $container } = this.parseViewElement($el); DOMUtils.html($container, ""); } /** * 设置删除按钮的文字 * @param $el 弹窗的元素 * @param text 按钮的文字 * @param [isHTML=false] 是否是html */ setDeleteBtnText($el: ShadowRoot | HTMLElement, text: string, isHTML: boolean = false) { const { $deleteBtn } = this.parseViewElement($el); if (isHTML) { DOMUtils.html($deleteBtn, text); } else { DOMUtils.text($deleteBtn, text); } } /** * 更新【清空所有】的按钮的文字 * @param option 规则的配置 * @param $el 弹窗的元素 */ async updateDeleteAllBtnText(option: RulePanelAnyOption, $el: ShadowRoot | HTMLElement) { let data = await option.data(); let dataCount = data.length; let text = `清空所有`; if (dataCount !== 0) { text += `(${dataCount})`; } this.setDeleteBtnText($el, text); } /** * 显示编辑视图 * @param option 规则的配置 * @param subscribeOption 订阅的配置 * @param isEdit 是否是编辑状态 * @param editData 编辑的数据 * @param $parent 关闭弹窗后对ShadowRoot进行操作 * @param $ruleItem 关闭弹窗后对规则行进行更新数据 * @param updateDataCallBack 关闭添加/编辑弹窗的回调(不更新数据) * @param submitCallBack 添加/修改提交的回调 */ showEditView( option: RulePanelAnyOption, subscribeOption: RulePanelSubscribeOption | undefined, isEdit: boolean, editData: T | RuleSubscribeOption, $parent?: ShadowRoot | HTMLElement, $ruleItem?: HTMLElement, updateDataCallBack?: (data: T | RuleSubscribeOption) => void, submitCallBack?: (data: T | RuleSubscribeOption) => void ) { /** * 弹窗关闭的回调函数 * @param isSubmit 是否是提交 */ let dialogCloseCallBack = async (isSubmit: boolean) => { if (isSubmit) { if (typeof submitCallBack === "function") { let newData = await option.getData(editData as T & RuleSubscribeOption); submitCallBack(newData); } } else { if (!isEdit) { // 添加规则,关闭时清理掉规则 await option.deleteData(editData as T & RuleSubscribeOption); } if (typeof updateDataCallBack === "function") { let newData = await option.getData(editData as T & RuleSubscribeOption); updateDataCallBack(newData); } } }; let editView = new RuleEditView>({ title: isEdit ? "编辑" : "添加", data: () => { return editData!; }, dialogCloseCallBack: dialogCloseCallBack, getView: async (data) => { return await option.btnControls?.ruleEdit?.getView?.(data as T & RuleSubscribeOption, isEdit)!; }, btn: { ok: { enable: true, text: isEdit ? "修改" : "添加", }, cancel: { callback: async (detail) => { detail.close(); await dialogCloseCallBack(false); }, }, close: { callback: async (detail) => { detail.close(); await dialogCloseCallBack(false); }, }, }, onsubmit: async ($form, data) => { const result = await option?.btnControls?.ruleEdit?.onsubmit?.( $form, isEdit, data as T & RuleSubscribeOption )!; if (result.success) { if (isEdit) { Qmsg.success("修改成功"); // 当前是编辑规则 // 给外面的弹窗更新元素 if ($parent) { await this.updateRuleItemElement(option, subscribeOption, result.data, $ruleItem!, $parent); } } else { // 当前是添加规则 // 给外面的弹窗添加元素 if ($parent) { await this.addRuleElement(option, subscribeOption, $parent, result.data); } } } else { // if (isEdit) { // Qmsg.error("修改失败"); // } if (isEdit) { log.error("修改失败"); } } return result; }, style: option?.btnControls?.ruleEdit?.style, width: option?.btnControls?.ruleEdit?.viewWidth, height: option?.btnControls?.ruleEdit?.viewHeight, }); editView.showView(); } } export { RulePanelView, type RulePanelAnyOption, type RulePanelBtnControlsOption, type RulePanelContentOption, type RulePanelOption, type RulePanelRuleOption, type RulePanelSubscribeOption, type RuleSubscribeOption, };