封装评查点的相关接口,完成评查点列表的简单搜索和查询

This commit is contained in:
2025-04-02 19:17:44 +08:00
parent 706cea8705
commit 2bde2bd76e
41 changed files with 11174 additions and 609 deletions
+1339
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,123 @@
/* Compatability shim for jQuery and underscores.js.
*
* Copyright Sphinx contributors
* Released under the two clause BSD licence
*/
/**
* small helper function to urldecode strings
*
* See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL
*/
jQuery.urldecode = function(x) {
if (!x) {
return x
}
return decodeURIComponent(x.replace(/\+/g, ' '));
};
/**
* small helper function to urlencode strings
*/
jQuery.urlencode = encodeURIComponent;
/**
* This function returns the parsed url parameters of the
* current request. Multiple values per key are supported,
* it will always return arrays of strings for the value parts.
*/
jQuery.getQueryParameters = function(s) {
if (typeof s === 'undefined')
s = document.location.search;
var parts = s.substr(s.indexOf('?') + 1).split('&');
var result = {};
for (var i = 0; i < parts.length; i++) {
var tmp = parts[i].split('=', 2);
var key = jQuery.urldecode(tmp[0]);
var value = jQuery.urldecode(tmp[1]);
if (key in result)
result[key].push(value);
else
result[key] = [value];
}
return result;
};
/**
* highlight a given string on a jquery object by wrapping it in
* span elements with the given class name.
*/
jQuery.fn.highlightText = function(text, className) {
function highlight(node, addItems) {
if (node.nodeType === 3) {
var val = node.nodeValue;
var pos = val.toLowerCase().indexOf(text);
if (pos >= 0 &&
!jQuery(node.parentNode).hasClass(className) &&
!jQuery(node.parentNode).hasClass("nohighlight")) {
var span;
var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg");
if (isInSVG) {
span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
} else {
span = document.createElement("span");
span.className = className;
}
span.appendChild(document.createTextNode(val.substr(pos, text.length)));
node.parentNode.insertBefore(span, node.parentNode.insertBefore(
document.createTextNode(val.substr(pos + text.length)),
node.nextSibling));
node.nodeValue = val.substr(0, pos);
if (isInSVG) {
var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
var bbox = node.parentElement.getBBox();
rect.x.baseVal.value = bbox.x;
rect.y.baseVal.value = bbox.y;
rect.width.baseVal.value = bbox.width;
rect.height.baseVal.value = bbox.height;
rect.setAttribute('class', className);
addItems.push({
"parent": node.parentNode,
"target": rect});
}
}
}
else if (!jQuery(node).is("button, select, textarea")) {
jQuery.each(node.childNodes, function() {
highlight(this, addItems);
});
}
}
var addItems = [];
var result = this.each(function() {
highlight(this, addItems);
});
for (var i = 0; i < addItems.length; ++i) {
jQuery(addItems[i].parent).before(addItems[i].target);
}
return result;
};
/*
* backward compatibility for jQuery.browser
* This will be supported until firefox bug is fixed.
*/
if (!jQuery.browser) {
jQuery.uaMatch = function(ua) {
ua = ua.toLowerCase();
var match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
/(webkit)[ \/]([\w.]+)/.exec(ua) ||
/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
/(msie) ([\w.]+)/.exec(ua) ||
ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
[];
return {
browser: match[ 1 ] || "",
version: match[ 2 ] || "0"
};
};
jQuery.browser = {};
jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true;
}
+3
View File
@@ -0,0 +1,3 @@
<!-- saved from url=(1905)https://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-8543159550507237&output=html&adk=1812271804&adf=3025194257&abgtt=6&lmt=1742918816&plat=1%3A16777216%2C2%3A16777216%2C3%3A16%2C4%3A16%2C9%3A134250504%2C16%3A8388608%2C17%3A32%2C24%3A32%2C25%3A32%2C30%3A1081344%2C32%3A32%2C41%3A32%2C42%3A32&format=0x0&url=https%3A%2F%2Fpostgrest.postgresql.ac.cn%2Fen%2Fv12%2Freferences%2Fapi%2Ftables_views.html&pra=5&wgl=1&aihb=0&asro=0&ailel=1~2~4~7~8~9~10~11~12~13~14~15~16~17~18~19~20~21~24~29~30~34&aiael=1~2~3~4~7~8~9~10~11~12~13~14~15~16~17~18~19~20~21~24~29~30~34&aicel=33~38&aifxl=29_18~30_19&aiixl=29_5~30_6&itsi=2&aiapm=0.15&aiapmi=0.33938&aiact=0.6&ailct=0.65&uach=WyJXaW5kb3dzIiwiMTUuMC4wIiwieDg2IiwiIiwiMTM0LjAuNjk5OC4xNzgiLG51bGwsMCxudWxsLCI2NCIsW1siQ2hyb21pdW0iLCIxMzQuMC42OTk4LjE3OCJdLFsiTm90OkEtQnJhbmQiLCIyNC4wLjAuMCJdLFsiR29vZ2xlIENocm9tZSIsIjEzNC4wLjY5OTguMTc4Il1dLDBd&dt=1743586875473&bpp=9&bdt=18&idt=42&shv=r20250401&mjsv=m202503270101&ptt=9&saldr=aa&abxe=1&cookie=ID%3D3cc7d08ea74aaacf%3AT%3D1743563350%3ART%3D1743586695%3AS%3DALNI_MYbZfLN193La06gpdGbWGcLF-geyQ&gpic=UID%3D0000100517b5f12e%3AT%3D1743563350%3ART%3D1743586695%3AS%3DALNI_MZ_DqSWmGT0F3xqdYcK-IpUUt7nyw&eo_id_str=ID%3De98a5540fe5f2598%3AT%3D1743563350%3ART%3D1743586695%3AS%3DAA-AfjZFwr_srXR4du2XMZLugW-_&nras=1&correlator=3071774986153&frm=20&pv=2&u_tz=480&u_his=34&u_h=1080&u_w=1920&u_ah=1032&u_aw=1920&u_cd=24&u_sd=1&dmc=8&adx=-12245933&ady=-12245933&biw=1784&bih=872&scr_x=0&scr_y=0&eid=95355972%2C95355974%2C31091240%2C42531705%2C95353387%2C95354562%2C95356499%2C95356505%2C31091407%2C31089210%2C95356787%2C95356928&oid=2&pvsid=2918862569797426&tmod=1115595993&uas=0&nvt=1&fsapi=1&fc=1920&brdim=24%2C148%2C24%2C148%2C1920%2C0%2C1815%2C967%2C1799%2C872&vis=1&rsz=%7C%7Cs%7C&abl=NS&fu=32768&bc=31&bz=1.01&td=1&tdf=2&psd=W251bGwsW251bGwsbnVsbCxudWxsLCJkZXByZWNhdGVkX2thbm9uIl0sbnVsbCwxXQ..&nt=1&ifi=1&uci=a!1&fsb=1&dtd=56 -->
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><script>var apcnf = '{"googMsgType":"apcnf","config":"[[],[],null,null,[],null,[],null,null,null,null,null,null,null,null,null,null,[1,null,null,[]],null,null,null,[\\\"zh-CN\\\"],null,null,null,null,null,null,null,null,null,null,[[\\\"\\\\u5c55\\\\u5f00/\\\\u6536\\\\u8d77\\\\u5de5\\\\u5177\\\\u680f\\\",\\\"\\\\u663e\\\\u793a/\\\\u9690\\\\u85cf\\\\u9690\\\\u79c1\\\\u8bbe\\\\u7f6e\\\\u548c\\\\u6cd5\\\\u5f8b\\\\u8bbe\\\\u7f6e\\\",\\\"\\\\u5173\\\\u95ed\\\\u663e\\\\u793a\\\\u9690\\\\u79c1\\\\u8bbe\\\\u7f6e\\\\u548c\\\\u6cd5\\\\u5f8b\\\\u8bbe\\\\u7f6e\\\"]]]"}';window.parent.postMessage(apcnf, '*');</script></head><body marginwidth="0" marginheight="0"></body></html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+95
View File
@@ -0,0 +1,95 @@
/* Copy buttons */
button.copybtn {
position: absolute;
display: flex;
top: .3em;
right: .3em;
width: 1.7em;
height: 1.7em;
opacity: 0;
transition: opacity 0.3s, border .3s, background-color .3s;
user-select: none;
padding: 0;
border: none;
outline: none;
border-radius: 0.4em;
/* The colors that GitHub uses */
border: #1b1f2426 1px solid;
background-color: #f6f8fa;
color: #57606a;
}
button.copybtn.success {
border-color: #22863a;
color: #22863a;
}
button.copybtn svg {
stroke: currentColor;
width: 1.5em;
height: 1.5em;
padding: 0.1em;
}
div.highlight {
position: relative;
}
/* Show the copybutton */
.highlight:hover button.copybtn, button.copybtn.success {
opacity: 1;
}
.highlight button.copybtn:hover {
background-color: rgb(235, 235, 235);
}
.highlight button.copybtn:active {
background-color: rgb(187, 187, 187);
}
/**
* A minimal CSS-only tooltip copied from:
* https://codepen.io/mildrenben/pen/rVBrpK
*
* To use, write HTML like the following:
*
* <p class="o-tooltip--left" data-tooltip="Hey">Short</p>
*/
.o-tooltip--left {
position: relative;
}
.o-tooltip--left:after {
opacity: 0;
visibility: hidden;
position: absolute;
content: attr(data-tooltip);
padding: .2em;
font-size: .8em;
left: -.2em;
background: grey;
color: white;
white-space: nowrap;
z-index: 2;
border-radius: 2px;
transform: translateX(-102%) translateY(0);
transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1);
}
.o-tooltip--left:hover:after {
display: block;
opacity: 1;
visibility: visible;
transform: translateX(-100%) translateY(0);
transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1);
transition-delay: .5s;
}
/* By default the copy button shouldn't show up when printing a page */
@media print {
button.copybtn {
display: none;
}
}
+248
View File
@@ -0,0 +1,248 @@
// Localization support
const messages = {
'en': {
'copy': 'Copy',
'copy_to_clipboard': 'Copy to clipboard',
'copy_success': 'Copied!',
'copy_failure': 'Failed to copy',
},
'es' : {
'copy': 'Copiar',
'copy_to_clipboard': 'Copiar al portapapeles',
'copy_success': '¡Copiado!',
'copy_failure': 'Error al copiar',
},
'de' : {
'copy': 'Kopieren',
'copy_to_clipboard': 'In die Zwischenablage kopieren',
'copy_success': 'Kopiert!',
'copy_failure': 'Fehler beim Kopieren',
},
'fr' : {
'copy': 'Copier',
'copy_to_clipboard': 'Copier dans le presse-papier',
'copy_success': 'Copié !',
'copy_failure': 'Échec de la copie',
},
'ru': {
'copy': 'Скопировать',
'copy_to_clipboard': 'Скопировать в буфер',
'copy_success': 'Скопировано!',
'copy_failure': 'Не удалось скопировать',
},
'zh-CN': {
'copy': '复制',
'copy_to_clipboard': '复制到剪贴板',
'copy_success': '复制成功!',
'copy_failure': '复制失败',
},
'it' : {
'copy': 'Copiare',
'copy_to_clipboard': 'Copiato negli appunti',
'copy_success': 'Copiato!',
'copy_failure': 'Errore durante la copia',
}
}
let locale = 'en'
if( document.documentElement.lang !== undefined
&& messages[document.documentElement.lang] !== undefined ) {
locale = document.documentElement.lang
}
let doc_url_root = DOCUMENTATION_OPTIONS.URL_ROOT;
if (doc_url_root == '#') {
doc_url_root = '';
}
/**
* SVG files for our copy buttons
*/
let iconCheck = `<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-check" width="44" height="44" viewBox="0 0 24 24" stroke-width="2" stroke="#22863a" fill="none" stroke-linecap="round" stroke-linejoin="round">
<title>${messages[locale]['copy_success']}</title>
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M5 12l5 5l10 -10" />
</svg>`
// If the user specified their own SVG use that, otherwise use the default
let iconCopy = ``;
if (!iconCopy) {
iconCopy = `<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-copy" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="#000000" fill="none" stroke-linecap="round" stroke-linejoin="round">
<title>${messages[locale]['copy_to_clipboard']}</title>
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<rect x="8" y="8" width="12" height="12" rx="2" />
<path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2" />
</svg>`
}
/**
* Set up copy/paste for code blocks
*/
const runWhenDOMLoaded = cb => {
if (document.readyState != 'loading') {
cb()
} else if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', cb)
} else {
document.attachEvent('onreadystatechange', function() {
if (document.readyState == 'complete') cb()
})
}
}
const codeCellId = index => `codecell${index}`
// Clears selected text since ClipboardJS will select the text when copying
const clearSelection = () => {
if (window.getSelection) {
window.getSelection().removeAllRanges()
} else if (document.selection) {
document.selection.empty()
}
}
// Changes tooltip text for a moment, then changes it back
// We want the timeout of our `success` class to be a bit shorter than the
// tooltip and icon change, so that we can hide the icon before changing back.
var timeoutIcon = 2000;
var timeoutSuccessClass = 1500;
const temporarilyChangeTooltip = (el, oldText, newText) => {
el.setAttribute('data-tooltip', newText)
el.classList.add('success')
// Remove success a little bit sooner than we change the tooltip
// So that we can use CSS to hide the copybutton first
setTimeout(() => el.classList.remove('success'), timeoutSuccessClass)
setTimeout(() => el.setAttribute('data-tooltip', oldText), timeoutIcon)
}
// Changes the copy button icon for two seconds, then changes it back
const temporarilyChangeIcon = (el) => {
el.innerHTML = iconCheck;
setTimeout(() => {el.innerHTML = iconCopy}, timeoutIcon)
}
const addCopyButtonToCodeCells = () => {
// If ClipboardJS hasn't loaded, wait a bit and try again. This
// happens because we load ClipboardJS asynchronously.
if (window.ClipboardJS === undefined) {
setTimeout(addCopyButtonToCodeCells, 250)
return
}
// Add copybuttons to all of our code cells
const COPYBUTTON_SELECTOR = 'div.highlight pre';
const codeCells = document.querySelectorAll(COPYBUTTON_SELECTOR)
codeCells.forEach((codeCell, index) => {
const id = codeCellId(index)
codeCell.setAttribute('id', id)
const clipboardButton = id =>
`<button class="copybtn o-tooltip--left" data-tooltip="${messages[locale]['copy']}" data-clipboard-target="#${id}">
${iconCopy}
</button>`
codeCell.insertAdjacentHTML('afterend', clipboardButton(id))
})
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
/**
* Removes excluded text from a Node.
*
* @param {Node} target Node to filter.
* @param {string} exclude CSS selector of nodes to exclude.
* @returns {DOMString} Text from `target` with text removed.
*/
function filterText(target, exclude) {
const clone = target.cloneNode(true); // clone as to not modify the live DOM
if (exclude) {
// remove excluded nodes
clone.querySelectorAll(exclude).forEach(node => node.remove());
}
return clone.innerText;
}
// Callback when a copy button is clicked. Will be passed the node that was clicked
// should then grab the text and replace pieces of text that shouldn't be used in output
function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") {
var regexp;
var match;
// Do we check for line continuation characters and "HERE-documents"?
var useLineCont = !!lineContinuationChar
var useHereDoc = !!hereDocDelim
// create regexp to capture prompt and remaining line
if (isRegexp) {
regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)')
} else {
regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)')
}
const outputLines = [];
var promptFound = false;
var gotLineCont = false;
var gotHereDoc = false;
const lineGotPrompt = [];
for (const line of textContent.split('\n')) {
match = line.match(regexp)
if (match || gotLineCont || gotHereDoc) {
promptFound = regexp.test(line)
lineGotPrompt.push(promptFound)
if (removePrompts && promptFound) {
outputLines.push(match[2])
} else {
outputLines.push(line)
}
gotLineCont = line.endsWith(lineContinuationChar) & useLineCont
if (line.includes(hereDocDelim) & useHereDoc)
gotHereDoc = !gotHereDoc
} else if (!onlyCopyPromptLines) {
outputLines.push(line)
} else if (copyEmptyLines && line.trim() === '') {
outputLines.push(line)
}
}
// If no lines with the prompt were found then just use original lines
if (lineGotPrompt.some(v => v === true)) {
textContent = outputLines.join('\n');
}
// Remove a trailing newline to avoid auto-running when pasting
if (textContent.endsWith("\n")) {
textContent = textContent.slice(0, -1)
}
return textContent
}
var copyTargetText = (trigger) => {
var target = document.querySelector(trigger.attributes['data-clipboard-target'].value);
// get filtered text
let exclude = '.linenos';
let text = filterText(target, exclude);
return formatCopyText(text, '', false, true, true, true, '', '')
}
// Initialize with a callback so we can modify the text before copy
const clipboard = new ClipboardJS('.copybtn', {text: copyTargetText})
// Update UI with error/success messages
clipboard.on('success', event => {
clearSelection()
temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_success'])
temporarilyChangeIcon(event.trigger)
})
clipboard.on('error', event => {
temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_failure'])
})
}
runWhenDOMLoaded(addCopyButtonToCodeCells)
+152
View File
@@ -0,0 +1,152 @@
.wy-nav-content {
max-width: initial;
}
#postgrest-documentation > h1 {
display: none;
}
div.wy-menu.rst-pro {
display: none !important;
}
div.highlight {
background: #fff !important;
}
div.line-block {
margin-bottom: 0px !important;
}
#sponsors {
text-align: center;
}
#sponsors h2 {
text-align: left;
}
#sponsors img{
margin: 10px;
width: 13em; /* ".. image::" does not apply width properly to SVGs */
}
#thanks{
text-align: center;
}
#thanks img{
margin: 10px;
}
#thanks h2{
text-align: left;
}
#thanks p{
text-align: left;
}
#thanks ul{
text-align: left;
}
.image-container {
max-width: 800px;
display: block;
margin-left: auto;
margin-right: auto;
margin-bottom: 24px;
}
.wy-table-responsive table td {
white-space: normal !important;
}
.wy-table-responsive {
overflow: visible !important;
}
#tutorials span.caption-text {
display: none;
}
#references span.caption-text {
display: none;
}
#explanations span.caption-text {
display: none;
}
#how-tos span.caption-text {
display: none;
}
#ecosystem span.caption-text {
display: none;
}
#integrations span.caption-text {
display: none;
}
#api span.caption-text {
display: none;
}
/* Tweaks for dark mode from extension: sphinx-rtd-dark-theme */
html[data-theme="dark"] .highlight {
background-color: #17181c !important;
}
html[data-theme="dark"] .sphinx-tabs-tab {
color: var(--dark-link-color);
}
html[data-theme="dark"] .sphinx-tabs-panel {
border: 1px solid #404040;
border-top: 0;
background: #141414;
}
html[data-theme="dark"] .sphinx-tabs-tab[aria-selected="true"] {
border: 1px solid #404040;
border-bottom: 1px solid #141414;
background-color: #141414;
}
html[data-theme="dark"] [role="tablist"] {
border-bottom: 1px solid #404040;
}
html[data-theme="dark"] .btn-neutral {
color: white !important;
}
html[data-theme="dark"] .img-dark {
display: inline;
}
html:not([data-theme="dark"]) .img-dark {
display: none;
}
html[data-theme="dark"] .img-light {
display: none;
}
html:not([data-theme="dark"]) .img-light {
display: inline;
}
html[data-theme="dark"] .img-translucent img {
background-color: #cccccc;
}
.img-translucent img {
transition: background-color 0.3s;
margin-bottom: 24px;
}
+521
View File
@@ -0,0 +1,521 @@
:root {
--dark-text-color: #c1c1c1;
--dark-link-color: #249ee8;
}
html[data-theme="dark"] body {
color: #bfbfbf;
}
html[data-theme="dark"] .wy-nav-content-wrap {
background-color: #101010;
}
html[data-theme="dark"] .wy-nav-content {
background-color: #141414;
}
html[data-theme="dark"] .section {
color: var(--dark-text-color);
}
html[data-theme="dark"] .highlight {
background-color: #17181c;
}
html[data-theme="dark"] .highlight .nn {
color: var(--dark-text-color);
}
html[data-theme="dark"] .highlight .nb {
color: #8bb8df;
}
html[data-theme="dark"] .highlight .nv {
color: #40ffff;
}
html[data-theme="dark"] .highlight .kn,
html[data-theme="dark"] .highlight .kc,
html[data-theme="dark"] .highlight .k {
color: #41c2ea;
}
html[data-theme="dark"] .highlight .s1,
html[data-theme="dark"] .highlight .s2 {
color: #b3e87f;
}
html[data-theme="dark"] .highlight .nt {
color: #ccb350;
}
html[data-theme="dark"] .highlight .c1 {
color: #686868;
}
html[data-theme="dark"] .highlight .hll {
background-color: #002c4d;
}
html[data-theme="dark"] .rst-content div[class^="highlight"] {
border-color: #1a1a1a;
}
html[data-theme="dark"] .wy-nav-content a,
html[data-theme="dark"] .wy-nav-content a:visited {
color: var(--dark-link-color);
}
html[data-theme="dark"] .btn-neutral {
background-color: #17181c !important;
}
html[data-theme="dark"] .btn-neutral:hover {
background-color: #101114 !important;
}
html[data-theme="dark"] .btn-neutral:visited {
color: #c1c1c1 !important;
}
html[data-theme="dark"] .btn {
box-shadow: none;
}
html[data-theme="dark"] footer {
color: #bdbdbd;
}
html[data-theme="dark"] .wy-nav-side {
background-color: #0d0d0d;
}
html[data-theme="dark"] .wy-menu-vertical li.current {
background-color: #141414;
}
html[data-theme="dark"] .wy-menu-vertical li.current > a,
html[data-theme="dark"] .wy-menu-vertical li.on a {
background-color: #141415;
color: var(--dark-text-color);
}
html[data-theme="dark"] .wy-menu-vertical li.toctree-l1.current > a,
html[data-theme="dark"] .wy-menu-vertical li.current a {
border-color: #0b0c0d;
}
html[data-theme="dark"] .wy-menu-vertical li.current a {
color: #bbb;
}
html[data-theme="dark"] .wy-menu-vertical li.current a:hover {
background-color: #222;
}
html[data-theme="dark"] .wy-menu-vertical a:hover,
html[data-theme="dark"] .wy-menu-vertical li.current > a:hover,
html[data-theme="dark"] .wy-menu-vertical li.on a:hover {
background-color: #1e1e1e;
}
html[data-theme="dark"] .wy-menu-vertical li.toctree-l2.current > a,
html[data-theme="dark"]
.wy-menu-vertical
li.toctree-l2.current
li.toctree-l3
> a {
background-color: #18181a;
}
html[data-theme="dark"] .wy-side-nav-search {
background-color: #0b152d;
}
html[data-theme="dark"] .wy-side-nav-search .wy-dropdown > a,
html[data-theme="dark"] .wy-side-nav-search > a {
color: #ddd;
}
html[data-theme="dark"] .wy-side-nav-search input[type="text"] {
border-color: #111;
background-color: #141414;
color: var(--dark-text-color);
}
html[data-theme="dark"] .theme-switcher {
background-color: #0b0c0d;
color: var(--dark-text-color);
}
html[data-theme="dark"].writer-html4 .rst-content dl:not(.docutils) > dt,
html[data-theme="dark"].writer-html5
.rst-content
dl[class]:not(.option-list):not(.field-list):not(.footnote):not(
.glossary
):not(.simple)
> dt {
background-color: #0b0b0b;
color: #007dce;
border-color: #282828;
}
html[data-theme="dark"] .rst-content code,
html[data-theme="dark"] .rst-content tt {
color: var(--dark-text-color);
}
html[data-theme="dark"].writer-html4
.rst-content
dl:not(.docutils)
dl:not(.field-list)
> dt,
html[data-theme="dark"].writer-html5
.rst-content
dl[class]:not(.option-list):not(.field-list):not(.footnote):not(
.glossary
):not(.simple)
dl:not(.field-list)
> dt {
background-color: #0f0f0f;
color: #959595;
border-color: #2b2b2b;
}
html[data-theme="dark"] .rst-content code,
html[data-theme="dark"] .rst-content tt,
html[data-theme="dark"] code {
background-color: #2d2d2d;
border-color: #1c1c1c;
}
html[data-theme="dark"] .rst-content code.xref,
html[data-theme="dark"] .rst-content tt.xref,
html[data-theme="dark"] a .rst-content code,
html[data-theme="dark"] a .rst-content tt {
color: #cecece;
}
html[data-theme="dark"] .rst-content .hint,
html[data-theme="dark"] .rst-content .important,
html[data-theme="dark"] .rst-content .tip,
html[data-theme="dark"] .rst-content .wy-alert-success.admonition,
html[data-theme="dark"] .rst-content .wy-alert-success.admonition-todo,
html[data-theme="dark"] .rst-content .wy-alert-success.attention,
html[data-theme="dark"] .rst-content .wy-alert-success.caution,
html[data-theme="dark"] .rst-content .wy-alert-success.danger,
html[data-theme="dark"] .rst-content .wy-alert-success.error,
html[data-theme="dark"] .rst-content .wy-alert-success.note,
html[data-theme="dark"] .rst-content .wy-alert-success.seealso,
html[data-theme="dark"] .rst-content .wy-alert-success.warning,
html[data-theme="dark"] .wy-alert.wy-alert-success {
background-color: #00392e;
}
html[data-theme="dark"] .rst-content .hint .admonition-title,
html[data-theme="dark"] .rst-content .hint .wy-alert-title,
html[data-theme="dark"] .rst-content .important .admonition-title,
html[data-theme="dark"] .rst-content .important .wy-alert-title,
html[data-theme="dark"] .rst-content .tip .admonition-title,
html[data-theme="dark"] .rst-content .tip .wy-alert-title,
html[data-theme="dark"]
.rst-content
.wy-alert-success.admonition-todo
.admonition-title,
html[data-theme="dark"]
.rst-content
.wy-alert-success.admonition-todo
.wy-alert-title,
html[data-theme="dark"]
.rst-content
.wy-alert-success.admonition
.admonition-title,
html[data-theme="dark"]
.rst-content
.wy-alert-success.admonition
.wy-alert-title,
html[data-theme="dark"]
.rst-content
.wy-alert-success.attention
.admonition-title,
html[data-theme="dark"]
.rst-content
.wy-alert-success.attention
.wy-alert-title,
html[data-theme="dark"]
.rst-content
.wy-alert-success.caution
.admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-success.caution .wy-alert-title,
html[data-theme="dark"] .rst-content .wy-alert-success.danger .admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-success.danger .wy-alert-title,
html[data-theme="dark"] .rst-content .wy-alert-success.error .admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-success.error .wy-alert-title,
html[data-theme="dark"] .rst-content .wy-alert-success.note .admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-success.note .wy-alert-title,
html[data-theme="dark"]
.rst-content
.wy-alert-success.seealso
.admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-success.seealso .wy-alert-title,
html[data-theme="dark"]
.rst-content
.wy-alert-success.warning
.admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-success.warning .wy-alert-title,
html[data-theme="dark"]
.rst-content
.wy-alert.wy-alert-success
.admonition-title,
html[data-theme="dark"]
.wy-alert.wy-alert-success
.rst-content
.admonition-title,
html[data-theme="dark"] .wy-alert.wy-alert-success .wy-alert-title {
background-color: #006a56;
}
html[data-theme="dark"] .rst-content .admonition,
html[data-theme="dark"] .rst-content .note,
html[data-theme="dark"] .rst-content .seealso,
html[data-theme="dark"] .rst-content .wy-alert-info.admonition,
html[data-theme="dark"] .rst-content .wy-alert-info.admonition-todo,
html[data-theme="dark"] .rst-content .wy-alert-info.attention,
html[data-theme="dark"] .rst-content .wy-alert-info.caution,
html[data-theme="dark"] .rst-content .wy-alert-info.danger,
html[data-theme="dark"] .rst-content .wy-alert-info.error,
html[data-theme="dark"] .rst-content .wy-alert-info.hint,
html[data-theme="dark"] .rst-content .wy-alert-info.important,
html[data-theme="dark"] .rst-content .wy-alert-info.tip,
html[data-theme="dark"] .rst-content .wy-alert-info.warning,
html[data-theme="dark"] .wy-alert.wy-alert-info {
background-color: #002c4d;
}
html[data-theme="dark"] .rst-content .admonition .admonition-title,
html[data-theme="dark"] .rst-content .note .admonition-title,
html[data-theme="dark"] .rst-content .note .wy-alert-title,
html[data-theme="dark"] .rst-content .seealso .admonition-title,
html[data-theme="dark"] .rst-content .seealso .wy-alert-title,
html[data-theme="dark"]
.rst-content
.wy-alert-info.admonition-todo
.admonition-title,
html[data-theme="dark"]
.rst-content
.wy-alert-info.admonition-todo
.wy-alert-title,
html[data-theme="dark"]
.rst-content
.wy-alert-info.admonition
.admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-info.admonition .wy-alert-title,
html[data-theme="dark"] .rst-content .wy-alert-info.attention .admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-info.attention .wy-alert-title,
html[data-theme="dark"] .rst-content .wy-alert-info.caution .admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-info.caution .wy-alert-title,
html[data-theme="dark"] .rst-content .wy-alert-info.danger .admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-info.danger .wy-alert-title,
html[data-theme="dark"] .rst-content .wy-alert-info.error .admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-info.error .wy-alert-title,
html[data-theme="dark"] .rst-content .wy-alert-info.hint .admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-info.hint .wy-alert-title,
html[data-theme="dark"] .rst-content .wy-alert-info.important .admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-info.important .wy-alert-title,
html[data-theme="dark"] .rst-content .wy-alert-info.tip .admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-info.tip .wy-alert-title,
html[data-theme="dark"] .rst-content .wy-alert-info.warning .admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-info.warning .wy-alert-title,
html[data-theme="dark"] .rst-content .wy-alert.wy-alert-info .admonition-title,
html[data-theme="dark"] .wy-alert.wy-alert-info .rst-content .admonition-title,
html[data-theme="dark"] .wy-alert.wy-alert-info .wy-alert-title {
background-color: #004a7b;
}
html[data-theme="dark"] .rst-content .admonition-todo,
html[data-theme="dark"] .rst-content .attention,
html[data-theme="dark"] .rst-content .caution,
html[data-theme="dark"] .rst-content .warning,
html[data-theme="dark"] .rst-content .wy-alert-warning.admonition,
html[data-theme="dark"] .rst-content .wy-alert-warning.danger,
html[data-theme="dark"] .rst-content .wy-alert-warning.error,
html[data-theme="dark"] .rst-content .wy-alert-warning.hint,
html[data-theme="dark"] .rst-content .wy-alert-warning.important,
html[data-theme="dark"] .rst-content .wy-alert-warning.note,
html[data-theme="dark"] .rst-content .wy-alert-warning.seealso,
html[data-theme="dark"] .rst-content .wy-alert-warning.tip,
html[data-theme="dark"] .wy-alert.wy-alert-warning {
background-color: #533500;
}
html[data-theme="dark"] .rst-content .admonition-todo .admonition-title,
html[data-theme="dark"] .rst-content .admonition-todo .wy-alert-title,
html[data-theme="dark"] .rst-content .attention .admonition-title,
html[data-theme="dark"] .rst-content .attention .wy-alert-title,
html[data-theme="dark"] .rst-content .caution .admonition-title,
html[data-theme="dark"] .rst-content .caution .wy-alert-title,
html[data-theme="dark"] .rst-content .warning .admonition-title,
html[data-theme="dark"] .rst-content .warning .wy-alert-title,
html[data-theme="dark"]
.rst-content
.wy-alert-warning.admonition
.admonition-title,
html[data-theme="dark"]
.rst-content
.wy-alert-warning.admonition
.wy-alert-title,
html[data-theme="dark"] .rst-content .wy-alert-warning.danger .admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-warning.danger .wy-alert-title,
html[data-theme="dark"] .rst-content .wy-alert-warning.error .admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-warning.error .wy-alert-title,
html[data-theme="dark"] .rst-content .wy-alert-warning.hint .admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-warning.hint .wy-alert-title,
html[data-theme="dark"]
.rst-content
.wy-alert-warning.important
.admonition-title,
html[data-theme="dark"]
.rst-content
.wy-alert-warning.important
.wy-alert-title,
html[data-theme="dark"] .rst-content .wy-alert-warning.note .admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-warning.note .wy-alert-title,
html[data-theme="dark"]
.rst-content
.wy-alert-warning.seealso
.admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-warning.seealso .wy-alert-title,
html[data-theme="dark"] .rst-content .wy-alert-warning.tip .admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-warning.tip .wy-alert-title,
html[data-theme="dark"]
.rst-content
.wy-alert.wy-alert-warning
.admonition-title,
html[data-theme="dark"]
.wy-alert.wy-alert-warning
.rst-content
.admonition-title,
html[data-theme="dark"] .wy-alert.wy-alert-warning .wy-alert-title {
background-color: #803b00;
}
html[data-theme="dark"] .rst-content .danger,
html[data-theme="dark"] .rst-content .error,
html[data-theme="dark"] .rst-content .wy-alert-danger.admonition,
html[data-theme="dark"] .rst-content .wy-alert-danger.admonition-todo,
html[data-theme="dark"] .rst-content .wy-alert-danger.attention,
html[data-theme="dark"] .rst-content .wy-alert-danger.caution,
html[data-theme="dark"] .rst-content .wy-alert-danger.hint,
html[data-theme="dark"] .rst-content .wy-alert-danger.important,
html[data-theme="dark"] .rst-content .wy-alert-danger.note,
html[data-theme="dark"] .rst-content .wy-alert-danger.seealso,
html[data-theme="dark"] .rst-content .wy-alert-danger.tip,
html[data-theme="dark"] .rst-content .wy-alert-danger.warning,
html[data-theme="dark"] .wy-alert.wy-alert-danger {
background-color: #82231a;
}
html[data-theme="dark"] .rst-content .danger .admonition-title,
html[data-theme="dark"] .rst-content .danger .wy-alert-title,
html[data-theme="dark"] .rst-content .error .admonition-title,
html[data-theme="dark"] .rst-content .error .wy-alert-title,
html[data-theme="dark"]
.rst-content
.wy-alert-danger.admonition-todo
.admonition-title,
html[data-theme="dark"]
.rst-content
.wy-alert-danger.admonition-todo
.wy-alert-title,
html[data-theme="dark"]
.rst-content
.wy-alert-danger.admonition
.admonition-title,
html[data-theme="dark"]
.rst-content
.wy-alert-danger.admonition
.wy-alert-title,
html[data-theme="dark"]
.rst-content
.wy-alert-danger.attention
.admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-danger.attention .wy-alert-title,
html[data-theme="dark"] .rst-content .wy-alert-danger.caution .admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-danger.caution .wy-alert-title,
html[data-theme="dark"] .rst-content .wy-alert-danger.hint .admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-danger.hint .wy-alert-title,
html[data-theme="dark"]
.rst-content
.wy-alert-danger.important
.admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-danger.important .wy-alert-title,
html[data-theme="dark"] .rst-content .wy-alert-danger.note .admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-danger.note .wy-alert-title,
html[data-theme="dark"] .rst-content .wy-alert-danger.seealso .admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-danger.seealso .wy-alert-title,
html[data-theme="dark"] .rst-content .wy-alert-danger.tip .admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-danger.tip .wy-alert-title,
html[data-theme="dark"] .rst-content .wy-alert-danger.warning .admonition-title,
html[data-theme="dark"] .rst-content .wy-alert-danger.warning .wy-alert-title,
html[data-theme="dark"]
.rst-content
.wy-alert.wy-alert-danger
.admonition-title,
html[data-theme="dark"]
.wy-alert.wy-alert-danger
.rst-content
.admonition-title,
html[data-theme="dark"] .wy-alert.wy-alert-danger .wy-alert-title {
background-color: #b9372b;
}
html[data-theme="dark"] .wy-nav-top {
background-color: #0b152d;
}
html[data-theme="dark"] .rst-content table.docutils thead,
html[data-theme="dark"] .rst-content table.field-list thead,
html[data-theme="dark"] .wy-table thead {
color: var(--dark-text-color);
}
html[data-theme="dark"]
.rst-content
table.docutils:not(.field-list)
tr:nth-child(2n-1)
td,
html[data-theme="dark"] .wy-table-backed,
html[data-theme="dark"] html[data-theme="dark"] .wy-table-odd td,
html[data-theme="dark"] .wy-table-striped tr:nth-child(2n-1) td {
background-color: #181818;
}
html[data-theme="dark"] .rst-content table.docutils td,
html[data-theme="dark"] .wy-table-bordered-all td,
html[data-theme="dark"].writer-html5 .rst-content table.docutils th,
html[data-theme="dark"] .rst-content table.docutils,
html[data-theme="dark"] .wy-table-bordered-all {
border-color: #262626;
}
html[data-theme="dark"] .rst-content table.docutils caption,
html[data-theme="dark"] .rst-content table.field-list caption,
html[data-theme="dark"] .wy-table caption {
color: var(--dark-text-color);
}
html[data-theme="dark"] .wy-menu-vertical li.toctree-l3.current > a,
html[data-theme="dark"]
.wy-menu-vertical
li.toctree-l3.current
li.toctree-l4
> a {
background-color: #18181a;
}
html[data-theme="dark"] .guilabel {
background-color: #343434;
border-color: #4d4d4d;
}
@@ -0,0 +1,13 @@
const loadTheme = () => {
let theme = localStorage.getItem('theme');
if (theme !== null) {
if (theme === 'dark')
document.documentElement.setAttribute('data-theme', 'dark');
} else {
localStorage.setItem('theme', 'light');
document.documentElement.setAttribute('data-theme', 'light');
}
};
loadTheme();
+156
View File
@@ -0,0 +1,156 @@
/*
* doctools.js
* ~~~~~~~~~~~
*
* Base JavaScript utilities for all Sphinx HTML documentation.
*
* :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
"use strict";
const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([
"TEXTAREA",
"INPUT",
"SELECT",
"BUTTON",
]);
const _ready = (callback) => {
if (document.readyState !== "loading") {
callback();
} else {
document.addEventListener("DOMContentLoaded", callback);
}
};
/**
* Small JavaScript module for the documentation.
*/
const Documentation = {
init: () => {
Documentation.initDomainIndexTable();
Documentation.initOnKeyListeners();
},
/**
* i18n support
*/
TRANSLATIONS: {},
PLURAL_EXPR: (n) => (n === 1 ? 0 : 1),
LOCALE: "unknown",
// gettext and ngettext don't access this so that the functions
// can safely bound to a different name (_ = Documentation.gettext)
gettext: (string) => {
const translated = Documentation.TRANSLATIONS[string];
switch (typeof translated) {
case "undefined":
return string; // no translation
case "string":
return translated; // translation exists
default:
return translated[0]; // (singular, plural) translation tuple exists
}
},
ngettext: (singular, plural, n) => {
const translated = Documentation.TRANSLATIONS[singular];
if (typeof translated !== "undefined")
return translated[Documentation.PLURAL_EXPR(n)];
return n === 1 ? singular : plural;
},
addTranslations: (catalog) => {
Object.assign(Documentation.TRANSLATIONS, catalog.messages);
Documentation.PLURAL_EXPR = new Function(
"n",
`return (${catalog.plural_expr})`
);
Documentation.LOCALE = catalog.locale;
},
/**
* helper function to focus on search bar
*/
focusSearchBar: () => {
document.querySelectorAll("input[name=q]")[0]?.focus();
},
/**
* Initialise the domain index toggle buttons
*/
initDomainIndexTable: () => {
const toggler = (el) => {
const idNumber = el.id.substr(7);
const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`);
if (el.src.substr(-9) === "minus.png") {
el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`;
toggledRows.forEach((el) => (el.style.display = "none"));
} else {
el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`;
toggledRows.forEach((el) => (el.style.display = ""));
}
};
const togglerElements = document.querySelectorAll("img.toggler");
togglerElements.forEach((el) =>
el.addEventListener("click", (event) => toggler(event.currentTarget))
);
togglerElements.forEach((el) => (el.style.display = ""));
if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler);
},
initOnKeyListeners: () => {
// only install a listener if it is really needed
if (
!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS &&
!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS
)
return;
document.addEventListener("keydown", (event) => {
// bail for input elements
if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
// bail with special keys
if (event.altKey || event.ctrlKey || event.metaKey) return;
if (!event.shiftKey) {
switch (event.key) {
case "ArrowLeft":
if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;
const prevLink = document.querySelector('link[rel="prev"]');
if (prevLink && prevLink.href) {
window.location.href = prevLink.href;
event.preventDefault();
}
break;
case "ArrowRight":
if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;
const nextLink = document.querySelector('link[rel="next"]');
if (nextLink && nextLink.href) {
window.location.href = nextLink.href;
event.preventDefault();
}
break;
}
}
// some keyboard layouts may need Shift to get /
switch (event.key) {
case "/":
if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break;
Documentation.focusSearchBar();
event.preventDefault();
}
});
},
};
// quick alias for translations
const _ = Documentation.gettext;
_ready(Documentation.init);
@@ -0,0 +1,13 @@
const DOCUMENTATION_OPTIONS = {
VERSION: '',
LANGUAGE: 'en',
COLLAPSE_INDEX: false,
BUILDER: 'html',
FILE_SUFFIX: '.html',
LINK_SUFFIX: '.html',
HAS_SOURCE: true,
SOURCELINK_SUFFIX: '.txt',
NAVIGATION_WITH_KEYS: false,
SHOW_SEARCH_SUMMARY: true,
ENABLE_SEARCH_SHORTCUTS: true,
};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+69
View File
@@ -0,0 +1,69 @@
input[type='color'],
input[type='date'],
input[type='datetime-local'],
input[type='datetime'],
input[type='email'],
input[type='month'],
input[type='number'],
input[type='password'],
input[type='search'],
input[type='tel'],
input[type='text'],
input[type='time'],
input[type='url'],
input[type='week'] {
box-shadow: none;
}
.theme-switcher {
border-radius: 50%;
position: fixed;
right: 1.6em;
bottom: 1.4em;
z-index: 3;
border: none;
height: 2.2em;
width: 2.2em;
background-color: #fcfcfc;
font-size: 20px;
-webkit-box-shadow: 0px 3px 14px 4px rgba(0, 0, 0, 0.62);
box-shadow: 0px 3px 14px 4px rgba(0, 0, 0, 0.62);
color: #404040;
transition: all 0.3s ease-in-out;
}
body,
.wy-nav-content-wrap,
.wy-nav-content,
.section,
.highlight,
.rst-content div[class^='highlight'],
.wy-nav-content a,
.btn-neutral,
.btn,
footer,
.wy-nav-side,
.wy-menu-vertical li,
.wy-menu-vertical a,
.wy-side-nav-search .wy-dropdown,
.wy-side-nav-search a,
.wy-side-nav-search input,
html.writer-html4 .rst-content dl:not(.docutils) > dt,
html.writer-html5
.rst-content
dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)
> dt,
.rst-content code,
.rst-content tt,
html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list) > dt,
html.writer-html5
.rst-content
dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)
dl:not(.field-list)
> dt,
code,
.rst-content code.xref,
.rst-content tt.xref {
transition: all 0.2s ease-in-out;
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+75
View File
@@ -0,0 +1,75 @@
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight { background: #eeffcc; }
.highlight .c { color: #408090; font-style: italic } /* Comment */
.highlight .err { border: 1px solid #FF0000 } /* Error */
.highlight .k { color: #007020; font-weight: bold } /* Keyword */
.highlight .o { color: #666666 } /* Operator */
.highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */
.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */
.highlight .cp { color: #007020 } /* Comment.Preproc */
.highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */
.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */
.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #A00000 } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #FF0000 } /* Generic.Error */
.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
.highlight .gi { color: #00A000 } /* Generic.Inserted */
.highlight .go { color: #333333 } /* Generic.Output */
.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
.highlight .gt { color: #0044DD } /* Generic.Traceback */
.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #007020 } /* Keyword.Pseudo */
.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #902000 } /* Keyword.Type */
.highlight .m { color: #208050 } /* Literal.Number */
.highlight .s { color: #4070a0 } /* Literal.String */
.highlight .na { color: #4070a0 } /* Name.Attribute */
.highlight .nb { color: #007020 } /* Name.Builtin */
.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */
.highlight .no { color: #60add5 } /* Name.Constant */
.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */
.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */
.highlight .ne { color: #007020 } /* Name.Exception */
.highlight .nf { color: #06287e } /* Name.Function */
.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */
.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #bb60d5 } /* Name.Variable */
.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #208050 } /* Literal.Number.Bin */
.highlight .mf { color: #208050 } /* Literal.Number.Float */
.highlight .mh { color: #208050 } /* Literal.Number.Hex */
.highlight .mi { color: #208050 } /* Literal.Number.Integer */
.highlight .mo { color: #208050 } /* Literal.Number.Oct */
.highlight .sa { color: #4070a0 } /* Literal.String.Affix */
.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */
.highlight .sc { color: #4070a0 } /* Literal.String.Char */
.highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */
.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */
.highlight .s2 { color: #4070a0 } /* Literal.String.Double */
.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */
.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */
.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */
.highlight .sx { color: #c65d09 } /* Literal.String.Other */
.highlight .sr { color: #235388 } /* Literal.String.Regex */
.highlight .s1 { color: #4070a0 } /* Literal.String.Single */
.highlight .ss { color: #517918 } /* Literal.String.Symbol */
.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #06287e } /* Name.Function.Magic */
.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */
.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */
.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */
.highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */
.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */
@@ -0,0 +1,302 @@
/* Left for CSS overrides we need to make in the future */
/* Fix badge on RTD Theme */
/* Please keep RTD badge displayed on your site */
.rst-versions.rst-badge {
display: block;
bottom: 50px;
/* Workaround for mkdocs which set a specific height for this element */
height: auto;
}
.rst-other-versions {
text-align: left;
}
.rst-other-versions a {
border: 0;
}
.rst-other-versions dl {
margin: 0;
}
.rtd-current-item {
font-weight: bold;
}
/* Fix RTD theme bottom margin */
.rst-content .line-block {
margin-bottom: 24px
}
/* Fix for nav bottom padding with flyout */
nav.wy-nav-side {
padding-bottom: 3em;
}
/* bookmark icon */
.bookmark-added-msg {display: none;}
.bookmark-active {display: none;}
.bookmark-inactive {display: none;}
/* Read the Docs promotional block, only applicable to RTD.org
To support sphinx_rtd_theme, a `wy-menu` element is added. Other themes are
targeted using the theme identifier and use custom elements instead of a CSS
framework html structure.
*/
div.ethical-sidebar,
div.ethical-footer {
display: block !important;
}
.ethical-sidebar,
.ethical-footer {
padding: 0.5em;
margin: 1em 0;
}
.ethical-sidebar img,
.ethical-footer img {
width: 120px;
height: 90px;
display: inline-block;
}
.ethical-sidebar .ethical-callout,
.ethical-footer .ethical-callout {
padding-top: 1em;
clear: both;
}
.ethical-sidebar .ethical-pixel,
.ethical-footer .ethical-pixel,
.ethical-fixedfooter .ethical-pixel {
display: none !important;
}
.ethical-sidebar .ethical-text,
.ethical-footer .ethical-text {
margin-top: 1em;
}
.ethical-sidebar .ethical-image-link,
.ethical-footer .ethical-image-link {
border: 0;
}
.ethical-sidebar,
.ethical-footer {
background-color: #eee;
border: 1px solid #ccc;
border-radius: 5px;
color: #0a0a0a;
font-size: 14px;
line-height: 20px;
}
/* Techstack badging */
.ethical-sidebar ul {
margin: 0 !important;
padding-left: 0;
list-style: none;
}
.ethical-sidebar ul li {
display: inline-block;
background-color: lightskyblue;
color: black;
padding: 0.25em 0.4em;
font-size: 75%;
font-weight: 700;
margin: 0.25em;
border-radius: 0.25rem;
text-align: center;
vertical-align: baseline;
white-space: nowrap;
line-height: 1.41;
}
.ethical-sidebar ul li:not(:last-child) {
margin-right: .25rem;
}
.ethical-sidebar a,
.ethical-sidebar a:visited,
.ethical-sidebar a:hover,
.ethical-sidebar a:active,
.ethical-footer a,
.ethical-footer a:visited,
.ethical-footer a:hover,
.ethical-footer a:active {
color: #0a0a0a;
text-decoration: none !important;
border-bottom: 0 !important;
}
.ethical-callout a {
color: #707070 !important;
text-decoration: none !important;
}
/* Sidebar promotions */
.ethical-sidebar {
text-align: center;
max-width: 300px;
margin-left: auto;
margin-right: auto;
}
/* Footer promotions */
.ethical-footer {
text-align: left;
font-size: 14px;
line-height: 20px;
}
.ethical-footer img {
float: right;
margin-left: 25px;
}
.ethical-footer .ethical-callout {
text-align: center;
}
.ethical-footer small {
font-size: 10px;
}
/* Fixed footer promotions */
.ethical-fixedfooter {
box-sizing: border-box;
position: fixed;
bottom: 0;
left: 0;
z-index: 100;
background-color: #eee;
border-top: 1px solid #bfbfbf;
font-size: 12px;
line-height: 1.5;
padding: 0.5em 1.5em;
text-align: center;
color: #404040;
width: 100%; /* Fallback for Opera Mini */
width: 100vw;
}
@media (min-width: 769px) {
/* Improve viewing on non-mobile */
.ethical-fixedfooter {
font-size: 13px;
padding: 1em 1.5em;
}
}
.ethical-fixedfooter .ethical-text:before {
margin-right: 4px;
padding: 2px 6px;
border-radius: 3px;
background-color: #4caf50;
color: #fff;
content: "Sponsored";
}
.ethical-fixedfooter .ethical-callout {
color: #999;
padding-left: 6px;
white-space: nowrap;
}
.ethical-fixedfooter a,
.ethical-fixedfooter a:hover,
.ethical-fixedfooter a:active,
.ethical-fixedfooter a:visited {
color: #404040;
text-decoration: none;
}
.ethical-fixedfooter .ethical-close {
position: absolute;
top: 0;
right: 5px;
font-size: 20px;
line-height: 20px;
}
/* RTD Theme specific customizations */
.wy-nav-side .ethical-rtd {
/* RTD theme doesn't correctly set the sidebar width */
max-width: 300px;
padding: 0 1em;
}
.ethical-rtd .ethical-sidebar {
/* RTD theme doesn't set sidebar text color */
color: #b3b3b3;
font-size: 14px;
line-height: 20px;
}
@media (min-width: 769px) {
/* Make sure the fixed footer ad is under the RTD theme version selector */
.wy-body-for-nav .ethical-fixedfooter {
padding-left: 300px;
}
}
/* Alabaster specific customizations */
.ethical-alabaster a.ethical-image-link {
/* Alabaster adds a border even to image links on hover */
border: 0 !important;
}
.ethical-alabaster hr {
/* Alabaster needs some extra spacing before the footer ad */
margin-top: 2em;
}
.ethical-alabaster::before {
/* Alabaster's search box above the ad is floating */
clear: both;
content: '';
display: table;
margin-top: 3em;
}
/* Dark theme */
.ethical-dark-theme .ethical-sidebar {
background-color: #4e4b4b;
border: 1px solid #a0a0a0;
color: #c2c2c2 !important;
}
.ethical-dark-theme a,
.ethical-dark-theme a:visited {
color: #e6e6e6 !important;
border-bottom: 0 !important;
}
.ethical-dark-theme .ethical-callout a {
color: #b3b3b3 !important;
}
/* Ad block nag */
.keep-us-sustainable {
padding: .5em;
margin: 1em auto;
text-align: center;
border: 1px dotted #8ECC4C;
max-width: 300px;
}
.keep-us-sustainable a,
.keep-us-sustainable a:hover,
.keep-us-sustainable a:visited {
text-decoration: none;
}
/* Read the Docs theme specific fixes */
.wy-nav-side .keep-us-sustainable {
margin: 1em 2em 1em 1em;
color: #b3b3b3;
}
.wy-nav-side .keep-us-sustainable a {
color: #efefef;
font-size: 14px;
line-height: 20px;
}
/* Margin between the search results */
.rtd_search_hits_spacing {
margin: 10px 0;
}
+3
View File
@@ -0,0 +1,3 @@
<!-- saved from url=(0011)about:blank -->
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></head><body></body></html>
@@ -0,0 +1,154 @@
/* Highlighting utilities for Sphinx HTML documentation. */
"use strict";
const SPHINX_HIGHLIGHT_ENABLED = true
/**
* highlight a given string on a node by wrapping it in
* span elements with the given class name.
*/
const _highlight = (node, addItems, text, className) => {
if (node.nodeType === Node.TEXT_NODE) {
const val = node.nodeValue;
const parent = node.parentNode;
const pos = val.toLowerCase().indexOf(text);
if (
pos >= 0 &&
!parent.classList.contains(className) &&
!parent.classList.contains("nohighlight")
) {
let span;
const closestNode = parent.closest("body, svg, foreignObject");
const isInSVG = closestNode && closestNode.matches("svg");
if (isInSVG) {
span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
} else {
span = document.createElement("span");
span.classList.add(className);
}
span.appendChild(document.createTextNode(val.substr(pos, text.length)));
const rest = document.createTextNode(val.substr(pos + text.length));
parent.insertBefore(
span,
parent.insertBefore(
rest,
node.nextSibling
)
);
node.nodeValue = val.substr(0, pos);
/* There may be more occurrences of search term in this node. So call this
* function recursively on the remaining fragment.
*/
_highlight(rest, addItems, text, className);
if (isInSVG) {
const rect = document.createElementNS(
"http://www.w3.org/2000/svg",
"rect"
);
const bbox = parent.getBBox();
rect.x.baseVal.value = bbox.x;
rect.y.baseVal.value = bbox.y;
rect.width.baseVal.value = bbox.width;
rect.height.baseVal.value = bbox.height;
rect.setAttribute("class", className);
addItems.push({ parent: parent, target: rect });
}
}
} else if (node.matches && !node.matches("button, select, textarea")) {
node.childNodes.forEach((el) => _highlight(el, addItems, text, className));
}
};
const _highlightText = (thisNode, text, className) => {
let addItems = [];
_highlight(thisNode, addItems, text, className);
addItems.forEach((obj) =>
obj.parent.insertAdjacentElement("beforebegin", obj.target)
);
};
/**
* Small JavaScript module for the documentation.
*/
const SphinxHighlight = {
/**
* highlight the search words provided in localstorage in the text
*/
highlightSearchWords: () => {
if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight
// get and clear terms from localstorage
const url = new URL(window.location);
const highlight =
localStorage.getItem("sphinx_highlight_terms")
|| url.searchParams.get("highlight")
|| "";
localStorage.removeItem("sphinx_highlight_terms")
url.searchParams.delete("highlight");
window.history.replaceState({}, "", url);
// get individual terms from highlight string
const terms = highlight.toLowerCase().split(/\s+/).filter(x => x);
if (terms.length === 0) return; // nothing to do
// There should never be more than one element matching "div.body"
const divBody = document.querySelectorAll("div.body");
const body = divBody.length ? divBody[0] : document.querySelector("body");
window.setTimeout(() => {
terms.forEach((term) => _highlightText(body, term, "highlighted"));
}, 10);
const searchBox = document.getElementById("searchbox");
if (searchBox === null) return;
searchBox.appendChild(
document
.createRange()
.createContextualFragment(
'<p class="highlight-link">' +
'<a href="javascript:SphinxHighlight.hideSearchWords()">' +
_("Hide Search Matches") +
"</a></p>"
)
);
},
/**
* helper function to hide the search marks again
*/
hideSearchWords: () => {
document
.querySelectorAll("#searchbox .highlight-link")
.forEach((el) => el.remove());
document
.querySelectorAll("span.highlighted")
.forEach((el) => el.classList.remove("highlighted"));
localStorage.removeItem("sphinx_highlight_terms")
},
initEscapeListener: () => {
// only install a listener if it is really needed
if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return;
document.addEventListener("keydown", (event) => {
// bail for input elements
if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
// bail with special keys
if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return;
if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) {
SphinxHighlight.hideSearchWords();
event.preventDefault();
}
});
},
};
_ready(() => {
/* Do not call highlightSearchWords() when we are on the search page.
* It will highlight words from the *previous* search query.
*/
if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords();
SphinxHighlight.initEscapeListener();
});
+90
View File
@@ -0,0 +1,90 @@
.sphinx-tabs {
margin-bottom: 1rem;
}
[role="tablist"] {
border-bottom: 1px solid #a0b3bf;
}
.sphinx-tabs-tab {
position: relative;
font-family: Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;
color: #1D5C87;
line-height: 24px;
margin: 0;
font-size: 16px;
font-weight: 400;
background-color: rgba(255, 255, 255, 0);
border-radius: 5px 5px 0 0;
border: 0;
padding: 1rem 1.5rem;
margin-bottom: 0;
}
.sphinx-tabs-tab[aria-selected="true"] {
font-weight: 700;
border: 1px solid #a0b3bf;
border-bottom: 1px solid white;
margin: -1px;
background-color: white;
}
.sphinx-tabs-tab:focus {
z-index: 1;
outline-offset: 1px;
}
.sphinx-tabs-panel {
position: relative;
padding: 1rem;
border: 1px solid #a0b3bf;
margin: 0px -1px -1px -1px;
border-radius: 0 0 5px 5px;
border-top: 0;
background: white;
}
.sphinx-tabs-panel.code-tab {
padding: 0.4rem;
}
.sphinx-tab img {
margin-bottom: 24 px;
}
/* Dark theme preference styling */
@media (prefers-color-scheme: dark) {
body[data-theme="auto"] .sphinx-tabs-panel {
color: white;
background-color: rgb(50, 50, 50);
}
body[data-theme="auto"] .sphinx-tabs-tab {
color: white;
background-color: rgba(255, 255, 255, 0.05);
}
body[data-theme="auto"] .sphinx-tabs-tab[aria-selected="true"] {
border-bottom: 1px solid rgb(50, 50, 50);
background-color: rgb(50, 50, 50);
}
}
/* Explicit dark theme styling */
body[data-theme="dark"] .sphinx-tabs-panel {
color: white;
background-color: rgb(50, 50, 50);
}
body[data-theme="dark"] .sphinx-tabs-tab {
color: white;
background-color: rgba(255, 255, 255, 0.05);
}
body[data-theme="dark"] .sphinx-tabs-tab[aria-selected="true"] {
border-bottom: 2px solid rgb(50, 50, 50);
background-color: rgb(50, 50, 50);
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,39 @@
const createThemeSwitcher = () => {
let btn = document.createElement('BUTTON');
btn.className = 'theme-switcher';
btn.id = 'themeSwitcher';
btn.innerHTML =
'<i id=themeMoon class="fa fa-moon-o"></i><i id=themeSun class="fa fa-sun-o"></i>';
document.body.appendChild(btn);
if (localStorage.getItem('theme') === 'dark') $('#themeMoon').hide(0);
else $('#themeSun').hide(0);
};
$(document).ready(() => {
createThemeSwitcher();
$('#themeSwitcher').click(switchTheme);
$('footer').html(
$('footer').html() +
'<a href="https://github.com/MrDogeBro/sphinx_rtd_dark_mode">Dark theme</a> provided by <a href="http://mrdogebro.com">MrDogeBro</a>.'
);
});
const switchTheme = () => {
if (localStorage.getItem('theme') === 'dark') {
localStorage.setItem('theme', 'light');
document.documentElement.setAttribute('data-theme', 'light');
$('#themeSun').fadeOut(200, () => {
$('#themeMoon').fadeIn(200);
});
} else {
localStorage.setItem('theme', 'dark');
document.documentElement.setAttribute('data-theme', 'dark');
$('#themeMoon').fadeOut(200, () => {
$('#themeSun').fadeIn(200);
});
}
};
+23
View File
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<!-- saved from url=(0090)https://googleads.g.doubleclick.net/pagead/html/r20250401/r20190131/zrt_lookup_fy2021.html -->
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><script>
(function(){'use strict';/*
Copyright The Closure Library Authors.
SPDX-License-Identifier: Apache-2.0
*/
var p=this||self;function q(){var a=p.navigator;return a&&(a=a.userAgent)?a:""};function x(a,b){Array.prototype.forEach.call(a,b,void 0)};function y(a){y[" "](a);return a}y[" "]=function(){};var aa=q().toLowerCase().indexOf("webkit")!=-1&&q().indexOf("Edge")==-1;function ba(a){var b=window;b.addEventListener&&b.addEventListener("load",a,!1)};/*
Copyright Google LLC
SPDX-License-Identifier: Apache-2.0
*/
let z=globalThis.trustedTypes,B;function ca(){let a=null;if(!z)return a;try{const b=c=>c;a=z.createPolicy("goog#html",{createHTML:b,createScript:b,createScriptURL:b})}catch(b){}return a};var C=class{constructor(a){this.g=a}toString(){return this.g+""}};function D(a){if(a instanceof C)return a.g;throw Error("");};var da=RegExp("^(?:([^:/?#.]+):)?(?://(?:([^\\\\/?#]*)@)?([^\\\\/?#]*?)(?::([0-9]+))?(?=[\\\\/?#]|$))?([^?#]+)?(?:\\?([^#]*))?(?:#([\\s\\S]*))?$");function E(a){try{var b;if(b=!!a&&a.location.href!=null)a:{try{y(a.foo);b=!0;break a}catch(c){}b=!1}return b}catch{return!1}}function F(a=!1){const b=[p.top],c=[];let d=0,f;for(;f=b[d++];){a&&!E(f)||c.push(f);try{if(f.frames)for(let e=0;e<f.frames.length&&b.length<1024;++e)b.push(f.frames[e])}catch{}}return c}function G(a,b){if(a)for(const c in a)Object.prototype.hasOwnProperty.call(a,c)&&b(a[c],c,a)}function ea(a=document){return a.createElement("img")};function fa(a){p.google_image_requests||(p.google_image_requests=[]);const b=ea(p.document);b.src=a;p.google_image_requests.push(b)};let H=null;function ha(){if(!H)b:{var a=F();for(var b=0;b<a.length;b++)try{const c=a[b].frames.google_esf;if(c&&E(c)){H=c;break b}}catch(c){}H=null}(a=H)?((b=a.esf_propArray)||(b=a.esf_propArray={}),a=b):a=null;return a?.[2]};var J=window;var K=/#(R?S)-(.*)/,ma=/^(\d+)-(.*)/;var na=class{constructor(a,b){this.error=a;this.meta={};this.context=b.context;this.msg=b.message||"";this.id=b.id||"jserror"}};function L(a){let b=a.toString();a.name&&b.indexOf(a.name)==-1&&(b+=": "+a.name);a.message&&b.indexOf(a.message)==-1&&(b+=": "+a.message);if(a.stack)a:{a=a.stack;var c=b;try{a.indexOf(c)==-1&&(a=c+"\n"+a);let d;for(;a!=d;)d=a,a=a.replace(RegExp("((https?:/..*/)[^/:]*:\\d+(?:.|\n)*)\\2"),"$1");b=a.replace(RegExp("\n *","g"),"\n");break a}catch(d){b=c;break a}b=void 0}return b};const oa=RegExp("^https?://(\\w|-)+\\.cdn\\.ampproject\\.(net|org)(\\?|/|$)");var pa=class{constructor(a,b){this.g=a;this.h=b}},qa=class{constructor(a,b){this.url=a;this.g=!!b;this.depth=null}};let M=null;function ra(){const a=p.performance;return a&&a.now&&a.timing?Math.floor(a.now()+a.timing.navigationStart):Date.now()}function sa(){const a=p.performance;return a&&a.now?a.now():null};var ta=class{constructor(a,b){var c=sa()||ra();this.label=a;this.type=b;this.value=c;this.duration=0;this.taskId=this.slotId=void 0;this.uniqueId=Math.random()}};const N=p.performance,ua=!!(N&&N.mark&&N.measure&&N.clearMarks),Q=function(a){let b=!1,c;return function(){b||(c=a(),b=!0);return c}}(()=>{var a;if(a=ua){var b;a=window;if(M===null){M="";try{let c="";try{c=a.top.location.hash}catch(d){c=a.location.hash}c&&(M=(b=c.match(/\bdeid=([\d,]+)/))?b[1]:"")}catch(c){}}b=M;a=!!b.indexOf&&b.indexOf("1337")>=0}return a});function va(a){a&&N&&Q()&&(N.clearMarks(`goog_${a.label}_${a.uniqueId}_start`),N.clearMarks(`goog_${a.label}_${a.uniqueId}_end`))};function R(a,b){const c={};c[a]=b;return[c]}function wa(a,b,c,d,f){const e=[];G(a,(g,l)=>{(g=xa(g,b,c,d,f))&&e.push(`${l}=${g}`)});return e.join(b)}
function xa(a,b,c,d,f){if(a==null)return"";b=b||"&";c=c||",$";typeof c==="string"&&(c=c.split(""));if(a instanceof Array){if(d||(d=0),d<c.length){const e=[];for(let g=0;g<a.length;g++)e.push(xa(a[g],b,c,d+1,f));return e.join(c[d])}}else if(typeof a==="object")return f||(f=0),f<2?encodeURIComponent(wa(a,b,c,d,f+1)):"...";return encodeURIComponent(String(a))}function ya(a){let b=1;for(const c in a.h)c.length>b&&(b=c.length);return 3997-b-a.i.length-1}
function za(a,b){let c="https://pagead2.googlesyndication.com"+b,d=ya(a)-b.length;if(d<0)return"";a.g.sort((e,g)=>e-g);b=null;let f="";for(let e=0;e<a.g.length;e++){const g=a.g[e],l=a.h[g];for(let h=0;h<l.length;h++){if(!d){b=b==null?g:b;break}let m=wa(l[h],a.i,",$");if(m){m=f+m;if(d>=m.length){d-=m.length;c+=m;f=a.i;break}b=b==null?g:b}}}a="";b!=null&&(a=`${f}${"trn"}=${b}`);return c+a}var S=class{constructor(){this.i="&";this.h={};this.j=0;this.g=[]}};var Ba=class{constructor(a=null){this.l=T;this.h=a;this.g=null;this.i=!1;this.m=this.j}j(a,b,c,d,f){f=f||"jserror";let e=void 0;try{const t=new S;var g=t;g.g.push(1);g.h[1]=R("context",a);b.error&&b.meta&&b.id||(b=new na(b,{message:L(b)}));g=b;if(g.msg){b=t;var l=g.msg.substring(0,512);b.g.push(2);b.h[2]=R("msg",l)}var h=g.meta||{};l=h;if(this.g)try{this.g(l)}catch(n){}if(d)try{d(l)}catch(n){}d=t;h=[h];d.g.push(3);d.h[3]=h;var m;if(!(m=r)){d=p;h=[];l=null;do{var k=d;if(E(k)){var v=k.location.href;
l=k.document&&k.document.referrer||null}else v=l,l=null;h.push(new qa(v||""));try{d=k.parent}catch(n){d=null}}while(d&&k!==d);for(let n=0,ia=h.length-1;n<=ia;++n)h[n].depth=ia-n;k=p;if(k.location&&k.location.ancestorOrigins&&k.location.ancestorOrigins.length===h.length-1)for(v=1;v<h.length;++v){const n=h[v];n.url||(n.url=k.location.ancestorOrigins[v-1]||"",n.g=!0)}m=h}var r=m;let I=new qa(p.location.href,!1);m=null;const O=r.length-1;for(k=O;k>=0;--k){var u=r[k];!m&&oa.test(u.url)&&(m=u);if(u.url&&
!u.g){I=u;break}}u=null;const Ga=r.length&&r[O].url;I.depth!==0&&Ga&&(u=r[O]);e=new pa(I,u);if(e.h){r=t;var w=e.h.url||"";r.g.push(4);r.h[4]=R("top",w)}var P={url:e.g.url||""};if(e.g.url){const n=e.g.url.match(da);var A=n[1],ja=n[3],ka=n[4];w="";A&&(w+=A+":");ja&&(w+="//",w+=ja,ka&&(w+=":"+ka));var la=w}else la="";A=t;P=[P,{url:la}];A.g.push(5);A.h[5]=P;Aa(this.l,f,t,this.i,c)}catch(t){try{Aa(this.l,f,{context:"ecmserr",rctx:a,msg:L(t),url:e?.g.url??""},this.i,c)}catch(I){}}return!0}};function Aa(a,b,c,d=!1,f){if((d?a.g:Math.random())<(f||.01))try{let e;c instanceof S?e=c:(e=new S,G(c,(l,h)=>{var m=e;const k=m.j++;l=R(h,l);m.g.push(k);m.h[k]=l}));const g=za(e,"/pagead/gen_204?id="+b+"&");g&&fa(g)}catch(e){}}function Ca(){var a=T,b=window.google_srt;b>=0&&b<=1&&(a.g=b)}var Da=class{constructor(){this.g=Math.random()}};let T,U;
const V=new class{constructor(a,b){this.h=[];this.i=b||p;let c=null;b&&(b.google_js_reporting_queue=b.google_js_reporting_queue||[],this.h=b.google_js_reporting_queue,c=b.google_measure_js_timing);this.g=Q()||(c!=null?c:Math.random()<a)}start(a,b){if(!this.g)return null;a=new ta(a,b);b=`goog_${a.label}_${a.uniqueId}_start`;N&&Q()&&N.mark(b);return a}end(a){if(this.g&&typeof a.value==="number"){a.duration=(sa()||ra())-a.value;var b=`goog_${a.label}_${a.uniqueId}_end`;N&&Q()&&N.mark(b);!this.g||this.h.length>
2048||this.h.push(a)}}}(1,window);function Ea(){window.google_measure_js_timing||(V.g=!1,V.h!==V.i.google_js_reporting_queue&&(Q()&&x(V.h,va),V.h.length=0))}(function(a){T=a??new Da;typeof window.google_srt!=="number"&&(window.google_srt=Math.random());Ca();U=new Ba(V);U.g=()=>{};U.i=!0;window.document.readyState==="complete"?Ea():V.g&&ba(()=>{Ea()})})();function Fa(a){U.g=b=>{x(a,c=>{c(b)})}};function Ha(a){a=a===null?"null":a===void 0?"undefined":a;B===void 0&&(B=ca());var b=B;return new C(b?b.createHTML(a):a)};var W;if(W=aa){var X="IFRAME",Ia=document;X=String(X);Ia.contentType==="application/xhtml+xml"&&(X=X.toLowerCase());W="srcdoc"in Ia.createElement(X)}const Ja=W;function Ka(a,b){a.open("text/html","replace");b=Ha(String(b));a.write(D(b));a.close()};function La(a){var b=F(!0).find(c=>!!c.google_reactive_sra_lookup)?.google_reactive_sra_lookup;return b?b[a]:(b=ha())?b[a]:null};(function(a){try{const b=/\/(r\d+|dev)\/r\d+\/zrt_lookup\.html/.exec(a.location.pathname);b&&b[1]&&Fa([c=>{c.shv=b[1]}])}catch(b){}})(window);function Ma(){var a=(K.exec("#"+J.name)||K.exec(J.location.href))?.[2];if(a&&(a=decodeURIComponent(a),a=ma.exec(a))&&(a=+a[1],!isNaN(a)&&(a=La(a)))){a=a.creative;let c=null;try{c=J.frameElement}catch(d){}var b;if(b=c)try{b=E(c.contentWindow)}catch(d){b=!1}b?(b=c,Ja?(a=Ha(a),b.srcdoc=D(a)):(b=b.contentWindow)&&Ka(b.document,a)):Ka(J.document,a)}}var Y=U;let Z;
try{Y.h&&Y.h.g?(Z=Y.h.start((200).toString(),3),Ma(),Y.h.end(Z)):Ma()}catch(a){let b=!0;try{va(Z),b=Y.m(200,new na(a,{message:L(a)}),void 0,void 0)}catch(c){Y.j(217,c)}if(b)window.console?.error?.(a);else throw a;};}).call(this);
</script>
</head><body></body></html>
+180 -274
View File
@@ -1,286 +1,192 @@
// app/api/client.ts
import { mockData, type MockApiResponse } from './mock';
/**
* API响应类型
*/
export type ApiResponse<T> = {
data?: T;
error?: string;
status: number;
};
data?: T;
error?: string;
status: number;
headers?: Record<string, string>; // 添加对响应头的支持
};
export type QueryParams = Record<string, string | number | boolean | undefined>;
// 获取 API 基础 URL
const API_BASE_URL = '172.16.0.119:9000/admin';
// 是否使用模拟数据(开发环境使用)
const USE_MOCK_DATA = false; // 设置为true使用模拟数据,避免API连接问题
/**
* 构建完整的 API URL
*/
function buildUrl(endpoint: string, params?: QueryParams): string {
// 创建 URL 字符串
const url = new URL(
endpoint.startsWith('http') ? endpoint : `http://${API_BASE_URL}${endpoint.startsWith('/') ? endpoint : `/${endpoint}`}`,
typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000'
);
export type QueryParams = Record<string, string | number | boolean | undefined>;
// 基本数据类型
interface BaseItem {
id: string;
[key: string]: unknown;
}
// 为模拟数据预定义类型定义(导出以允许其他文件引用)
// 这些类型被用在模拟数据中,虽然没有直接引用
export interface Document extends BaseItem {
name: string;
type: string;
size: number;
status: string;
uploadDate: string;
lastModified: string;
}
export interface Rule extends BaseItem {
code: string;
name: string;
ruleType: string;
groupId: string;
groupName: string;
priority: string;
description: string;
isActive: boolean;
createdAt: string;
updatedAt: string;
}
export interface RuleGroup extends BaseItem {
name: string;
description: string;
isActive: boolean;
}
export interface CountItem extends BaseItem {
count: number;
}
// 获取 API 基础 URL,支持服务器端和客户端环境
const API_BASE_URL = typeof process !== 'undefined' && process.env.API_BASE_URL
? process.env.API_BASE_URL
: 'http://nas.7bm.co:54302/api/docauditai'; // 如果服务器不可用,会自动使用模拟数据
// 获取 API 访问令牌
const API_TOKEN = typeof process !== 'undefined' && process.env.API_TOKEN
? process.env.API_TOKEN
: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYXBpX3VzZXIifQ.KVLm7rZOF0MuX3MR9LqiYA14gba-MaDK_EQXrJ9u5_Y';
// 是否使用模拟数据(开发环境使用)
const USE_MOCK_DATA = true; // 设置为 true 可启用模拟数据
/**
* 构建完整的 API URL,支持服务器端和客户端环境
*/
function buildUrl(endpoint: string, params?: QueryParams): string {
// 创建 URL 字符串
const url = new URL(
endpoint.startsWith('http') ? endpoint : API_BASE_URL + endpoint,
// 服务器端使用绝对 URL,客户端使用相对 URL
typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000'
);
// 添加查询参数
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
url.searchParams.append(key, String(value));
}
});
}
return url.toString();
}
// 超时控制
const fetchWithTimeout = async (url: string, options: RequestInit, timeout = 5000) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
...options,
signal: controller.signal
});
clearTimeout(id);
return response;
} catch (error) {
clearTimeout(id);
throw error;
}
};
// 模拟数据库响应的类型
type MockData = {
[key: string]: BaseItem[];
};
// 模拟数据库响应
const mockDataResponses: MockData = {
// 文档列表模拟数据
'/documents': [
{
id: '1',
name: '合同.pdf',
type: 'contract',
size: 1024000,
status: 'approved',
uploadDate: new Date().toISOString(),
lastModified: new Date().toISOString()
},
{
id: '2',
name: '报告.docx',
type: 'report',
size: 512000,
status: 'pending',
uploadDate: new Date().toISOString(),
lastModified: new Date().toISOString()
// 添加查询参数
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
url.searchParams.append(key, String(value));
}
],
// 规则列表模拟数据
'/evaluation_points': [
{
id: '1',
code: 'R001',
name: '合同名称',
ruleType: 'essential',
groupId: '1',
groupName: '合同基本要素类检查',
priority: 'high',
description: '文档必须包含合同名称',
isActive: true,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
},
{
id: '2',
code: 'R002',
name: '合同编号',
ruleType: 'legal',
groupId: '2',
groupName: '销售合同专项检查',
priority: 'medium',
description: '文档必须包含合同编号',
isActive: true,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
}
],
// 评查点组列表模拟数据
'/evaluation_point_groups': [
{ id: '1', name: '合同基本要素类检查', description: '合同基本要素类检查', isActive: true },
{ id: '2', name: '销售合同专项检查', description: '销售合同专项检查', isActive: true },
{ id: '3', name: '采购合同专项检查', description: '采购合同专项检查', isActive: true },
{ id: '4', name: '专卖许可证审核规则', description: '专卖许可证审核规则', isActive: true },
{ id: '5', name: '行政处罚规范性检查', description: '行政处罚规范性检查', isActive: true }
],
// 计数查询 - 为了满足 BaseItem 类型,添加 id 字段
'/evaluation_points/count': [{ id: 'count', count: 2 }],
'/documents/count': [{ id: 'count', count: 2 }]
};
});
}
/**
* 获取模拟响应数据
* @param endpoint API端点
* @param params 查询参数
* @returns 模拟的响应数据
*/
function getMockResponse<T>(endpoint: string, params?: QueryParams): ApiResponse<T> {
console.log(`[开发模式] 使用模拟数据: ${endpoint}`);
// 移除开头的斜杠以便于匹配
const path = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;
// 检查是否有匹配的路径
for (const [mockPath, mockData] of Object.entries(mockDataResponses)) {
const normalizedMockPath = mockPath.startsWith('/') ? mockPath.substring(1) : mockPath;
if (path === normalizedMockPath || path.startsWith(normalizedMockPath + '/') || path.startsWith(normalizedMockPath + '?')) {
// 如果 ID 路径参数 (如 /rules/1),返回单个项目
const pathParts = path.split('/');
const mockPathParts = normalizedMockPath.split('/');
return url.toString();
}
// 超时控制
const fetchWithTimeout = async (url: string, options: RequestInit, timeout = 5000) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
...options,
signal: controller.signal
});
clearTimeout(id);
return response;
} catch (error) {
clearTimeout(id);
throw error;
}
};
/**
* 获取模拟响应数据
*/
function getMockResponse<T>(endpoint: string): ApiResponse<T> {
console.log(`[开发模式] 使用模拟数据: ${endpoint}`);
// 移除开头的斜杠以便于匹配
const path = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;
// 查找匹配的模拟数据
for (const mockPath in mockData) {
const normalizedMockPath = mockPath.startsWith('/') ? mockPath.substring(1) : mockPath;
if (path === normalizedMockPath || path.startsWith(normalizedMockPath + '/')) {
// 如果是详情查询 (如 /evaluation_points/1)
if (path.includes('/') && path !== normalizedMockPath) {
const id = parseInt(path.split('/')[1]);
const mockDataItem = mockData[mockPath as keyof typeof mockData] as MockApiResponse<unknown>;
if (pathParts.length > mockPathParts.length && !isNaN(Number(pathParts[mockPathParts.length]))) {
const id = pathParts[mockPathParts.length];
const item = mockData.find(i => i.id === id);
return item
? { data: item as unknown as T, status: 200 }
: { error: '未找到', status: 404 };
if (Array.isArray(mockDataItem.data)) {
const item = mockDataItem.data.find((item: {id: number}) => item.id === id);
if (item) {
return {
data: {
code: 0,
msg: "成功",
data: item
} as unknown as T,
status: 200
};
}
}
// 处理分页
if (params?.limit && params?.offset) {
const limit = Number(params.limit);
const offset = Number(params.offset);
const paginatedData = mockData.slice(offset, offset + limit);
return { data: paginatedData as unknown as T, status: 200 };
}
// 返回完整数据
return { data: mockData as unknown as T, status: 200 };
}
}
// 没有匹配的模拟数据
return { error: '没有匹配的模拟数据', status: 404 };
}
/**
* 通用 API 请求函数
*/
export async function apiRequest<T>(
endpoint: string,
options: RequestInit = {},
params?: QueryParams
): Promise<ApiResponse<T>> {
// 如果使用模拟数据,直接返回模拟响应
if (USE_MOCK_DATA) {
return getMockResponse<T>(endpoint, params);
}
try {
// 构建 URL
const url = buildUrl(endpoint, params);
// 设置默认请求头
const headers = new Headers(options.headers || {});
if (!headers.has('Content-Type') && options.method !== 'GET') {
headers.set('Content-Type', 'application/json');
return { error: '未找到数据', status: 404 };
}
// 数据库连接授权信息
if (!headers.has('Authorization')) {
headers.set('Authorization', `Bearer ${API_TOKEN}`);
}
// 发送请求,5秒超时
const response = await fetchWithTimeout(url, {
...options,
headers
}, 5000);
// 解析响应
let data = null;
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json') && response.status !== 204) {
data = await response.json();
}
if (!response.ok) {
console.error(`API请求失败: ${response.status} - ${url}`);
return {
error: data?.message || `请求失败: ${response.status}`,
status: response.status
};
}
return {
data,
status: response.status
};
} catch (error) {
console.error('API请求失败:', error);
// 如果超时或网络错误,使用模拟数据(仅开发环境)
if (process.env.NODE_ENV !== 'production') {
console.warn('自动使用模拟数据作为回退');
return getMockResponse<T>(endpoint, params);
}
return {
error: error instanceof Error ? error.message : '未知错误',
status: 500
// 返回列表数据
return {
data: mockData[mockPath as keyof typeof mockData] as unknown as T,
status: 200
};
}
}
}
return { error: '没有匹配的模拟数据', status: 404 };
}
/**
* 通用 API 请求函数
*/
export async function apiRequest<T>(
endpoint: string,
options: RequestInit = {},
params?: QueryParams
): Promise<ApiResponse<T>> {
// 如果使用模拟数据,直接返回模拟响应
if (USE_MOCK_DATA) {
return getMockResponse<T>(endpoint);
}
try {
// 构建 URL
const url = buildUrl(endpoint, params);
// 设置默认请求头
const headers = new Headers(options.headers || {});
if (!headers.has('Content-Type') && options.method !== 'GET') {
headers.set('Content-Type', 'application/json');
}
if (!headers.has('Accept')) {
headers.set('Accept', 'application/json');
}
// 发送请求,5秒超时
const response = await fetchWithTimeout(url, {
...options,
headers
}, 10000);
// 解析响应
let data = null;
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json') && response.status !== 204) {
data = await response.json();
}
// 收集响应头信息
const responseHeaders: Record<string, string> = {};
response.headers.forEach((value, key) => {
responseHeaders[key] = value;
});
// 检查API返回的状态码
if (data && 'code' in data && data.code !== 0) {
console.error(`API请求失败: ${data.msg || '未知错误'} - ${url}`);
return {
error: data.msg || '请求失败',
status: response.status,
headers: responseHeaders
};
}
if (!response.ok) {
console.error(`HTTP请求失败: ${response.status} - ${url}`);
return {
error: data?.msg || `请求失败: ${response.status}`,
status: response.status,
headers: responseHeaders
};
}
return {
data,
status: response.status,
headers: responseHeaders
};
} catch (error) {
console.error('API请求失败:', error);
// 如果超时或网络错误,使用模拟数据(仅开发环境)
if (process.env.NODE_ENV !== 'production') {
console.warn('自动使用模拟数据作为回退');
return getMockResponse<T>(endpoint);
}
return {
error: error instanceof Error ? error.message : '未知错误',
status: 500
};
}
}
+467 -68
View File
@@ -1,4 +1,5 @@
import { postgrestGet, postgrestPost, postgrestPut, postgrestDelete } from '../postgrest-client';
import { postgrestGet, postgrestPost, postgrestPut, postgrestDelete, type PostgrestParams } from '../postgrest-client';
import dayjs from 'dayjs';
/**
* 评查点列表查询参数
@@ -10,6 +11,8 @@ export interface RulesQueryParams {
groupId?: string;
isActive?: boolean;
keyword?: string;
orderBy?: string;
orderDirection?: 'asc' | 'desc';
}
/**
@@ -21,7 +24,48 @@ export interface RulesListResponse {
}
/**
* 评查点详情
* API返回的评查点详情
*/
export interface ApiRule {
id: number;
code: string;
name: string;
evaluation_point_groups_id: number;
risk: string;
description: string;
is_enabled: boolean;
evaluation_point_groups?: {
name: string;
};
references_laws: Record<string, unknown>;
extraction_config: {
type: string;
fields: string[];
prompt_setting?: {
type: string;
template: string;
};
};
evaluation_config: {
rules: Array<{
id: string;
type: string;
config: Record<string, unknown>;
}>;
logicType: string;
};
pass_message: string;
fail_message: string;
suggestion_message: string;
suggestion_message_type: string;
post_action: string;
action_config: string;
created_at: string;
updated_at: string;
}
/**
* 评查点详情(前端模型)
*/
export interface Rule {
id: string;
@@ -38,13 +82,42 @@ export interface Rule {
}
/**
* 评查点分组
* 映射API返回的评查点数据到前端模型
* @param apiRule API返回的评查点数据
* @returns 前端评查点模型
*/
export interface RuleGroup {
id: string;
name: string;
description: string;
isActive: boolean;
function mapApiRuleToFrontendModel(apiRule: ApiRule): Rule {
// 风险等级映射到优先级
const priorityMap: Record<string, string> = {
'高': 'high',
'中': 'medium',
'低': 'low'
};
// 规则类型映射(这里根据实际业务逻辑设置一个默认值)
const ruleType = 'essential'; // 实际应用中可能需要从其他字段推断
// 优先使用关联查询获取的分组名称,如果不存在则使用默认值
const groupName = apiRule.evaluation_point_groups?.name || `${apiRule.evaluation_point_groups_id}`;
return {
id: apiRule.id.toString(),
code: apiRule.code,
name: apiRule.name,
ruleType: ruleType,
groupId: apiRule.evaluation_point_groups_id.toString(),
groupName: groupName,
priority: priorityMap[apiRule.risk] || 'medium',
description: apiRule.description,
isActive: apiRule.is_enabled,
createdAt: apiRule.created_at,
updatedAt: apiRule.updated_at
};
}
// 格式化日期的辅助函数
function formatDate(dateString: string): string {
return dayjs(dateString).format('YYYY-MM-DD HH:mm:ss');
}
/**
@@ -54,68 +127,190 @@ export interface RuleGroup {
*/
export async function getRulesList(params: RulesQueryParams): Promise<{data: RulesListResponse; error?: never} | {data?: never; error: string; status?: number}> {
try {
// 构建 PostgREST 查询参数
const { page = 1, pageSize = 10, ...filters } = params;
const offset = (page - 1) * pageSize;
// 解构并设置默认值
const {
page = 1,
pageSize = 10,
ruleType,
groupId,
isActive,
keyword,
orderBy = 'created_at',
orderDirection = 'desc'
} = params;
// 构建过滤条件
const filterArray: Record<string, unknown> = {};
if (filters.ruleType) {
filterArray['rule_type'] = `eq.${filters.ruleType}`;
}
if (filters.groupId) {
filterArray['group_id'] = `eq.${filters.groupId}`;
}
if (filters.isActive !== undefined) {
filterArray['is_active'] = `eq.${filters.isActive}`;
}
if (filters.keyword) {
// 关键字搜索
filterArray['or'] = `name.ilike.*${filters.keyword}*,code.ilike.*${filters.keyword}*`;
}
// 执行多个 API 调用(获取评查点列表、总数和评查点组)
const [rulesResponse, countResponse] = await Promise.all([
postgrestGet<Rule[]>('/evaluation_points', {
select: '*',
order: 'created_at.desc',
limit: pageSize,
offset,
filter: filterArray
}),
// 构建PostgrestParams参数
const postgrestParams: PostgrestParams = {
// 使用PostgREST资源嵌入语法获取关联数据
// 这里使用外键关系自动关联evaluation_point_groups表
select: `
id,
code,
name,
evaluation_point_groups_id,
evaluation_point_groups(name),
risk,
description,
is_enabled,
created_at,
updated_at
`,
// 设置分页
limit: pageSize,
offset: (page - 1) * pageSize,
postgrestGet<[{count: number}]>('/evaluation_points/count', {
filter: filterArray
}),
// 设置排序
order: `${orderBy}.${orderDirection}`,
]);
// 处理错误情况
if (rulesResponse.error) {
return { error: `获取评查点列表失败: ${rulesResponse.error}`, status: 500 };
}
if (countResponse.error) {
return { error: `获取评查点总数失败: ${countResponse.error}`, status: 500 };
}
// 确保数据不为 undefined,提供默认值
const rules = rulesResponse.data || [];
const totalCount = countResponse.data && countResponse.data[0] ? countResponse.data[0].count : 0;
// 成功返回数据
return {
data: {
rules,
totalCount,
// 构建过滤条件
filter: {},
// 添加额外头部,用于获取总记录数
headers: {
'Prefer': 'count=exact'
}
};
// 添加精确匹配过滤
if (groupId) {
postgrestParams.filter!['evaluation_point_groups_id'] = `eq.${groupId}`;
}
if (isActive !== undefined) {
postgrestParams.filter!['is_enabled'] = `eq.${isActive}`;
}
// 添加模糊搜索
if (keyword) {
// 使用PostgREST的or条件查询
// 同时搜索name和code字段
postgrestParams.or = [
{ name: `ilike.*${keyword}*` },
{ code: `ilike.*${keyword}*` }
];
}
// 使用postgrestGet发送请求
const response = await postgrestGet<{code: number; msg: string; data: ApiRule[]}>('evaluation_points', postgrestParams);
// 检查是否有错误响应
if (response.error) {
return { error: response.error, status: response.status };
}
// 确保响应数据存在且符合预期格式
if (!response.data || !response.data.data || !Array.isArray(response.data.data)) {
return { error: '接口返回数据格式不正确', status: 500 };
}
// 获取API返回的所有评查点
const apiRules = response.data.data;
// 从响应头中获取总记录数(如果存在)
// 注意:这里假设总记录数会在接口响应的某个位置返回
// 实际使用时,需要根据 PostgREST 的响应格式进行调整
let totalCount = 0;
// 尝试从响应中获取总数
if (response.headers && response.headers['content-range']) {
// 例如 Content-Range: 0-9/42 表示总共有 42 条记录
const range = response.headers['content-range'];
const total = range.split('/')[1];
if (total !== '*') { // '*' 表示未知总数
totalCount = parseInt(total, 10);
}
} else {
// 如果没有响应头,则使用当前返回的数据长度作为默认值
// 这种情况下分页可能不准确
totalCount = apiRules.length;
console.warn('未能从响应中获取总记录数,使用当前页数据长度作为默认值');
}
// 打印第一个数据项来查看结构(仅用于调试)
if (apiRules.length > 0) {
console.log('评查点数据示例:', JSON.stringify(apiRules[0], null, 2));
}
// 收集所有分组ID (多进行一步查找表的操作)
const groupIds = [...new Set(apiRules.map(rule => rule.evaluation_point_groups_id))];
// 如果有分组ID,查询分组信息
const groupsMap: Record<string, string> = {};
if (groupIds.length > 0) {
try {
// 构建查询参数
const groupsParams: PostgrestParams = {
select: 'id,name',
filter: {
'id': `in.(${groupIds.join(',')})`
}
};
// 查询评查点分组表
const groupsResponse = await postgrestGet<{code: number; msg: string; data: {id: number; name: string}[]}>('evaluation_point_groups', groupsParams);
if (groupsResponse.data?.data) {
// 创建ID到名称的映射
groupsResponse.data.data.forEach(group => {
groupsMap[group.id.toString()] = group.name;
});
// 打印分组数据(仅用于调试)
console.log('分组数据:', groupsMap);
}
} catch (error) {
console.error('获取分组数据失败:', error);
// 失败不阻止主流程,使用默认分组名
}
}
// 应用本地过滤 - 只在API不支持的情况下使用
let filteredRules = [...apiRules];
// 如果有ruleType过滤(API暂不支持),在本地过滤
if (ruleType) {
// 注意:这是本地过滤,实际情况下最好在API层面支持
filteredRules = filteredRules.filter(() => {
// 实现一个映射逻辑,比如根据其他字段推导ruleType
const derivedType = 'essential'; // 此处应为实际推导逻辑
return derivedType === ruleType;
});
}
// 如果进行了本地过滤,则需要调整总记录数
if (ruleType) {
totalCount = filteredRules.length;
}
// 将API返回的数据映射到前端模型,并附加分组名称
const mappedRules = filteredRules.map(apiRule => {
// 创建修改版的apiRule,添加从分组映射获取的名称
const enhancedApiRule = {
...apiRule,
// 从映射中获取分组名称,如果不存在则使用默认值
evaluation_point_groups: {
name: groupsMap[apiRule.evaluation_point_groups_id.toString()] || `${apiRule.evaluation_point_groups_id}`
// name: apiRule.evaluation_point_groups?.name || `${apiRule.evaluation_point_groups_id}`
}
};
const rule = mapApiRuleToFrontendModel(enhancedApiRule);
// 格式化日期字段
rule.createdAt = formatDate(rule.createdAt);
rule.updatedAt = formatDate(rule.updatedAt);
return rule;
});
// 返回结果
return {
data: {
rules: mappedRules,
totalCount
}
};
} catch (error) {
console.error('获取评查点列表出错:', error);
return {
@@ -131,7 +326,86 @@ export async function getRulesList(params: RulesQueryParams): Promise<{data: Rul
* @returns 评查点详情
*/
export async function getRule(id: string): Promise<{data: Rule; error?: never} | {data?: never; error: string; status?: number}> {
return postgrestGet<Rule>(`/evaluation_points/${id}`);
try {
// 使用postgrestGet获取单个评查点数据
const postgrestParams: PostgrestParams = {
// 使用PostgREST资源嵌入语法获取关联数据
select: `
id,
code,
name,
evaluation_point_groups_id,
risk,
description,
is_enabled,
references_laws,
extraction_config,
evaluation_config,
pass_message,
fail_message,
suggestion_message,
suggestion_message_type,
post_action,
action_config,
created_at,
updated_at
`
};
// 获取评查点详情
const response = await postgrestGet<{code: number; msg: string; data: ApiRule}>(`evaluation_points/${id}`, postgrestParams);
// 检查是否有错误响应
if (response.error) {
return { error: response.error, status: response.status };
}
// 确保响应数据存在且符合预期格式
if (!response.data || !response.data.data) {
return { error: '接口返回数据格式不正确', status: 500 };
}
const apiRule = response.data.data;
// 获取分组信息
try {
if (apiRule.evaluation_point_groups_id) {
const groupParams: PostgrestParams = {
select: 'id,name',
filter: {
'id': `eq.${apiRule.evaluation_point_groups_id}`
}
};
// 查询评查点分组
const groupResponse = await postgrestGet<{code: number; msg: string; data: {id: number; name: string}[]}>('evaluation_point_groups', groupParams);
if (groupResponse.data?.data && groupResponse.data.data.length > 0) {
// 将分组信息添加到评查点数据中
const group = groupResponse.data.data[0];
apiRule.evaluation_point_groups = { name: group.name };
}
}
} catch (error) {
console.error('获取分组信息失败:', error);
// 忽略错误,使用默认分组名
}
// 将API返回的数据映射到前端模型
const rule = mapApiRuleToFrontendModel(apiRule);
// 格式化日期字段
rule.createdAt = formatDate(rule.createdAt);
rule.updatedAt = formatDate(rule.updatedAt);
return { data: rule };
} catch (error) {
console.error('获取评查点详情出错:', error);
return {
error: error instanceof Error ? error.message : '获取评查点详情失败',
status: 500
};
}
}
/**
@@ -140,7 +414,57 @@ export async function getRule(id: string): Promise<{data: Rule; error?: never} |
* @returns 创建的评查点
*/
export async function createRule(ruleData: Omit<Rule, 'id' | 'createdAt' | 'updatedAt'>): Promise<{data: Rule; error?: never} | {data?: never; error: string; status?: number}> {
return postgrestPost<Rule, Omit<Rule, 'id' | 'createdAt' | 'updatedAt'>>('/evaluation_points', ruleData);
try {
// 将前端模型转换为API接受的格式
const apiRuleData = {
code: ruleData.code,
name: ruleData.name,
evaluation_point_groups_id: parseInt(ruleData.groupId),
risk: ruleData.priority === 'high' ? '高' : ruleData.priority === 'medium' ? '中' : '低',
description: ruleData.description,
is_enabled: ruleData.isActive,
// 以下是默认值,实际应用中需要根据业务逻辑设置
references_laws: {},
extraction_config: {
type: "OCR+LLM",
fields: []
},
evaluation_config: {
rules: [],
logicType: "and"
},
pass_message: "",
fail_message: "",
suggestion_message: "",
suggestion_message_type: "warning",
post_action: "none",
action_config: ""
};
// 使用postgrestPost创建评查点
const response = await postgrestPost<{code: number; msg: string; data: ApiRule}, typeof apiRuleData>('evaluation_points', apiRuleData);
// 检查是否有错误响应
if (response.error) {
return { error: response.error, status: response.status };
}
// 确保响应数据存在且符合预期格式
if (!response.data || !response.data.data) {
return { error: '接口返回数据格式不正确', status: 500 };
}
// 将API返回的数据映射到前端模型
const rule = mapApiRuleToFrontendModel(response.data.data);
return { data: rule };
} catch (error) {
console.error('创建评查点出错:', error);
return {
error: error instanceof Error ? error.message : '创建评查点失败',
status: 500
};
}
}
/**
@@ -150,7 +474,58 @@ export async function createRule(ruleData: Omit<Rule, 'id' | 'createdAt' | 'upda
* @returns 更新后的评查点
*/
export async function updateRule(id: string, ruleData: Partial<Omit<Rule, 'id' | 'createdAt' | 'updatedAt'>>): Promise<{data: Rule; error?: never} | {data?: never; error: string; status?: number}> {
return postgrestPut<Rule, Partial<Omit<Rule, 'id' | 'createdAt' | 'updatedAt'>>>(`/evaluation_points/${id}`, ruleData);
try {
// 构建API接受的更新数据
const apiRuleData: Record<string, unknown> = {};
if (ruleData.code !== undefined) {
apiRuleData.code = ruleData.code;
}
if (ruleData.name !== undefined) {
apiRuleData.name = ruleData.name;
}
if (ruleData.groupId !== undefined) {
apiRuleData.evaluation_point_groups_id = parseInt(ruleData.groupId);
}
if (ruleData.priority !== undefined) {
apiRuleData.risk = ruleData.priority === 'high' ? '高' : ruleData.priority === 'medium' ? '中' : '低';
}
if (ruleData.description !== undefined) {
apiRuleData.description = ruleData.description;
}
if (ruleData.isActive !== undefined) {
apiRuleData.is_enabled = ruleData.isActive;
}
// 使用postgrestPut更新评查点
const response = await postgrestPut<{code: number; msg: string; data: ApiRule}, typeof apiRuleData>(`evaluation_points/${id}`, apiRuleData);
// 检查是否有错误响应
if (response.error) {
return { error: response.error, status: response.status };
}
// 确保响应数据存在且符合预期格式
if (!response.data || !response.data.data) {
return { error: '接口返回数据格式不正确', status: 500 };
}
// 将API返回的数据映射到前端模型
const rule = mapApiRuleToFrontendModel(response.data.data);
return { data: rule };
} catch (error) {
console.error('更新评查点出错:', error);
return {
error: error instanceof Error ? error.message : '更新评查点失败',
status: 500
};
}
}
/**
@@ -159,7 +534,31 @@ export async function updateRule(id: string, ruleData: Partial<Omit<Rule, 'id' |
* @returns 删除结果
*/
export async function deleteRule(id: string): Promise<{data: Rule; error?: never} | {data?: never; error: string; status?: number}> {
return postgrestDelete<Rule>(`/evaluation_points/${id}`);
try {
// 使用postgrestDelete删除评查点
const response = await postgrestDelete<{code: number; msg: string; data: ApiRule}>(`evaluation_points/${id}`);
// 检查是否有错误响应
if (response.error) {
return { error: response.error, status: response.status };
}
// 确保响应数据存在且符合预期格式
if (!response.data || !response.data.data) {
return { error: '接口返回数据格式不正确', status: 500 };
}
// 将API返回的数据映射到前端模型
const rule = mapApiRuleToFrontendModel(response.data.data);
return { data: rule };
} catch (error) {
console.error('删除评查点出错:', error);
return {
error: error instanceof Error ? error.message : '删除评查点失败',
status: 500
};
}
}
/**
+33
View File
@@ -0,0 +1,33 @@
/**
* 评查点分组模拟数据
*/
export const evaluationPointGroupsMockData = {
code: 0,
msg: "成功",
data: [
{
id: 1,
name: "行政处罚",
description: "行政处罚评查点",
is_enabled: true
},
{
id: 2,
name: "专卖许可证",
description: "专卖许可证评查点",
is_enabled: true
},
{
id: 3,
name: "合同审核",
description: "合同审核评查点",
is_enabled: true
},
{
id: 4,
name: "立案文书",
description: "立案文书评查点",
is_enabled: true
}
]
};
+177
View File
@@ -0,0 +1,177 @@
/**
* 评查点模拟数据
*/
export const evaluationPointsMockData = {
code: 0,
msg: "成功",
data: [
{
id: 5,
code: "LIAN_WEN_SHU_WAN_ZHENG_XING_JIAN_CHA",
name: "立案文书完整性检查",
evaluation_point_groups_id: 1,
risk: "高",
description: "须立案而没有立案文书的(2分)",
is_enabled: true,
references_laws: {},
extraction_config: {
type: "OCR+LLM",
fields: ["立案报告表-负责人意见"]
},
evaluation_config: {
rules: [
{
id: "规则1",
type: "exists",
config: {
logic: "and",
fields: ["立案报告表-负责人意见"]
}
},
{
id: "规则2",
type: "regex",
config: {
field: "立案报告表-负责人意见",
pattern: "同意",
matchType: "match"
}
}
],
logicType: "and"
},
pass_message: "立案文件合格,负责人意见已填写且同意。",
fail_message: "立案文件不合格,负责人意见没有内容,请核对检查。",
suggestion_message: "请检查立案报告表中的负责人意见栏,确保已填写且同意。",
suggestion_message_type: "warning",
post_action: "none",
action_config: "",
created_at: "2023-10-01T00:00:00+00:00",
updated_at: "2023-10-01T00:00:00+00:00"
},
{
id: 6,
code: "AN_JIAN_LAI_YUAN_YI_ZHI_XING_JIAO_YAN",
name: "案件来源一致性校验",
evaluation_point_groups_id: 1,
risk: "中",
description: "没有记载案件来源或案件来源与其他文书不一致的(0.5分)",
is_enabled: true,
references_laws: {},
extraction_config: {
type: "OCR+LLM",
fields: ["立案报告表-案件来源", "案件处理审批表-案件来源", "案件调查终结报告-案件来源"]
},
evaluation_config: {
rules: [
{
id: "规则1",
type: "exists",
config: {
logic: "and",
fields: ["立案报告表-案件来源", "案件处理审批表-案件来源", "案件调查终结报告-案件来源"]
}
}
],
logicType: "and"
},
pass_message: "案件来源完整",
fail_message: "案件来源信息不一致或缺失,请核对。",
suggestion_message: "请检查立案报告表、案件处理审批表和案件调查终结报告中的案件来源信息,确保一致。",
suggestion_message_type: "warning",
post_action: "none",
action_config: "",
created_at: "2023-10-01T00:00:00+00:00",
updated_at: "2023-10-01T00:00:00+00:00"
},
{
id: 7,
code: "AN_YOU_FA_AN_SHI_JIAN_HE_FA_AN_DI_DIAN_JI_ZAI_ZHUN_QUE_XING_YOU_WU",
name: "案由、发案时间和发案地点记载准确性-有无",
evaluation_point_groups_id: 1,
risk: "高",
description: "没有记载或错误记载案由、发案时间和发案地点的(1分)",
is_enabled: true,
references_laws: {},
extraction_config: {
type: "OCR+LLM",
fields: ["立案报告表-案由", "立案报告表-案件来源", "案件处理审批表-案件来源", "案件调查终结报告-案件来源"],
prompt_setting: {
type: "custom",
template: "从立案报告表中抽取案由信息。"
}
},
evaluation_config: {
rules: [
{
id: "规则1",
type: "exists",
config: {
logic: "and",
fields: ["立案报告表-案由"]
}
}
],
logicType: "and"
},
pass_message: "案由、发案时间和发案地点记录准确。",
fail_message: "案由、发案时间和发案地点记录有误或缺失,请核对。",
suggestion_message: "请检查立案报告表中的案由信息,确保已填写。",
suggestion_message_type: "warning",
post_action: "none",
action_config: "",
created_at: "2023-10-01T00:00:00+00:00",
updated_at: "2023-10-01T00:00:00+00:00"
},
{
id: 8,
code: "AN_YOU_FA_AN_SHI_JIAN_HE_FA_AN_DI_DIAN_JI_ZAI_ZHUN_QUE_XING_YI_ZHI",
name: "案由、发案时间和发案地点记载准确性-一致",
evaluation_point_groups_id: 1,
risk: "高",
description: "案由、发案时间和发案地点记载不一致的(1分)",
is_enabled: true,
references_laws: {},
extraction_config: {
type: "LLM-VL",
fields: ["立案报告表-案发时间", "立案报告表-案发地址", "现场笔录-检查时间", "现场笔录-检查地点"],
prompt_setting: {
type: "system",
template: "从立案报告表和现场笔录中抽取案发时间、案发地址、检查时间和检查地点信息。"
}
},
evaluation_config: {
rules: [
{
id: "规则1",
type: "consistency",
config: {
logic: "and",
pairs: [
{
sourceField: "立案报告表-案发时间",
targetField: "现场笔录-检查时间",
compareMethod: "exact"
},
{
sourceField: "立案报告表-案发地址",
targetField: "现场笔录-检查地点",
compareMethod: "semantic"
}
]
}
}
],
logicType: "and"
},
pass_message: "案由、发案时间和发案地点记录准确。",
fail_message: "案由、发案时间和发案地点记录有误或缺失,请核对。",
suggestion_message: "请检查立案报告表和现场笔录中的案发时间和地点信息,确保一致。",
suggestion_message_type: "warning",
post_action: "none",
action_config: "",
created_at: "2023-10-01T00:00:00+00:00",
updated_at: "2023-10-01T00:00:00+00:00"
}
]
};
+22
View File
@@ -0,0 +1,22 @@
/**
* 统一导出所有模拟数据
*/
import { evaluationPointsMockData } from './evaluation_points';
import { evaluationPointGroupsMockData } from './evaluation_point_groups';
/**
* 模拟数据响应格式
*/
export interface MockApiResponse<T> {
code: number;
msg: string;
data: T;
}
/**
* 系统模拟数据
*/
export const mockData = {
'/evaluation_points': evaluationPointsMockData,
'/evaluation_point_groups': evaluationPointGroupsMockData
};
+204 -13
View File
@@ -12,7 +12,147 @@ export interface PostgrestParams {
offset?: number;
filter?: Record<string, unknown>;
schema?: string; // 指定 PostgreSQL schema
or?: Array<Record<string, unknown>> | string; // 支持OR条件查询
[key: string]: unknown; // 允许添加其他参数
headers?: Record<string, string>;
}
/**
* 打印 PostgREST 查询日志
* @param endpoint 端点
* @param params 参数
*/
function logPostgrestQuery(endpoint: string, params?: QueryParams): void {
if (process.env.NODE_ENV !== 'production') {
const baseUrl = 'http://172.16.0.119:9000/admin';
console.log('\n📦 PostgREST 查询日志 ========================');
console.log(`📦 API 端点: ${baseUrl}/${endpoint}`);
if (params && Object.keys(params).length > 0) {
console.log('📦 查询参数:');
// 以可读格式单独打印每个参数
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
if (key === 'select' && typeof value === 'string') {
// 美化 select 参数,使其看起来像 SQL 查询
console.log(` - ${key}:`);
const fields = value.replace(/\s+/g, ' ').trim().split(',');
fields.forEach(field => {
console.log(` ${field.trim()}`);
});
} else if (key === 'order' && typeof value === 'string') {
// 格式化排序参数
console.log(` - ${key}: ${value.replace(/\./g, ' ')}`); // 例如:created_at desc
} else if (key === 'or' && typeof value === 'string') {
// 格式化OR条件
console.log(` - ${key}: ${value.replace(/\./g, ' -> ').replace(/,/g, ' 或 ')}`);
} else {
console.log(` - ${key}: ${JSON.stringify(value)}`);
}
}
});
// 构建人类可读的简化URL
const readableQueryString = Object.entries(params)
.filter(([, value]) => value !== undefined)
.map(([key, value]) => {
if (key === 'select' && typeof value === 'string') {
// 简化select查询
return `${key}=...`;
}
return `${key}=${value}`;
})
.join('&');
console.log(`\n📦 可读URL: ${baseUrl}/${endpoint}${readableQueryString ? '?' + readableQueryString : ''}`);
// 格式化查询为 PostgreSQL 风格的查询
let postgrestQuery = `SELECT `;
if (params.select && typeof params.select === 'string') {
postgrestQuery += params.select.replace(/\s+/g, ' ').trim();
} else {
postgrestQuery += '*';
}
postgrestQuery += ` FROM ${endpoint}`;
const conditions: string[] = [];
// 添加过滤条件
if (params.filter) {
Object.entries(params.filter).forEach(([key, value]) => {
if (value !== undefined && typeof value === 'string') {
// 解析 eq.X, neq.X, gt.X 等
const parts = value.toString().split('.');
if (parts.length >= 2) {
const operator = parts[0];
const operatorMap: Record<string, string> = {
'eq': '=',
'neq': '!=',
'gt': '>',
'gte': '>=',
'lt': '<',
'lte': '<=',
'like': 'LIKE',
'ilike': 'ILIKE'
};
const sqlOperator = operatorMap[operator] || operator;
const value = parts.slice(1).join('.');
conditions.push(`${key} ${sqlOperator} '${value}'`);
}
}
});
}
// 添加 OR 条件
if (params.or) {
if (typeof params.or === 'string') {
if (params.or.startsWith('(') && params.or.endsWith(')')) {
// 处理 or=(cond1,cond2) 格式
const orConditions = params.or.slice(1, -1).split(',').map(cond => {
const [field, operator] = cond.split('.');
return `${field} ${operator.replace(/ilike/i, 'ILIKE')}`;
});
if (orConditions.length > 0) {
conditions.push(`(${orConditions.join(' OR ')})`);
}
} else {
// 简单 or 条件
conditions.push(params.or);
}
}
}
// 添加WHERE子句
if (conditions.length > 0) {
postgrestQuery += ` WHERE ${conditions.join(' AND ')}`;
}
// 添加ORDER BY
if (params.order && typeof params.order === 'string') {
const [field, direction] = params.order.split('.');
postgrestQuery += ` ORDER BY ${field} ${direction.toUpperCase()}`;
}
// 添加LIMIT和OFFSET
if (params.limit !== undefined) {
postgrestQuery += ` LIMIT ${params.limit}`;
}
if (params.offset !== undefined) {
postgrestQuery += ` OFFSET ${params.offset}`;
}
console.log('\n📦 等效SQL查询:');
console.log(postgrestQuery);
console.log('=========================================\n');
}
}
}
/**
@@ -47,6 +187,30 @@ export function transformParams(params: PostgrestParams): QueryParams {
result.schema = params.schema;
}
// 处理或条件 (OR) - 两种格式支持
if (params.or) {
// 如果是字符串格式 (例如 "name.ilike.*keyword*,code.ilike.*keyword*")
if (typeof params.or === 'string') {
result.or = params.or;
}
// 如果是数组格式, 转换为 PostgREST 的 or=(condition1,condition2) 格式
else if (Array.isArray(params.or)) {
const orConditions: string[] = [];
params.or.forEach(condition => {
const entries = Object.entries(condition);
if (entries.length === 1) {
const [field, operator] = entries[0];
orConditions.push(`${field}.${operator}`);
}
});
if (orConditions.length > 0) {
result.or = `(${orConditions.join(',')})`;
}
}
}
// 处理过滤条件 - PostgresREST 格式
if (params.filter) {
Object.entries(params.filter).forEach(([key, value]) => {
@@ -61,7 +225,7 @@ export function transformParams(params: PostgrestParams): QueryParams {
// 处理其他额外参数
Object.entries(params).forEach(([key, value]) => {
// 跳过已处理的特殊参数
if (!['select', 'order', 'limit', 'offset', 'filter', 'schema'].includes(key) && value !== undefined) {
if (!['select', 'order', 'limit', 'offset', 'filter', 'schema', 'or'].includes(key) && value !== undefined) {
result[key] = value as string | number | boolean;
}
});
@@ -75,15 +239,29 @@ export function transformParams(params: PostgrestParams): QueryParams {
* @param params 查询参数
* @returns 响应数据
*/
export async function postgrestGet<T>(endpoint: string, params?: PostgrestParams): Promise<{data: T; error?: never} | {data?: never; error: string; status?: number}> {
export async function postgrestGet<T>(endpoint: string, params?: PostgrestParams): Promise<{data: T; headers?: Record<string, string>; error?: never} | {data?: never; error: string; status?: number}> {
try {
const queryParams = params ? transformParams(params) : {};
// 添加前缀表示使用 docauditai 数据库
const apiEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
// 确保端点没有前导斜杠,因为API_BASE_URL已经包含了路径前缀
const apiEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;
// 打印查询信息
logPostgrestQuery(apiEndpoint, queryParams);
// 提取并移除自定义头部参数
const headers: Record<string, string> = params?.headers || {};
// 清除查询参数中的headers属性,避免将其作为URL参数
if (queryParams.headers) {
delete queryParams.headers;
}
const response = await apiRequest<T>(
apiEndpoint,
{ method: 'GET' },
{
method: 'GET',
headers: headers
},
queryParams
);
@@ -91,7 +269,11 @@ export async function postgrestGet<T>(endpoint: string, params?: PostgrestParams
throw new Error(response.error);
}
return { data: response.data as T };
// 返回数据和响应头
return {
data: response.data as T,
headers: response.headers // 假设apiRequest函数已返回响应头
};
} catch (error) {
const apiError = handleApiError(error);
return { error: apiError.message, status: apiError.status };
@@ -106,8 +288,11 @@ export async function postgrestGet<T>(endpoint: string, params?: PostgrestParams
*/
export async function postgrestPost<T, D = Record<string, unknown>>(endpoint: string, data: D): Promise<{data: T; error?: never} | {data?: never; error: string; status?: number}> {
try {
// 添加前缀表示使用 docauditai 数据库
const apiEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
// 确保端点没有前导斜杠
const apiEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;
// 打印查询信息(POST请求只打印端点)
logPostgrestQuery(apiEndpoint);
const response = await apiRequest<T>(
apiEndpoint,
@@ -139,13 +324,16 @@ export async function postgrestPost<T, D = Record<string, unknown>>(endpoint: st
*/
export async function postgrestPut<T, D = Record<string, unknown>>(endpoint: string, data: D): Promise<{data: T; error?: never} | {data?: never; error: string; status?: number}> {
try {
// 添加前缀表示使用 docauditai 数据库
const apiEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
// 确保端点没有前导斜杠
const apiEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;
// 打印查询信息(PUT请求只打印端点)
logPostgrestQuery(apiEndpoint);
const response = await apiRequest<T>(
apiEndpoint,
{
method: 'PUT',
method: 'PATCH',
body: JSON.stringify(data),
headers: {
'Prefer': 'return=representation'
@@ -171,8 +359,11 @@ export async function postgrestPut<T, D = Record<string, unknown>>(endpoint: str
*/
export async function postgrestDelete<T>(endpoint: string): Promise<{data: T; error?: never} | {data?: never; error: string; status?: number}> {
try {
// 添加前缀表示使用 docauditai 数据库
const apiEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
// 确保端点没有前导斜杠
const apiEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;
// 打印查询信息(DELETE请求只打印端点)
logPostgrestQuery(apiEndpoint);
const response = await apiRequest<T>(
apiEndpoint,
+14 -34
View File
@@ -6,6 +6,8 @@ import { FileIcon } from "~/components/ui/FileIcon";
import { FilterPanel, FilterSelect, SearchFilter } from "~/components/ui/FilterPanel";
import { Pagination } from "~/components/ui/Pagination";
import { Table } from "~/components/ui/Table";
import { FileTypeTag } from "~/components/ui/FileTypeTag";
import { StatusBadge } from "~/components/ui/StatusBadge";
import rulesFilesStyles from "~/styles/pages/rules-files.css?url";
export const links = () => [
@@ -371,22 +373,6 @@ export default function RulesFiles() {
{ value: DateRange.CUSTOM, label: '自定义时间段' }
];
// 获取文件状态对应的图标和类名
const getStatusInfo = (status: ReviewStatus) => {
switch (status) {
case ReviewStatus.PASS:
return { icon: "ri-checkbox-circle-line", className: "success" };
case ReviewStatus.WARNING:
return { icon: "ri-alert-line", className: "warning" };
case ReviewStatus.FAIL:
return { icon: "ri-close-circle-line", className: "error" };
case ReviewStatus.PENDING:
return { icon: "ri-time-line", className: "processing" };
default:
return { icon: "", className: "default" };
}
};
// 定义表格列配置
const columns = [
{
@@ -414,14 +400,11 @@ export default function RulesFiles() {
key: "fileType",
width: "12%",
render: (_: unknown, file: ReviewFile) => (
<span className={`file-type-tag file-type-tag-${file.fileType}`}>
{file.fileType === FileType.CONTRACT && <i className="ri-file-list-3-line"></i>}
{file.fileType === FileType.LICENSE && <i className="ri-vip-crown-line"></i>}
{file.fileType === FileType.PUNISHMENT && <i className="ri-scales-line"></i>}
{file.fileType === FileType.REPORT && <i className="ri-file-chart-line"></i>}
{file.fileType === FileType.OTHER && <i className="ri-file-line"></i>}
{FILE_TYPE_LABELS[file.fileType]}
</span>
<FileTypeTag
type={file.fileType}
text={FILE_TYPE_LABELS[file.fileType]}
showIcon={true}
/>
)
},
{
@@ -440,16 +423,13 @@ export default function RulesFiles() {
title: "评查状态",
key: "reviewStatus",
width: "12%",
render: (_: unknown, file: ReviewFile) => {
const statusInfo = getStatusInfo(file.reviewStatus);
return (
<span className={`status-badge status-badge-${statusInfo.className.replace('status-', '')}`}>
<i className={`${statusInfo.icon} mr-1`}></i>
{REVIEW_STATUS_LABELS[file.reviewStatus]}
{file.issueCount > 0 && ` (${file.issueCount})`}
</span>
);
}
render: (_: unknown, file: ReviewFile) => (
<StatusBadge
status={file.reviewStatus}
text={file.issueCount > 0 ? `${REVIEW_STATUS_LABELS[file.reviewStatus]} (${file.issueCount})` : REVIEW_STATUS_LABELS[file.reviewStatus]}
showIcon={true}
/>
)
},
{
title: "问题摘要",
+77 -192
View File
@@ -6,13 +6,13 @@ import { Card } from '~/components/ui/Card';
import { Tag } from '~/components/ui/Tag';
import { StatusDot } from '~/components/ui/StatusDot';
import rulesStyles from "~/styles/pages/rules_index.css?url";
import type { Rule } from '~/models/rule';
import type { Rule, RuleType, RulePriority } from '~/models/rule';
import { RULE_TYPE_LABELS, RULE_TYPE_COLORS, RULE_PRIORITY_LABELS, RULE_PRIORITY_COLORS } from '~/models/rule';
import type { TagColor } from '~/components/ui/Tag';
import { Table } from '~/components/ui/Table';
import { FilterPanel, FilterSelect, SearchFilter } from '~/components/ui/FilterPanel';
import { Pagination } from '~/components/ui/Pagination';
// import { getRulesList } from '~/api/evaluation_points/rules';
import { getRulesList } from '~/api/evaluation_points/rules';
export const links = () => [
{ rel: "stylesheet", href: rulesStyles }
@@ -27,159 +27,47 @@ export const meta: MetaFunction = () => {
];
};
// 模拟数据 - 用于开发阶段展示UI
const mockRules: Rule[] = [
{
id: '1',
code: 'EP001',
name: '合同名称要素检查',
ruleType: 'essential',
ruleGroupId: '1',
groupName: '合同基本要素类检查',
priority: 'high',
description: '检查合同是否包含清晰的合同名称',
checkMethod: 'automatic',
prompt: '查找文档中的合同名称',
isActive: true,
createdAt: '2024-03-15T08:30:00Z',
updatedAt: '2024-03-15T08:30:00Z'
},
{
id: '2',
code: 'EP002',
name: '合同编号要素检查',
ruleType: 'essential',
ruleGroupId: '1',
groupName: '合同基本要素类检查',
priority: 'high',
description: '检查合同是否包含唯一的合同编号',
checkMethod: 'automatic',
prompt: '查找文档中的合同编号',
isActive: true,
createdAt: '2024-03-15T09:15:00Z',
updatedAt: '2024-03-15T09:15:00Z'
},
{
id: '3',
code: 'EP003',
name: '合同主体资格检查',
ruleType: 'legal',
ruleGroupId: '2',
groupName: '销售合同专项检查',
priority: 'medium',
description: '检查合同签署方是否具有合法的主体资格',
checkMethod: 'manual',
prompt: '确认合同签署方的法律主体资格',
isActive: true,
createdAt: '2024-03-16T10:20:00Z',
updatedAt: '2024-03-16T10:20:00Z'
},
{
id: '4',
code: 'EP004',
name: '付款条件检查',
ruleType: 'content',
ruleGroupId: '2',
groupName: '销售合同专项检查',
priority: 'medium',
description: '检查合同中的付款条件是否明确',
checkMethod: 'automatic',
prompt: '提取文档中的付款条件相关内容',
isActive: true,
createdAt: '2024-03-17T11:30:00Z',
updatedAt: '2024-03-17T11:30:00Z'
},
{
id: '5',
code: 'EP005',
name: '违约责任条款检查',
ruleType: 'legal',
ruleGroupId: '3',
groupName: '采购合同专项检查',
priority: 'high',
description: '检查合同是否包含违约责任条款',
checkMethod: 'mixed',
prompt: '提取文档中的违约责任相关条款',
isActive: true,
createdAt: '2024-03-18T13:45:00Z',
updatedAt: '2024-03-18T13:45:00Z'
},
{
id: '6',
code: 'EP006',
name: '合同文本格式检查',
ruleType: 'format',
ruleGroupId: '1',
groupName: '合同基本要素类检查',
priority: 'low',
description: '检查合同文本格式是否符合规范',
checkMethod: 'automatic',
prompt: '检查文档的整体格式规范性',
isActive: false,
createdAt: '2024-03-19T14:50:00Z',
updatedAt: '2024-03-19T14:50:00Z'
},
{
id: '7',
code: 'EP007',
name: '专卖许可证有效性检查',
ruleType: 'legal',
ruleGroupId: '4',
groupName: '专卖许可证审核规则',
priority: 'high',
description: '检查专卖许可证是否在有效期内',
checkMethod: 'automatic',
prompt: '提取专卖许可证有效期信息并判断有效性',
isActive: true,
createdAt: '2024-03-20T15:55:00Z',
updatedAt: '2024-03-20T15:55:00Z'
},
{
id: '8',
code: 'EP008',
name: '处罚决定书格式检查',
ruleType: 'format',
ruleGroupId: '5',
groupName: '行政处罚规范性检查',
priority: 'medium',
description: '检查行政处罚决定书格式是否规范',
checkMethod: 'automatic',
prompt: '检查处罚决定书的格式规范性',
isActive: true,
createdAt: '2024-03-21T16:00:00Z',
updatedAt: '2024-03-21T16:00:00Z'
},
{
id: '9',
code: 'EP009',
name: '处罚依据合法性检查',
ruleType: 'legal',
ruleGroupId: '5',
groupName: '行政处罚规范性检查',
priority: 'high',
description: '检查行政处罚依据是否合法',
checkMethod: 'manual',
prompt: '审核处罚依据的法律合法性',
isActive: true,
createdAt: '2024-03-22T09:10:00Z',
updatedAt: '2024-03-22T09:10:00Z'
},
{
id: '10',
code: 'EP010',
name: '业务特殊条款检查',
ruleType: 'business',
ruleGroupId: '3',
groupName: '采购合同专项检查',
priority: 'medium',
description: '检查合同是否包含烟草行业特殊条款',
checkMethod: 'mixed',
prompt: '识别文档中的烟草行业特殊要求条款',
isActive: true,
createdAt: '2024-03-23T10:15:00Z',
updatedAt: '2024-03-23T10:15:00Z'
}
];
// 声明loader返回的数据类型
export type LoaderData = {
rules: Rule[];
totalCount: number;
currentPage: number;
pageSize: number;
totalPages: number;
};
// API返回的数据映射到前端模型
interface ApiRule {
id: string;
code: string;
name: string;
ruleType: string;
groupId: string;
groupName: string;
priority: string;
description: string;
isActive: boolean;
createdAt: string;
updatedAt: string;
}
function mapApiRuleToModel(apiRule: ApiRule): Rule {
return {
id: apiRule.id,
code: apiRule.code,
name: apiRule.name,
ruleType: apiRule.ruleType as RuleType, // 类型转换
ruleGroupId: apiRule.groupId,
groupName: apiRule.groupName,
priority: apiRule.priority as RulePriority, // 类型转换
description: apiRule.description,
checkMethod: 'automatic', // 默认值
prompt: apiRule.description, // 使用描述作为默认prompt
isActive: apiRule.isActive,
createdAt: apiRule.createdAt,
updatedAt: apiRule.updatedAt
};
}
export async function loader({ request }: LoaderFunctionArgs) {
const url = new URL(request.url);
@@ -195,34 +83,23 @@ export async function loader({ request }: LoaderFunctionArgs) {
};
try {
// 使用模拟数据而不是API调用
// const response = await getRulesList(params);
// 使用API调用获取数据
const response = await getRulesList(params);
// 过滤模拟数据
let filteredRules = [...mockRules];
if (params.ruleType) {
filteredRules = filteredRules.filter(rule => rule.ruleType === params.ruleType);
// API错误处理集中在rules.ts中,这里只需检查是否有错误
if (response.error) {
throw new Error(response.error);
}
if (params.groupId) {
filteredRules = filteredRules.filter(rule => rule.ruleGroupId === params.groupId);
if (!response.data) {
throw new Error('API返回数据为空');
}
if (params.isActive !== undefined) {
filteredRules = filteredRules.filter(rule => rule.isActive === params.isActive);
}
const apiRules = response.data.rules;
const totalCount = response.data.totalCount;
const rules = apiRules.map((apiRule: ApiRule) => mapApiRuleToModel(apiRule));
if (params.keyword) {
const keyword = params.keyword.toLowerCase();
filteredRules = filteredRules.filter(
rule => rule.name.toLowerCase().includes(keyword) ||
rule.code.toLowerCase().includes(keyword)
);
}
// 计算总记录数
const totalCount = filteredRules.length;
// 计算总页数
const totalPages = Math.ceil(totalCount / params.pageSize);
// 验证页码范围
@@ -232,12 +109,8 @@ export async function loader({ request }: LoaderFunctionArgs) {
return redirect(newUrl.pathname + newUrl.search);
}
// 分页
const offset = (params.page - 1) * params.pageSize;
const paginatedRules = filteredRules.slice(offset, offset + params.pageSize);
return json({
rules: paginatedRules,
return Response.json({
rules,
totalCount,
currentPage: params.page,
pageSize: params.pageSize,
@@ -260,7 +133,7 @@ export async function action({ request }: LoaderFunctionArgs) {
const ruleId = formData.get('ruleId');
if (!ruleId) {
return json({ success: false, error: "缺少评查点ID" }, { status: 400 });
return Response.json({ success: false, error: "缺少评查点ID" }, { status: 400 });
}
try {
@@ -416,18 +289,25 @@ export default function RulesIndex() {
title: "评查点编码",
dataIndex: "code" as keyof Rule,
key: "code",
align: "center" as const
align: "left" as const,
width: "20%",
className: "whitespace-normal break-all",
render: (value: string) => (
<div className="whitespace-normal break-all overflow-visible">{value}</div>
)
},
{
title: "评查点名称",
dataIndex: "name" as keyof Rule,
key: "name",
align: "center" as const
align: "left" as const,
width: "20%"
},
{
title: "评查点类型",
key: "ruleType",
align: "center" as const,
align: "left" as const,
width: "12%",
render: (_: unknown, record: Rule) => {
const typeColor = RULE_TYPE_COLORS[record.ruleType] as TagColor;
return (
@@ -441,12 +321,14 @@ export default function RulesIndex() {
title: "所属规则组",
dataIndex: "groupName" as keyof Rule,
key: "groupName",
align: "center" as const
align: "left" as const,
width: "10%"
},
{
title: "优先级",
key: "priority",
align: "center" as const,
align: "left" as const,
width: "5%",
render: (_: unknown, record: Rule) => {
const priorityColor = RULE_PRIORITY_COLORS[record.priority] as TagColor;
return (
@@ -459,7 +341,8 @@ export default function RulesIndex() {
{
title: "状态",
key: "isActive",
align: "center" as const,
align: "left" as const,
width: "8%",
className: "status-column",
render: (_: unknown, record: Rule) => (
<StatusDot status={record.isActive} text={record.isActive ? "启用" : "禁用"} />
@@ -469,12 +352,14 @@ export default function RulesIndex() {
title: "创建时间",
dataIndex: "createdAt" as keyof Rule,
key: "createdAt",
align: "center" as const
align: "left" as const,
width: "10%"
},
{
title: "操作",
key: "operation",
align: "center" as const,
align: "left" as const,
width: "10%",
render: (_: unknown, record: Rule) => (
<div className="operations-cell">
<Link to={`/rules/${record.id}`} className="operation-btn">
+1 -1
View File
@@ -90,7 +90,7 @@
}
.ant-btn-primary {
@apply bg-[#00684a] text-white hover:bg-[#005a3f] focus:ring-[#00684a];
@apply bg-[#00684a] text-white hover:bg-[#005a3f] focus:ring-[#00684a] hover:text-white;
}
.ant-btn-default {
+6 -27
View File
@@ -69,42 +69,21 @@
border-collapse: collapse;
}
.rules-page .ant-table th {
background-color: #f9f9f9;
font-weight: 500;
padding: 10px;
text-align: center;
border-bottom: 1px solid #e9ecef;
color: #333;
font-size: 14px;
}
.rules-page .ant-table td {
padding: 10px;
border-bottom: 1px solid #e9ecef;
font-weight: 400;
text-align: center;
vertical-align: middle;
font-size: 14px;
}
.rules-page .ant-table tr:hover {
background-color: rgba(0, 0, 0, 0.02);
}
/* 使用Table组件时的样式 */
.rules-page .rules-table th {
text-align: center !important;
/* 评查点编码列自动换行 */
.rules-page .rules-table .break-all {
word-break: break-all;
white-space: normal;
}
.rules-page .rules-table td {
text-align: center !important;
}
.rules-page .ant-table .status-column {
text-align: center;
width: 80px;
}
/* 表格操作列样式 */
.rules-page .operations-cell {
+1
View File
File diff suppressed because one or more lines are too long
+6
View File
@@ -9,6 +9,7 @@
"@remix-run/node": "^2.16.2",
"@remix-run/react": "^2.16.2",
"@remix-run/serve": "^2.16.2",
"dayjs": "^1.11.13",
"isbot": "^4.1.0",
"pg": "^8.14.1",
"react": "^18.2.0",
@@ -4084,6 +4085,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/dayjs": {
"version": "1.11.13",
"resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz",
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
},
"node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz",
+1
View File
@@ -14,6 +14,7 @@
"@remix-run/node": "^2.16.2",
"@remix-run/react": "^2.16.2",
"@remix-run/serve": "^2.16.2",
"dayjs": "^1.11.13",
"isbot": "^4.1.0",
"pg": "^8.14.1",
"react": "^18.2.0",