page.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020
  1. let nowFileName = "";
  2. let jsonInfo = {};
  3. let tabId = "";
  4. let nodeObj;
  5. let jslist;
  6. let cssList;
  7. let cssFileInfo = {}
  8. let oldFile; // 记录上一次点击的文件信息
  9. // 记录页卡信息
  10. // let pageTab = new PageTab();
  11. chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
  12. if (request.msgToPopup === "发送页面") {
  13. cssFileInfo = {}
  14. //初始化JS代码编辑器
  15. window.jsCode = new CodeEditor();
  16. jsCode.InitEditor($("#jscode")[0], false, false, [], false);
  17. jsCode.dataType = 3;
  18. InitTable();
  19. // 结构接收的信息
  20. let { jslist: _jslist, targe, csslist, tabId: _tabId, path } = request.data;
  21. jslist = _jslist;
  22. tabId = _tabId;
  23. cssList = csslist; // 页面的所有样式信息
  24. parseCodeToAST(
  25. jslist.map((item) => {
  26. return {
  27. code: item.data,
  28. name: item.fileName,
  29. };
  30. })
  31. );
  32. let data = window.findSelector();
  33. let id = $(targe).attr("id");
  34. let classList = $(targe).attr("class").split(" ");
  35. data = data.filter((m) => {
  36. let v = m.过滤器;
  37. if (v.includes(id)) return true;
  38. if (classList.length > 0) {
  39. for (const classitem of classList) {
  40. if (v.includes(classitem)) return true;
  41. }
  42. }
  43. });
  44. await attach();
  45. await getPathNodeInfo(path);
  46. let events = await findEvent();
  47. TabInstance.option({
  48. dataSource: data,
  49. });
  50. let cssJsonData = CssJsonData(jsonInfo)
  51. CSSTabInstance.option({
  52. dataSource: cssJsonData,
  53. });
  54. eventTableInstance.option({
  55. dataSource: translateEventData(events),
  56. });
  57. }
  58. });
  59. function InitTable() {
  60. window.TabInstance = $("#filterTable")
  61. .dxDataGrid({
  62. dataSource: [],
  63. keyExpr: "resource_detail_id",
  64. remoteOperations: false,
  65. searchPanel: {
  66. visible: true,
  67. highlightCaseSensitive: true,
  68. },
  69. groupPanel: { visible: false },
  70. grouping: {
  71. allowCollapsing: true,
  72. autoExpandAll: true,
  73. expandMode: "rowClick",
  74. },
  75. selection: {
  76. mode: "single",
  77. },
  78. allowColumnReordering: true,
  79. rowAlternationEnabled: true,
  80. showBorders: true,
  81. height: "100%",
  82. columns: [
  83. {
  84. dataField: "JS文件",
  85. dataType: "JS文件",
  86. groupIndex: 0
  87. },
  88. {
  89. dataField: "DOM操作分类",
  90. dataType: "DOM操作分类",
  91. },
  92. {
  93. dataField: "过滤器",
  94. caption: "过滤器",
  95. },
  96. {
  97. dataField: "所在位置",
  98. dataType: "所在位置",
  99. },
  100. ],
  101. onContentReady(e) { },
  102. onSelectionChanged: function (selectedItems) {
  103. let data = selectedItems.selectedRowsData[0];
  104. if (data) {
  105. oldFile = ""
  106. jsCode.dataType = 3;
  107. let ast = data.astJSON;
  108. if (data.JS文件 !== nowFileName) {
  109. nowFileName = data.JS文件;
  110. let code = window.fileList.find((o) => o.name === data.JS文件).code;
  111. //更改JS代码
  112. jsCode.SetCode(code);
  113. }
  114. //jscode定位
  115. let {
  116. defineNode: {
  117. iid,
  118. loc: { end, start },
  119. },
  120. } = data.source_data;
  121. {
  122. //移出所有高亮
  123. jsCode.RemoveAllHighlight();
  124. //设置高亮
  125. jsCode.CreateHighlight({
  126. startLine: start?.line,
  127. startColumn: start?.column + 1,
  128. endLine: end?.line,
  129. endColumn: end?.column + 1,
  130. });
  131. }
  132. }
  133. },
  134. })
  135. .dxDataGrid("instance");
  136. window.CSSTabInstance = $("#cssJson")
  137. .dxDataGrid({
  138. dataSource: [],
  139. keyExpr: "id",
  140. remoteOperations: false,
  141. searchPanel: {
  142. visible: true,
  143. highlightCaseSensitive: true,
  144. },
  145. selection: {
  146. mode: "single",
  147. },
  148. groupPanel: { visible: false },
  149. grouping: {
  150. allowCollapsing: true,
  151. autoExpandAll: true,
  152. expandMode: "rowClick",
  153. },
  154. allowColumnReordering: true,
  155. rowAlternationEnabled: true,
  156. showBorders: true,
  157. height: "100%",
  158. paging: {
  159. enabled: false,
  160. pageSize: 0,
  161. },
  162. columns: [
  163. {
  164. dataField: "文件",
  165. caption: "来源文件",
  166. groupCellTemplate: function (tdom, tdMsg) {
  167. let name = tdMsg.value;
  168. try {
  169. name = (name === "用户自定义" ? name : name.match(/\/([^\/]+\.css)(\?[^ ]*)?$/)[1])
  170. } catch {
  171. }
  172. tdom.text(name)
  173. },
  174. groupIndex: 0
  175. },
  176. {
  177. dataField: "分类",
  178. caption: "分类"
  179. },
  180. {
  181. dataField: "选择器",
  182. caption: "选择器"
  183. }, {
  184. dataField: "样式属性",
  185. caption: "样式属性",
  186. cellTemplate: function (tdom, tdMsg) {
  187. let styles = tdMsg.value;
  188. let html = styles.map(({ name, value }) => `<li>${name}: ${value}</li>`).join('')
  189. tdom.html(`<ul>${html}</ul>`)
  190. }
  191. },
  192. ],
  193. onSelectionChanged: async function (selectedItems) {
  194. let data = selectedItems.selectedRowsData[0];
  195. if (data) {
  196. if (data.文件) {
  197. let fileName = data.文件;
  198. let code = ""
  199. if (!cssFileInfo[fileName]) {
  200. code = fileName === "用户自定义" ? "" : await (await fetch(fileName)).text();
  201. cssFileInfo[fileName] = {
  202. code
  203. }
  204. } else {
  205. code = cssFileInfo[fileName].code
  206. }
  207. // 设置显示css
  208. jsCode.dataType = 2;
  209. if (oldFile !== fileName) {
  210. //更改JS代码
  211. jsCode.SetCode(code);
  212. }
  213. //移出所有高亮
  214. jsCode.RemoveAllHighlight();
  215. oldFile = fileName;
  216. // 定位使用
  217. let selectors = [];
  218. if (!cssFileInfo[fileName].selectors) {
  219. selectors = await cssCodeToAST(jsCode.GetCode());
  220. cssFileInfo[fileName].selectors = selectors
  221. } else {
  222. selectors = cssFileInfo[fileName].selectors
  223. }
  224. let location = selectors.find(a => a.name === data.选择器);
  225. if (location) {
  226. setTimeout(() => {
  227. let { loc: { start, end } } = location;
  228. //设置高亮
  229. jsCode.CreateHighlight({
  230. startLine: start?.line,
  231. startColumn: start?.column,
  232. endLine: end?.line,
  233. endColumn: end?.column,
  234. });
  235. }, 500);
  236. }
  237. }
  238. }
  239. },
  240. })
  241. .dxDataGrid("instance");
  242. window.eventTableInstance = $("#eventTable")
  243. .dxDataGrid({
  244. dataSource: [],
  245. keyExpr: "id",
  246. remoteOperations: false,
  247. searchPanel: {
  248. visible: true,
  249. highlightCaseSensitive: true,
  250. },
  251. selection: {
  252. mode: "single",
  253. },
  254. allowColumnReordering: true,
  255. rowAlternationEnabled: true,
  256. showBorders: true,
  257. paging: {
  258. enabled: false,
  259. pageSize: 0,
  260. },
  261. height: "100%",
  262. columns: [
  263. {
  264. dataField: "事件名称",
  265. caption: "事件名称",
  266. },
  267. {
  268. dataField: "绑定对象",
  269. caption: "绑定对象",
  270. },
  271. {
  272. dataField: "事件文件",
  273. caption: "事件文件",
  274. },
  275. ],
  276. })
  277. .dxDataGrid("instance");
  278. }
  279. function findPropRef(key, json) {
  280. let matchedCSSRules = json.matchedCSSRules;
  281. let retArr = [];
  282. for (const matchedCSSRule of matchedCSSRules) {
  283. let cssProperties = matchedCSSRule.rule.style.cssProperties;
  284. let cssProp = cssProperties.find((m) => m.name == key);
  285. if (!cssProp) continue;
  286. let ret = {
  287. 来源: "CSS",
  288. 选择器: matchedCSSRule.rule.selectorList.text,
  289. 选中选择器: matchedCSSRule.matchingSelectors[0],
  290. 属性值: cssProp.value,
  291. };
  292. retArr.push(ret);
  293. }
  294. return retArr;
  295. }
  296. async function cssCodeToAST(cssCode) {
  297. let curAst = csstree.parse(cssCode, {
  298. parseAtrulePrelude: false,
  299. parseRulePrelude: false,
  300. parseValue: false,
  301. positions: true,
  302. });
  303. let selectors = [];
  304. //https://github.com/csstree/csstree/blob/master/docs/parsing.md#parsesource-options
  305. await csstree.walk(curAst, (node) => {
  306. if (node.type === 'Rule')//node.type === 'Declaration'
  307. {
  308. let curSelector;
  309. if (node.prelude.children) {
  310. curSelector = node.prelude.children.map(child => child.raw);
  311. }
  312. else {
  313. curSelector = node.prelude.value;
  314. }
  315. let loc = node.prelude.loc;
  316. if (curSelector) selectors.push({
  317. //name: curSelector,//curSelector,
  318. name: curSelector.replaceAll("\r\n*", " ").replaceAll("\r\n", " "),
  319. sourceName: cssCode.substring(loc.start.offset, loc.end.offset),
  320. loc: loc,
  321. });
  322. }
  323. });
  324. return selectors;
  325. }
  326. async function getPathNodeInfo(path) {
  327. const doc = await getDocument();
  328. // const htmlInfo = doc.children.find((m) => m.localName === "html");
  329. let info = doc;
  330. for (const index of path) {
  331. info = getpath(info, index);
  332. }
  333. nodeObj = info;
  334. let nodeId = info.nodeId;
  335. let cssInfo = await getMatchedStylesForNode(nodeId);
  336. jsonInfo = cssInfo;
  337. function getpath(info, index) {
  338. let children = info.children;
  339. return children[index];
  340. }
  341. }
  342. async function getMatchedStylesForNode(nodeId) {
  343. let ret = await chrome.debugger.sendCommand(
  344. { tabId },
  345. "CSS.getMatchedStylesForNode",
  346. {
  347. nodeId,
  348. }
  349. );
  350. return ret;
  351. }
  352. async function getDocument() {
  353. const doc = await chrome.debugger.sendCommand({ tabId }, "DOM.getDocument", {
  354. pierce: true,
  355. depth: -1,
  356. });
  357. return doc.root;
  358. }
  359. async function attach() {
  360. // const targets = await chrome.debugger.getTargets();
  361. // let target = targets.find((m) => m.tabId == tabId);
  362. // if (target?.attached === true) {
  363. // } else {
  364. // await chrome.debugger.attach({ tabId }, "1.3");
  365. // }
  366. await chrome.debugger.attach({ tabId }, "1.3").catch(() => { });
  367. await chrome.debugger.sendCommand({ tabId }, "DOM.enable");
  368. await chrome.debugger.sendCommand({ tabId }, "CSS.enable");
  369. await chrome.debugger.sendCommand({ tabId }, "Debugger.enable");
  370. }
  371. async function findEvent() {
  372. let winObj = await chrome.debugger.sendCommand(
  373. { tabId },
  374. "Runtime.evaluate",
  375. {
  376. expression: "self",
  377. objectGroup: "",
  378. includeCommandLineAPI: false,
  379. silent: true,
  380. returnByValue: false,
  381. generatePreview: false,
  382. userGesture: false,
  383. awaitPromise: false,
  384. }
  385. );
  386. let winEvents = await chrome.debugger.sendCommand(
  387. { tabId },
  388. "DOMDebugger.getEventListeners",
  389. {
  390. objectId: winObj.result.objectId,
  391. }
  392. );
  393. let documentObj = await chrome.debugger.sendCommand(
  394. { tabId },
  395. "Runtime.evaluate",
  396. {
  397. expression: "document",
  398. objectGroup: "",
  399. includeCommandLineAPI: false,
  400. silent: true,
  401. returnByValue: false,
  402. generatePreview: false,
  403. userGesture: false,
  404. awaitPromise: false,
  405. }
  406. );
  407. let documentEvents = await chrome.debugger.sendCommand(
  408. { tabId },
  409. "DOMDebugger.getEventListeners",
  410. {
  411. objectId: documentObj.result.objectId,
  412. }
  413. );
  414. let selfObj = await chrome.debugger.sendCommand(
  415. { tabId },
  416. "DOM.resolveNode",
  417. {
  418. nodeId: nodeObj.nodeId,
  419. }
  420. );
  421. let selfEvents = await chrome.debugger.sendCommand(
  422. { tabId },
  423. "DOMDebugger.getEventListeners",
  424. {
  425. objectId: selfObj.object.objectId,
  426. }
  427. );
  428. for (const listener of winEvents.listeners) {
  429. await listenerGetFile(listener);
  430. }
  431. for (const listener of documentEvents.listeners) {
  432. await listenerGetFile(listener);
  433. }
  434. for (const listener of selfEvents.listeners) {
  435. await listenerGetFile(listener);
  436. }
  437. return {
  438. winEvents: winEvents.listeners,
  439. documentEvents: documentEvents.listeners,
  440. selfEvents: selfEvents.listeners,
  441. };
  442. }
  443. async function listenerGetFile(listener) {
  444. let ret = await chrome.debugger.sendCommand(
  445. { tabId },
  446. "Debugger.getScriptSource",
  447. {
  448. scriptId: listener.scriptId,
  449. }
  450. );
  451. let js = jslist.find((m) => m.data == ret.scriptSource);
  452. if (js) {
  453. js.scriptId = listener.scriptId;
  454. listener.fileName = js.fileName;
  455. listener.fileUrl = js.url;
  456. listener.fileData = js.data;
  457. }
  458. }
  459. function translateEventData(eventObj) {
  460. let { winEvents, documentEvents, selfEvents } = eventObj;
  461. let result = [];
  462. result.push(
  463. ...winEvents.map((even) => ({
  464. 事件名称: even.type,
  465. 绑定对象: "window",
  466. 事件文件: even.fileName,
  467. }))
  468. );
  469. result.push(
  470. ...documentEvents.map((even) => ({
  471. 事件名称: even.type,
  472. 绑定对象: "document",
  473. 事件文件: even.fileName,
  474. }))
  475. );
  476. result.push(
  477. ...selfEvents.map((even) => ({
  478. 事件名称: even.type,
  479. 绑定对象: "selft",
  480. 事件文件: even.fileName,
  481. }))
  482. );
  483. return result.map((a, index) => ({ ...a, id: index }));
  484. }
  485. window.onload = function () {
  486. InitTabOperation();
  487. };
  488. /**
  489. * 设置操作页卡功能
  490. */
  491. function InitTabOperation() {
  492. document.querySelector(".tabs").addEventListener("click", function (e) {
  493. let target = e.target;
  494. if (target.classList.contains("tab")) {
  495. showContent(target);
  496. }
  497. });
  498. }
  499. function showContent(tab) {
  500. let id = tab.dataset.id; // 获取点击的页卡ID
  501. // 获取选中的页卡元素
  502. let activeTab = document.querySelector(".tabs .active");
  503. if (activeTab !== null) {
  504. if (id === activeTab.dataset.id) return;
  505. activeTab.classList.remove("active");
  506. }
  507. // 获取选中的容器的元素
  508. let activeContent = document.querySelector(".tab-container .active");
  509. if (activeContent !== null) {
  510. if (activeContent.id === id) return;
  511. activeContent.classList.remove("active");
  512. }
  513. // 给新选中的页卡加入选中效果
  514. tab.classList.add("active");
  515. let curActiveContent = document.querySelector(`.tab-container #${id}`);
  516. if (curActiveContent !== null) {
  517. curActiveContent.classList.add("active");
  518. }
  519. }
  520. /**
  521. * 接收转换后有用的json数据
  522. * @param {} jsonData
  523. */
  524. function CssJsonData(jsonData) {
  525. let data = CssJsonRules.parseJson(jsonData);
  526. let { 伪类的样式, 内敛的样式, 匹配的样式, 继承的样式 } = data;
  527. let result = [];
  528. 伪类的样式.forEach((rule) => {
  529. let { matchs, pseudoType } = rule;
  530. matchs.forEach(match => {
  531. result.push({
  532. 文件: match.origin,
  533. 样式属性: match.style,
  534. 选择器: match.text,
  535. 分类: "伪类样式"
  536. })
  537. })
  538. });
  539. if (内敛的样式.length > 0) {
  540. result.push({
  541. 文件: "用户自定义",
  542. 样式属性: 内敛的样式,
  543. 选择器: "",
  544. 分类: "内敛样式"
  545. })
  546. }
  547. 匹配的样式.forEach((rule) => {
  548. result.push({
  549. 文件: rule.origin,
  550. 样式属性: rule.style,
  551. 选择器: rule.text,
  552. 分类: "外敛样式"
  553. })
  554. })
  555. 继承的样式.forEach((rule) => {
  556. result.push({
  557. 文件: rule.origin,
  558. 样式属性: rule.style,
  559. 选择器: rule.text,
  560. 分类: "继承样式"
  561. })
  562. })
  563. return result.map((a, index) => ({ ...a, id: index }));
  564. }
  565. /**
  566. * css基础权重规则
  567. */
  568. class CSSSRule {
  569. static calculateSpecificity(selector) {
  570. // 初始化权重
  571. let ids = 0;
  572. let classes = 0;
  573. let tags = 0;
  574. // 去掉多余的空格
  575. const parts = selector.trim().split(/\s+/);
  576. parts.forEach(part => {
  577. // 检查每个选择器部分
  578. if (part.startsWith('#')) {
  579. ids += 1; // ID选择器
  580. } else if (part.startsWith('.')) {
  581. classes += 1; // 类选择器
  582. } else {
  583. tags += 1; // 标签选择器
  584. }
  585. });
  586. // 返回权重对象
  587. // 计算最终权重值
  588. const specificityValue = (ids * 100) + (classes * 10) + tags;
  589. return specificityValue;
  590. }
  591. static cacheStyle = {}
  592. static getStyleHref(styleId, selectorText) {
  593. if (CSSSRule.cacheStyle[styleId]) return CSSSRule.cacheStyle[styleId];
  594. foo: for (let sheet of cssList) {
  595. try {
  596. let { href, selectorTexts } = sheet;
  597. for (let text of selectorTexts)
  598. if (selectorText === text) {
  599. CSSSRule.cacheStyle[styleId] = href;
  600. break foo;
  601. }
  602. } catch { }
  603. }
  604. return CSSSRule.cacheStyle[styleId]
  605. }
  606. // css属性扩展
  607. static cssExtent = [
  608. {
  609. "property": "margin",
  610. "sub-properties": [
  611. "margin-top",
  612. "margin-right",
  613. "margin-bottom",
  614. "margin-left"
  615. ]
  616. },
  617. {
  618. "property": "padding",
  619. "sub-properties": [
  620. "padding-top",
  621. "padding-right",
  622. "padding-bottom",
  623. "padding-left"
  624. ]
  625. },
  626. {
  627. "property": "border",
  628. "sub-properties": [
  629. "border-width",
  630. "border-style",
  631. "border-color",
  632. "border-top",
  633. "border-right",
  634. "border-bottom",
  635. "border-left"
  636. ]
  637. },
  638. {
  639. "property": "border-style",
  640. "sub-properties": [
  641. "border-top-style",
  642. "border-right-style",
  643. "border-bottom-style",
  644. "border-left-style"
  645. ]
  646. },
  647. {
  648. "property": "border-top",
  649. "sub-properties": [
  650. "border-top-width",
  651. "border-top-style",
  652. "border-top-color"
  653. ]
  654. },
  655. {
  656. "property": "border-bottom",
  657. "sub-properties": [
  658. "border-bottom-width",
  659. "border-bottom-style",
  660. "border-bottom-color"
  661. ]
  662. },
  663. {
  664. "property": "border-left",
  665. "sub-properties": [
  666. "border-left-width",
  667. "border-left-style",
  668. "border-left-color"
  669. ]
  670. },
  671. {
  672. "property": "border-right",
  673. "sub-properties": [
  674. "border-right-width",
  675. "border-right-style",
  676. "border-right-color"
  677. ]
  678. },
  679. {
  680. "property": "border-radius",
  681. "sub-properties": [
  682. "border-top-left-radius",
  683. "border-top-right-radius",
  684. "border-bottom-right-radius",
  685. "border-bottom-left-radius"
  686. ]
  687. },
  688. {
  689. "property": "background",
  690. "sub-properties": [
  691. "background-color",
  692. "background-image",
  693. "background-repeat",
  694. "background-position",
  695. "background-size",
  696. "background-attachment",
  697. "background-clip",
  698. "background-origin"
  699. ]
  700. },
  701. {
  702. "property": "font",
  703. "sub-properties": [
  704. "font-style",
  705. "font-variant",
  706. "font-weight",
  707. "font-size",
  708. "line-height",
  709. "font-family"
  710. ]
  711. },
  712. {
  713. "property": "list-style",
  714. "sub-properties": [
  715. "list-style-type",
  716. "list-style-position",
  717. "list-style-image"
  718. ]
  719. },
  720. {
  721. "property": "text-decoration",
  722. "sub-properties": [
  723. "text-decoration-line",
  724. "text-decoration-color",
  725. "text-decoration-style",
  726. "text-decoration-thickness"
  727. ]
  728. },
  729. {
  730. "property": "transition",
  731. "sub-properties": [
  732. "transition-property",
  733. "transition-duration",
  734. "transition-timing-function",
  735. "transition-delay"
  736. ]
  737. },
  738. {
  739. "property": "animation",
  740. "sub-properties": [
  741. "animation-name",
  742. "animation-duration",
  743. "animation-timing-function",
  744. "animation-delay",
  745. "animation-iteration-count",
  746. "animation-direction",
  747. "animation-fill-mode",
  748. "animation-play-state"
  749. ]
  750. },
  751. {
  752. "property": "grid",
  753. "sub-properties": [
  754. "grid-template-rows",
  755. "grid-template-columns",
  756. "grid-template-areas",
  757. "grid-area",
  758. "grid-column",
  759. "grid-row",
  760. "grid-auto-rows",
  761. "grid-auto-columns",
  762. "grid-auto-flow"
  763. ]
  764. },
  765. {
  766. "property": "flex",
  767. "sub-properties": [
  768. "flex-grow",
  769. "flex-shrink",
  770. "flex-basis"
  771. ]
  772. },
  773. {
  774. "property": "outline",
  775. "sub-properties": [
  776. "outline-width",
  777. "outline-style",
  778. "outline-color"
  779. ]
  780. }
  781. ]
  782. }
  783. /**
  784. * 根据json导出数据
  785. */
  786. class CssJsonRules {
  787. static findExtentInfo(entries) {
  788. return entries.map(e => {
  789. let extentInfo = CSSSRule.cssExtent.find(a => a.property === e.name);
  790. if (extentInfo) {
  791. return extentInfo["sub-properties"];
  792. }
  793. return undefined
  794. }).filter(Boolean).flat();
  795. }
  796. /**
  797. * 解析json数据
  798. * @param {*} jsonData
  799. * @returns
  800. */
  801. static parseJson(jsonData) {
  802. let { inlineStyle, matchedCSSRules, pseudoElements, inherited } = jsonData;
  803. let result = {
  804. "内敛的样式": [],
  805. "匹配的样式": [],
  806. "伪类的样式": [],
  807. "继承的样式": []
  808. }
  809. // 解析json中的内敛样式
  810. result.内敛的样式.push(...inlineStyle.cssProperties.filter(a => a.text !== undefined).map(({ name, value }) => ({ name, value })));
  811. // 解析json中的匹配样式
  812. matchedCSSRules.forEach(cssRule => {
  813. let { rule } = cssRule;
  814. let { shorthandEntries } = rule;
  815. // 按照规律查询出信息
  816. let style = rule.style.cssProperties.filter(a => a.text !== undefined || rule.origin === "user-agent").map(({ name, value }) => ({ name, value }));
  817. if (rule.origin === "user-agent" && shorthandEntries && shorthandEntries.length > 0) {
  818. let sameInfo = CssJsonRules.findExtentInfo(shorthandEntries);
  819. style = style.filter(s => !sameInfo.includes(s.name))
  820. }
  821. let text = rule.selectorList.text;
  822. let obj = {
  823. origin: rule.styleSheetId ? CSSSRule.getStyleHref(rule.styleSheetId, text) : '用户自定义',
  824. text,
  825. style
  826. };
  827. result.匹配的样式.push(obj)
  828. })
  829. // 解析伪类的样式
  830. pseudoElements.forEach(cssRule => {
  831. let { pseudoType, matches } = cssRule;
  832. let obj = {
  833. pseudoType,
  834. matchs: matches.map(({ rule }) => {
  835. let text = rule.selectorList.text;
  836. return {
  837. origin: rule.styleSheetId ? CSSSRule.getStyleHref(rule.styleSheetId, text) : '用户自定义',
  838. text,
  839. style: rule.style.cssProperties.filter(a => a.text !== undefined).map(({ name, value }) => ({ name, value }))
  840. }
  841. })
  842. };
  843. result.伪类的样式.push(obj)
  844. })
  845. // 解析继承的样式
  846. inherited.forEach(inheritedCssRule => {
  847. let matchedCSSRules = inheritedCssRule.matchedCSSRules;
  848. matchedCSSRules.forEach((cssRule) => {
  849. let { rule } = cssRule;
  850. let style = rule.style.cssProperties.filter(a => a.text !== undefined).map(({ name, value }) => ({ name, value }));
  851. if (style.length === 0) return;
  852. let text = rule.selectorList.text;
  853. result.继承的样式.push({
  854. origin: rule.styleSheetId ? CSSSRule.getStyleHref(rule.styleSheetId, text) : '用户自定义',
  855. text,
  856. style
  857. })
  858. })
  859. })
  860. return result;
  861. }
  862. }
  863. /**
  864. * 页卡组件
  865. */
  866. class PageTab {
  867. #tabs = []
  868. #tabContainer
  869. constructor({ tabs }) {
  870. this.#tabs = tabs || [];
  871. this.#tabContainer = document.querySelector('.tabs');
  872. this.#initClickEvent();
  873. }
  874. addTab(tabInfo) {
  875. this.#tabs.push(tabInfo)
  876. }
  877. #initClickEvent() {
  878. this.#tabContainer.addEventListener('click', (e) => {
  879. let target = e.target;
  880. if (target.classList.contains("tab")) {
  881. this.activeTabById(target.dataset.id);
  882. }
  883. })
  884. }
  885. get tabs() {
  886. return this.#tabs;
  887. }
  888. activeTabById(id) {
  889. // 移除已经选中的
  890. let activedTab = this.tabs.find(a => a.isActive);
  891. if (activedTab) {
  892. activedTab.removeActive();
  893. }
  894. // 给设置的选中
  895. let awaitActiveTab = this.tabs.find(a => a.id === id);
  896. if (awaitActiveTab) {
  897. awaitActiveTab.addActive();
  898. }
  899. }
  900. }
  901. class tab {
  902. #id // 页卡id
  903. #tabEle // tab页卡元素
  904. #contentEle // tab内容元素
  905. #isAlived // 是否保持持久化(为true表示 页卡激活事件只执行一次,当前内容会一直存在,如果为false,则表示每次点击页卡都会重新执行激活事件)
  906. activeEvent // 激活页卡执行事件
  907. #isActived // 是否选中
  908. constructor({ id, isAlived, activeEvent }) {
  909. this.#id = id;
  910. this.#tabEle = tab.getTabEleById(id);
  911. this.#contentEle = tab.getTabContentById(id);
  912. this.#isAlived = isAlived;
  913. this.#isActived = false;
  914. this.activeEvent = activeEvent;
  915. }
  916. get tabInfo() {
  917. return {
  918. isActive: this.isActive(),
  919. id: this.#id
  920. }
  921. }
  922. /**
  923. * 判断是否为选中的标签
  924. */
  925. isActive() {
  926. return this.#isActived;
  927. }
  928. /**
  929. * 添加选中效果
  930. */
  931. addActive() {
  932. this.#tabEle.classList.add('active');
  933. this.#contentEle.classList.add('active');
  934. this.#isActived = true;
  935. this.activeEvent();
  936. }
  937. activeEvent() {
  938. if (this.#isAlived === true) {
  939. // 如果开启了持久化,表示只执行一次
  940. this.activeEvent = function () { }
  941. }
  942. typeof this.activeEvent === 'function' && this.activeEvent();
  943. }
  944. /**
  945. * 移除选中效果
  946. */
  947. removeActive() {
  948. this.#tabEle.classList.remove('active');
  949. this.#contentEle.classList.remove('active');
  950. this.#isActived = false;
  951. }
  952. static getTabEleById(id) {
  953. return document.querySelector(`.tabs tab[data-id="${id}"]`);
  954. }
  955. static getTabContentById(id) {
  956. return document.querySelector(`.tab-container #${id}`);
  957. }
  958. }