封装评查点的相关接口,完成评查点列表的简单搜索和查询
This commit is contained in:
+1339
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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -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
@@ -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
@@ -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
@@ -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">
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Generated
+6
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user