update 更新编辑器;完成动态代码测试

This commit is contained in:
LittleBoy 2024-12-28 19:57:48 +08:00
parent f0a1b687b7
commit a2e27103a4
20 changed files with 1385 additions and 80 deletions

View File

@ -12,6 +12,7 @@
"dependencies": {
"@tiptap/core": "^2.10.4",
"@tiptap/pm": "^2.10.4",
"@tiptap/suggestion": "^2.10.4",
"@tiptap/vue-3": "^2.10.4",
"ant-design-vue": "^4.2.6",
"autoprefixer": "^10.4.20",

View File

@ -4,7 +4,6 @@ import PageLoading from "@/components/page-loading/index.vue";
import { useUserStore } from "@/service/user-store.ts";
import Login from "@/components/login/index.vue";
//
const store = useUserStore()

View File

@ -35,10 +35,8 @@
position: relative;
}
.icon-loading{
display: inline-block;
margin-right: 5px;
animation: loading-360 linear 1s infinite;
vertical-align: middle;
display: inline-flex;
align-items: center;
color: inherit;

View File

@ -6,6 +6,7 @@ const props = defineProps<{
block?: boolean;
shape?: 'default' | 'circle' | 'round';
loading?: boolean;
icon?:import("vue-types").VueTypeValidableDef<any>
}>()
const btnClass = {
'btn-primary': props.type == 'primary',
@ -20,6 +21,7 @@ const btnClass = {
<template>
<button class="btn" :class="btnClass">
<span v-if="props.loading" class="icon-loading"></span>
<template v-else-if="!!props.icon" ><props.icon /></template>
<span><slot/></span>
</button>
</template>

View File

@ -0,0 +1,17 @@
<template>
<div class="bg-blue-500 p-2 rounded">
<textarea ref="txtBox" class="w-full block rounded outline-none"></textarea>
</div>
</template>
<script setup lang="ts">
import {onMounted, ref} from "vue";
const txtBox = ref<HTMLTextAreaElement>();
const content = defineModel<string>()
onMounted(() => {
txtBox.value?.addEventListener('input', () => {
content.value = txtBox.value?.value || ''
}, false)
})
</script>

View File

@ -1,36 +1,61 @@
<template>
<EditorContent :editor="editor" />
<EditorContent :editor="editor"/>
</template>
<script setup lang="ts">
import { Editor, EditorContent } from '@tiptap/vue-3'
import { extensions, Placeholder } from './tiptap'
import { watch, onMounted, onUnmounted } from 'vue'
import {Editor, EditorContent} from '@tiptap/vue-3'
import {extensions, Mention, Placeholder} from './tiptap'
import {watch, onMounted, onUnmounted} from 'vue'
import vars from "@/pages/result/vars.ts";
defineOptions({
name: 'Tiptap',
})
const props = defineProps<{
placeholder?: string
}>()
const model = defineModel<string>()
const editor = new Editor({
content: ``,
// extensions: [StarterKit],
extensions: [
...extensions,
Mention.configure({
HTMLAttributes: {
class: 'result-var',
},
suggestion: vars
}),
Placeholder.configure({
placeholder: props.placeholder || '请输入内容',
})
}),
]
})
function getMergeContent(){
const content = editor.getJSON()
const mergedContentList:ExpressionValue[] = [];
content.content?.forEach(it=>{
if(it.type == "paragraph" && it.content?.length > 0){
mergedContentList.push(it)
}
})
if(content.content?.length > mergedContentList.length){
content.content = mergedContentList;
//
editor.commands.setContent(content)
}
return content;
}
onMounted(() => {
if (model.value) editor.commands.setContent(model.value)
if (model.value?.trim().length > 0) editor.commands.setContent(JSON.parse(model.value))
editor.on('update', () => {
model.value = editor.getText()
model.value = JSON.stringify(getMergeContent())
})
})
watch(model, (newValue) => {
watch(()=>model, (newValue) => {
editor.commands.setContent(newValue || '')
})
@ -39,11 +64,26 @@ onUnmounted(() => {
})
</script>
<style>
.tiptap .is-empty{
.ProseMirror-trailingBreak{
display: none;
}
}
.tiptap p.is-editor-empty:first-child::before {
display: block;
color: #adb5bd;
content: attr(data-placeholder);
float: left;
height: 0;
pointer-events: none;
}
.tiptap .result-var {
background-color: #3574f0;
border-radius: 3px;
-webkit-box-decoration-break: clone;
box-decoration-break: clone;
color: white;
padding: 2px 4px;
}
</style>

View File

@ -5,15 +5,7 @@ import Suggestion, { SuggestionOptions } from '@tiptap/suggestion'
// See `addAttributes` below
export interface MentionNodeAttrs {
/**
* The identifier for the selected item that was mentioned, stored as a `data-id`
* attribute.
*/
id: string | null;
/**
* The label to be rendered by the editor as the displayed text for this mentioned
* item, if provided. Stored as a `data-label` attribute. See `renderLabel`.
*/
label?: string | null;
}
@ -136,13 +128,9 @@ export const Mention = Node.create<MentionOptions>({
},
group: 'inline',
inline: true,
selectable: false,
atom: true,
addAttributes() {
return {
id: {
@ -174,7 +162,6 @@ export const Mention = Node.create<MentionOptions>({
},
}
},
parseHTML() {
return [
{
@ -182,7 +169,6 @@ export const Mention = Node.create<MentionOptions>({
},
]
},
renderHTML({ node, HTMLAttributes }) {
if (this.options.renderLabel !== undefined) {
console.warn('renderLabel is deprecated use renderText and renderHTML instead')

View File

@ -2,6 +2,7 @@ import { Text } from './ext-text.ts'
import { Paragraph } from './ext-paragraph.ts'
import { Document } from './ext-document.ts'
export {Placeholder } from './ext-placeholder.ts'
export {Mention} from './ext-mention.ts'
// export

12
src/pages/T.vue Normal file
View File

@ -0,0 +1,12 @@
<template>
<div>
<TestRef v-model="testrv"/>
<div>{{ testrv }}</div>
</div>
</template>
<script setup>
import TestRef from "@/components/editor/test.vue";
import {ref} from "vue";
const testrv = ref('')
</script>

View File

@ -1,50 +1,92 @@
<template>
<div class="result-container">
<PageHeader title="输出指标编辑" description="*输出指标: 使用输入参数进行计算,可对指标进行编辑操作。">
<Button :loading="loading" type="primary" @click="save">保存</Button>
<Button :icon="h(SaveOutlined)" :loading="loading" type="primary" @click="save">保存</Button>
</PageHeader>
</div>
<div class="result-calc-expression-list-container overflow-auto grid grid-cols-2 gap-4"
style="max-height: calc(100vh - 240px);margin-right:-5px;padding-right:5px;">
<div class="result-calc-expression-list-container overflow-auto grid grid-cols-1 gap-4"
style="max-height: calc(100vh - 240px);margin-right:-5px;padding-right:5px;">
<div class="result-item bg-gray-100 p-3 rounded" v-for="item in ResultExpressionList" :key="item.key">
<div class="result-item-title text-base font-bold">{{ item.title }}</div>
<div class="result-item-title flex justify-between">
<div class=" text-base font-bold">
<span>{{ item.title }}</span>
<span>{{ parseExpression(item.expression) }}</span>
</div>
<div class="action">
<Button :icon="h(CodeOutlined)" type="primary" @click="testExpression(item)">测试</Button>
</div>
</div>
<div class="result-item-expression mt-3">
<Tiptap v-model="item.expression" class="w-full bg-white rounded outline-none p-3 bg-none resize-none"
:placeholder="`请输入&nbsp;${item.title}&nbsp;的计算公式`" />
<!-- <textarea class="w-full h-[66px] rounded outline-none p-3 bg-none resize-none" v-model="item.expression"
:placeholder="`请输入${item.title}计算公式`" /> -->
:placeholder="`请输入&nbsp;${item.title}&nbsp;的计算公式`"/>
</div>
</div>
</div>
<!-- <div class="hidden">
<DataField />
<DataField />
<Button type="primary" @click="showMessage">test</Button>
<p>输出计算</p>
</div> -->
</template>
<script setup lang="ts">
import {message,Button} from "ant-design-vue";
import {h, ref} from "vue";
import { CodeOutlined, SaveOutlined } from '@ant-design/icons-vue';
import { message } from "ant-design-vue";
import { ref } from "vue";
import { expressionList, saveExpression } from "@/service/api/result";
import {expressionList, saveExpression} from "@/service/api/result";
import PageHeader from "@/components/page-header.vue";
import Tiptap from "@/components/editor/tiptap.vue";
import Button from "@/components/button/button.vue";
// import Button from "@/components/button/button.vue";
import useRequest from "@/service/useRequest";
import input from "./test/data-input.ts"
import {getAllProduct} from "@/service/use-result-vars.ts";
const ResultExpressionList = ref<ResultExpression[]>([]);
expressionList().then((res) => {
console.log(res)
ResultExpressionList.value = res.list;
});
const {loading,run:save} = useRequest(()=> saveExpression(ResultExpressionList.value),{
const productValues = getAllProduct();
const {loading, run: save} = useRequest(() => saveExpression(ResultExpressionList.value), {
manual: true,
onSuccess() {
message.success("保存成功");
}
})
function runCode(expression:string,vars: any){
return new Function('vars',`with(vars) { return ${expression};}`)(vars)
}
function testExpression(item: ResultExpression) {
const expression = parseExpression(item.expression);
const result = runCode(expression,{
...(productValues.value),
input,
})
console.log('result',result)
// console.log('expression =>', expression,input,productValues.value)
}
function parseExpression(str: string) {
if (!str) return;
const doc = JSON.parse(str) as TiptapContentValue;
if (!doc.content || doc.content.length == 0) {
return '';
}
const expressionList = doc.content[0].content;
if (!expressionList) return '';
const expression = expressionList.map(item => {
if (item.type == 'text') {
return item.text;
}
return item.attrs.id
}).join('');
return expression;
// console.log(JSON.stringify({content:doc.content[0].content}))
}
</script>
<style lang="scss">
@ -57,27 +99,4 @@ const {loading,run:save} = useRequest(()=> saveExpression(ResultExpressionList.v
}
}
}
// .result-calc-expression-list-container {}
// .result-item {
// display: flex;
// flex-direction: column;
// margin-bottom: 20px;
// .result-item-title {
// font-size: 14px;
// font-weight: 500;
// color: #333333;
// margin-bottom: 10px;
// }
// .result-item-expression {}
// textarea {
// width: 100%;
// height: 40px;
// outline: none;
// }
// }</style>
</style>

View File

@ -0,0 +1,50 @@
export default {
"weight": 50,
"niaosu": 10,
"ruineng": 1,
"ruidai": 1,
"ruixian": 0,
"ruigao": 0,
"ruisu": 0,
"nengquanli": 0,
"kangquanli": 0,
"kangquangan": 0,
"baipuli": 0,
"ailunduo": 0,
"weiwo": 0,
"jiaweiti": 0,
"yilijia": 0,
"ruiyixi": 0,
"nengquansu": 0,
"ansu": 0,
"quanansu": 0,
"yilijia100": 0,
"shui": 0,
"anjisuan_ruye": 0,
"yilijia100_ruye": 0,
"quanansu_ruye": 0,
"ansu_ruye": 0,
"nengquansu_ruye": 0,
"ruqingdanbailiang": 0,
"ruianji": 0,
"quanyingda": 0,
"likawen": 0,
"lefanming85": 0,
"lefanming114": 0,
"anjisuan": 0,
"litai": 0,
"lineng": 0,
"liwen": 0,
"youwen": 0,
"yingtuolipite20": 0,
"yingtuolipite30": 0,
"putaotangluhuana5": 0,
"putaotang5": 0,
"putaotang10": 0,
"putaotang50": 0,
"luhuana09": 0,
"luhuana10": 0,
"geliefusi": 0,
"luhuajia": 0,
"baidanbai": 0
}

View File

@ -0,0 +1,718 @@
/*
* Generated by PEG.js 0.10.0.
*
* http://pegjs.org/
*/
"use strict";
function peg$subclass(child, parent) {
function ctor() { this.constructor = child; }
ctor.prototype = parent.prototype;
child.prototype = new ctor();
}
function peg$SyntaxError(message, expected, found, location) {
this.message = message;
this.expected = expected;
this.found = found;
this.location = location;
this.name = "SyntaxError";
if (typeof Error.captureStackTrace === "function") {
Error.captureStackTrace(this, peg$SyntaxError);
}
}
peg$subclass(peg$SyntaxError, Error);
peg$SyntaxError.buildMessage = function(expected, found) {
var DESCRIBE_EXPECTATION_FNS = {
literal: function(expectation) {
return "\"" + literalEscape(expectation.text) + "\"";
},
"class": function(expectation) {
var escapedParts = "",
i;
for (i = 0; i < expectation.parts.length; i++) {
escapedParts += expectation.parts[i] instanceof Array
? classEscape(expectation.parts[i][0]) + "-" + classEscape(expectation.parts[i][1])
: classEscape(expectation.parts[i]);
}
return "[" + (expectation.inverted ? "^" : "") + escapedParts + "]";
},
any: function(expectation) {
return "any character";
},
end: function(expectation) {
return "end of input";
},
other: function(expectation) {
return expectation.description;
}
};
function hex(ch) {
return ch.charCodeAt(0).toString(16).toUpperCase();
}
function literalEscape(s) {
return s
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\0/g, '\\0')
.replace(/\t/g, '\\t')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/[\x00-\x0F]/g, function(ch) { return '\\x0' + hex(ch); })
.replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return '\\x' + hex(ch); });
}
function classEscape(s) {
return s
.replace(/\\/g, '\\\\')
.replace(/\]/g, '\\]')
.replace(/\^/g, '\\^')
.replace(/-/g, '\\-')
.replace(/\0/g, '\\0')
.replace(/\t/g, '\\t')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/[\x00-\x0F]/g, function(ch) { return '\\x0' + hex(ch); })
.replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return '\\x' + hex(ch); });
}
function describeExpectation(expectation) {
return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation);
}
function describeExpected(expected) {
var descriptions = new Array(expected.length),
i, j;
for (i = 0; i < expected.length; i++) {
descriptions[i] = describeExpectation(expected[i]);
}
descriptions.sort();
if (descriptions.length > 0) {
for (i = 1, j = 1; i < descriptions.length; i++) {
if (descriptions[i - 1] !== descriptions[i]) {
descriptions[j] = descriptions[i];
j++;
}
}
descriptions.length = j;
}
switch (descriptions.length) {
case 1:
return descriptions[0];
case 2:
return descriptions[0] + " or " + descriptions[1];
default:
return descriptions.slice(0, -1).join(", ")
+ ", or "
+ descriptions[descriptions.length - 1];
}
}
function describeFound(found) {
return found ? "\"" + literalEscape(found) + "\"" : "end of input";
}
return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found.";
};
function peg$parse(input, options) {
options = options !== void 0 ? options : {};
var peg$FAILED = {},
peg$startRuleFunctions = { start: peg$parsestart },
peg$startRuleFunction = peg$parsestart,
peg$c0 = "+",
peg$c1 = peg$literalExpectation("+", false),
peg$c2 = "-",
peg$c3 = peg$literalExpectation("-", false),
peg$c4 = function(left, op, right) {
return op === "+" ? left + right : left - right;
},
peg$c5 = "*",
peg$c6 = peg$literalExpectation("*", false),
peg$c7 = "/",
peg$c8 = peg$literalExpectation("/", false),
peg$c9 = function(left, op, right) {
return op === "*" ? left * right : left / right;
},
peg$c10 = "(",
peg$c11 = peg$literalExpectation("(", false),
peg$c12 = ")",
peg$c13 = peg$literalExpectation(")", false),
peg$c14 = function(expr) { return expr; },
peg$c15 = function() { return -factor; },
peg$c16 = /^[a-zA-Z_]/,
peg$c17 = peg$classExpectation([["a", "z"], ["A", "Z"], "_"], false, false),
peg$c18 = /^[a-zA-Z0-9_]/,
peg$c19 = peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"], "_"], false, false),
peg$c20 = function(name) { return vars[name.join("")] || 0; },
peg$c21 = /^[0-9]/,
peg$c22 = peg$classExpectation([["0", "9"]], false, false),
peg$c23 = ".",
peg$c24 = peg$literalExpectation(".", false),
peg$c25 = function() { return parseFloat(text()); },
peg$c26 = /^[ \t\r\n]/,
peg$c27 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false),
peg$currPos = 0,
peg$savedPos = 0,
peg$posDetailsCache = [{ line: 1, column: 1 }],
peg$maxFailPos = 0,
peg$maxFailExpected = [],
peg$silentFails = 0,
peg$result;
if ("startRule" in options) {
if (!(options.startRule in peg$startRuleFunctions)) {
throw new Error("Can't start parsing from rule \"" + options.startRule + "\".");
}
peg$startRuleFunction = peg$startRuleFunctions[options.startRule];
}
function text() {
return input.substring(peg$savedPos, peg$currPos);
}
function location() {
return peg$computeLocation(peg$savedPos, peg$currPos);
}
function expected(description, location) {
location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos)
throw peg$buildStructuredError(
[peg$otherExpectation(description)],
input.substring(peg$savedPos, peg$currPos),
location
);
}
function error(message, location) {
location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos)
throw peg$buildSimpleError(message, location);
}
function peg$literalExpectation(text, ignoreCase) {
return { type: "literal", text: text, ignoreCase: ignoreCase };
}
function peg$classExpectation(parts, inverted, ignoreCase) {
return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase };
}
function peg$anyExpectation() {
return { type: "any" };
}
function peg$endExpectation() {
return { type: "end" };
}
function peg$otherExpectation(description) {
return { type: "other", description: description };
}
function peg$computePosDetails(pos) {
var details = peg$posDetailsCache[pos], p;
if (details) {
return details;
} else {
p = pos - 1;
while (!peg$posDetailsCache[p]) {
p--;
}
details = peg$posDetailsCache[p];
details = {
line: details.line,
column: details.column
};
while (p < pos) {
if (input.charCodeAt(p) === 10) {
details.line++;
details.column = 1;
} else {
details.column++;
}
p++;
}
peg$posDetailsCache[pos] = details;
return details;
}
}
function peg$computeLocation(startPos, endPos) {
var startPosDetails = peg$computePosDetails(startPos),
endPosDetails = peg$computePosDetails(endPos);
return {
start: {
offset: startPos,
line: startPosDetails.line,
column: startPosDetails.column
},
end: {
offset: endPos,
line: endPosDetails.line,
column: endPosDetails.column
}
};
}
function peg$fail(expected) {
if (peg$currPos < peg$maxFailPos) { return; }
if (peg$currPos > peg$maxFailPos) {
peg$maxFailPos = peg$currPos;
peg$maxFailExpected = [];
}
peg$maxFailExpected.push(expected);
}
function peg$buildSimpleError(message, location) {
return new peg$SyntaxError(message, null, null, location);
}
function peg$buildStructuredError(expected, found, location) {
return new peg$SyntaxError(
peg$SyntaxError.buildMessage(expected, found),
expected,
found,
location
);
}
function peg$parsestart() {
var s0;
s0 = peg$parseexpression();
return s0;
}
function peg$parseexpression() {
var s0, s1, s2, s3, s4, s5;
s0 = peg$currPos;
s1 = peg$parseterm();
if (s1 !== peg$FAILED) {
s2 = peg$parse_();
if (s2 !== peg$FAILED) {
if (input.charCodeAt(peg$currPos) === 43) {
s3 = peg$c0;
peg$currPos++;
} else {
s3 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c1); }
}
if (s3 === peg$FAILED) {
if (input.charCodeAt(peg$currPos) === 45) {
s3 = peg$c2;
peg$currPos++;
} else {
s3 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c3); }
}
}
if (s3 !== peg$FAILED) {
s4 = peg$parse_();
if (s4 !== peg$FAILED) {
s5 = peg$parseexpression();
if (s5 !== peg$FAILED) {
peg$savedPos = s0;
s1 = peg$c4(s1, s3, s5);
s0 = s1;
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
if (s0 === peg$FAILED) {
s0 = peg$parseterm();
}
return s0;
}
function peg$parseterm() {
var s0, s1, s2, s3, s4, s5;
s0 = peg$currPos;
s1 = peg$parsefactor();
if (s1 !== peg$FAILED) {
s2 = peg$parse_();
if (s2 !== peg$FAILED) {
if (input.charCodeAt(peg$currPos) === 42) {
s3 = peg$c5;
peg$currPos++;
} else {
s3 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c6); }
}
if (s3 === peg$FAILED) {
if (input.charCodeAt(peg$currPos) === 47) {
s3 = peg$c7;
peg$currPos++;
} else {
s3 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c8); }
}
}
if (s3 !== peg$FAILED) {
s4 = peg$parse_();
if (s4 !== peg$FAILED) {
s5 = peg$parseterm();
if (s5 !== peg$FAILED) {
peg$savedPos = s0;
s1 = peg$c9(s1, s3, s5);
s0 = s1;
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
if (s0 === peg$FAILED) {
s0 = peg$parsefactor();
}
return s0;
}
function peg$parsefactor() {
var s0, s1, s2, s3, s4, s5;
s0 = peg$currPos;
if (input.charCodeAt(peg$currPos) === 40) {
s1 = peg$c10;
peg$currPos++;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c11); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parse_();
if (s2 !== peg$FAILED) {
s3 = peg$parseexpression();
if (s3 !== peg$FAILED) {
s4 = peg$parse_();
if (s4 !== peg$FAILED) {
if (input.charCodeAt(peg$currPos) === 41) {
s5 = peg$c12;
peg$currPos++;
} else {
s5 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c13); }
}
if (s5 !== peg$FAILED) {
peg$savedPos = s0;
s1 = peg$c14(s3);
s0 = s1;
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
if (s0 === peg$FAILED) {
s0 = peg$currPos;
if (input.charCodeAt(peg$currPos) === 45) {
s1 = peg$c2;
peg$currPos++;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c3); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsefactor();
if (s2 !== peg$FAILED) {
peg$savedPos = s0;
s1 = peg$c15();
s0 = s1;
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
if (s0 === peg$FAILED) {
s0 = peg$parsevariable();
if (s0 === peg$FAILED) {
s0 = peg$parsenumber();
}
}
}
return s0;
}
function peg$parsevariable() {
var s0, s1, s2, s3;
s0 = peg$currPos;
if (peg$c16.test(input.charAt(peg$currPos))) {
s1 = input.charAt(peg$currPos);
peg$currPos++;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c17); }
}
if (s1 !== peg$FAILED) {
s2 = [];
if (peg$c18.test(input.charAt(peg$currPos))) {
s3 = input.charAt(peg$currPos);
peg$currPos++;
} else {
s3 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c19); }
}
while (s3 !== peg$FAILED) {
s2.push(s3);
if (peg$c18.test(input.charAt(peg$currPos))) {
s3 = input.charAt(peg$currPos);
peg$currPos++;
} else {
s3 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c19); }
}
}
if (s2 !== peg$FAILED) {
peg$savedPos = s0;
s1 = peg$c20(s1);
s0 = s1;
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
return s0;
}
function peg$parsenumber() {
var s0, s1, s2, s3, s4, s5, s6;
s0 = peg$currPos;
if (input.charCodeAt(peg$currPos) === 45) {
s1 = peg$c2;
peg$currPos++;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c3); }
}
if (s1 === peg$FAILED) {
s1 = null;
}
if (s1 !== peg$FAILED) {
s2 = [];
if (peg$c21.test(input.charAt(peg$currPos))) {
s3 = input.charAt(peg$currPos);
peg$currPos++;
} else {
s3 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c22); }
}
if (s3 !== peg$FAILED) {
while (s3 !== peg$FAILED) {
s2.push(s3);
if (peg$c21.test(input.charAt(peg$currPos))) {
s3 = input.charAt(peg$currPos);
peg$currPos++;
} else {
s3 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c22); }
}
}
} else {
s2 = peg$FAILED;
}
if (s2 !== peg$FAILED) {
s3 = peg$currPos;
if (input.charCodeAt(peg$currPos) === 46) {
s4 = peg$c23;
peg$currPos++;
} else {
s4 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c24); }
}
if (s4 !== peg$FAILED) {
s5 = [];
if (peg$c21.test(input.charAt(peg$currPos))) {
s6 = input.charAt(peg$currPos);
peg$currPos++;
} else {
s6 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c22); }
}
if (s6 !== peg$FAILED) {
while (s6 !== peg$FAILED) {
s5.push(s6);
if (peg$c21.test(input.charAt(peg$currPos))) {
s6 = input.charAt(peg$currPos);
peg$currPos++;
} else {
s6 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c22); }
}
}
} else {
s5 = peg$FAILED;
}
if (s5 !== peg$FAILED) {
s4 = [s4, s5];
s3 = s4;
} else {
peg$currPos = s3;
s3 = peg$FAILED;
}
} else {
peg$currPos = s3;
s3 = peg$FAILED;
}
if (s3 === peg$FAILED) {
s3 = null;
}
if (s3 !== peg$FAILED) {
peg$savedPos = s0;
s1 = peg$c25();
s0 = s1;
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
return s0;
}
function peg$parse_() {
var s0, s1;
s0 = [];
if (peg$c26.test(input.charAt(peg$currPos))) {
s1 = input.charAt(peg$currPos);
peg$currPos++;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c27); }
}
while (s1 !== peg$FAILED) {
s0.push(s1);
if (peg$c26.test(input.charAt(peg$currPos))) {
s1 = input.charAt(peg$currPos);
peg$currPos++;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c27); }
}
}
return s0;
}
peg$result = peg$startRuleFunction();
if (peg$result !== peg$FAILED && peg$currPos === input.length) {
return peg$result;
} else {
if (peg$result !== peg$FAILED && peg$currPos < input.length) {
peg$fail(peg$endExpectation());
}
throw peg$buildStructuredError(
peg$maxFailExpected,
peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null,
peg$maxFailPos < input.length
? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1)
: peg$computeLocation(peg$maxFailPos, peg$maxFailPos)
);
}
}
module.exports = {
SyntaxError: peg$SyntaxError,
parse: peg$parse
};

121
src/pages/result/var.vue Normal file
View File

@ -0,0 +1,121 @@
<template>
<div class="dropdown-menu">
<div class="dropdown-menu-list" v-if="items.length">
<div
class="dropdown-item"
:class="{ 'is-selected': index === selectedIndex }"
v-for="(item, index) in varList"
:key="index"
@click="selectItem(index)"
>
<div class="label"> {{ item.text }}</div>
<span class="value">{{item.value}}</span>
</div>
</div>
<div class="not-exists" v-else>
<span>数据指标不存在</span>
</div>
</div>
</template>
<script setup lang="ts">
import {getProductValue} from "@/service/use-result-vars.js";
import {watch, ref} from "vue";
const productList = getProductValue();
const varList = ref<ResultVarItem[] >([])
const props = defineProps({
items: {
type: Array,
},
query:{
type: String,
},
command: {
type: Function,
required: true,
},
});
const selectedIndex = ref(0)
watch(()=>props.query, () => {
selectedIndex.value = 0;
if(props.query){
varList.value = productList.value.filter(item => (
item.value.includes(props.query) || item.label.includes(props.query) || item.text.includes(props.query)
)).filter((_,index)=> index < 10)
}else{
varList.value = productList.value.filter((_,index)=> index < 10)
}
});
const upHandler = () => {
selectedIndex.value = ((selectedIndex.value + varList.value.length) - 1) % varList.value.length
}
const downHandler = () => {
selectedIndex.value = (selectedIndex.value + 1) % varList.value.length
}
const enterHandler = () => {
selectItem(selectedIndex.value)
}
const selectItem = (index) => {
const item = varList.value[index]
if (item) {
props.command({id: item.value, label: item.text || item.label})
}
// selectedIndex.value = index
}
const onKeyDown = ({event}: { event: { key: string } }) => {
if (event.key === 'ArrowUp') {
upHandler()
return true
}
if (event.key === 'ArrowDown') {
downHandler()
return true
}
if (event.key === 'Enter') {
enterHandler()
return true
}
return false
}
defineExpose({onKeyDown})
</script>
<style lang="scss">
/* Dropdown menu */
.dropdown-menu-list{}
.dropdown-menu-list{}
.dropdown-menu {
@apply bg-white relative overflow-auto flex flex-col shadow rounded border border-gray-200;
}
.dropdown-item {
@apply w-full p-2 gap-2 bg-transparent flex items-center cursor-pointer justify-between;
text-align: left;
&:hover,
&:hover.is-selected {
@apply bg-gray-200;
}
&.is-selected {
@apply bg-gray-100;
}
.label{
@apply text-gray-800;
}
.value{
@apply text-gray-300 text-sm;
}
}
.not-exists {
@apply p-2;
}
</style>

64
src/pages/result/vars.ts Normal file
View File

@ -0,0 +1,64 @@
import {VueRenderer} from '@tiptap/vue-3'
import tippy from 'tippy.js'
import vars from './var.vue'
export default {
items: ({query}) => {
return ['1,2,3']
},
render: () => {
let component
let popup
return {
onStart: props => {
component = new VueRenderer(vars, {
props,
editor: props.editor,
})
if (!props.clientRect) {
return
}
popup = tippy('body', {
getReferenceClientRect: props.clientRect,
appendTo: () => document.body,
content: component.element,
showOnCreate: true,
interactive: true,
trigger: 'manual',
placement: 'bottom-start',
})
},
onUpdate(props) {
component.updateProps(props)
if (!props.clientRect) {
return
}
popup[0].setProps({
getReferenceClientRect: props.clientRect,
})
},
onKeyDown(props) {
if (props.event.key === 'Escape') {
popup[0].hide()
return true
}
// 将查询字符透传给组件
return component.ref?.onKeyDown?.(props)
},
onExit() {
popup[0]?.destroy?.()
component?.destroy?.()
},
}
},
}

View File

@ -31,6 +31,15 @@ export const routes:RouteRecordRaw[] = [
},
component: () => import('./pages/result/index.vue')
},
{
path: 'ttt',
name: 'ttt',
meta: {
title: '输出计算',
icon:'icon-calculator'
},
component: () => import('./pages/T.vue')
},
{
path: 'product-data',
name: 'product',

View File

@ -1,19 +1,6 @@
import { sleep } from "@/core/sleep";
import { post } from "./request";
export const ProductCols = [
{ id: 1, name: "能量密度", alias: "power" },
{ id: 2, name: "蛋白Pro", alias: "protein" },
{ id: 3, name: "Glu", alias: "glu" },
{ id: 4, name: "Fat", alias: "fat" },
{ id: 5, name: "纤维素", alias: "cellulose" },
{ id: 6, name: "Na", alias: "na" },
{ id: 7, name: "K", alias: "k" },
{ id: 8, name: "Ca", alias: "ca" },
{ id: 9, name: "P", alias: "p" },
{ id: 10, name: "Mg", alias: "mg" },
];
export async function expressionList() {
await sleep(200);
return await post<DataList<ResultExpression>>(`/result/expression/all`);

View File

@ -0,0 +1,256 @@
import {getList} from "@/service/api/product.ts";
import {ref} from "vue";
type InputValue = {
name: string;
key: string;
unit?: string;
placeholder?: string;
type?: string;
addition?: {
key: string
placeholder: string
unit?: string;
}
}
/*
{
"weight": 50,
"niaosu": 12,
"ruineng": 10,
"ruidai": 0,
"ruixian": 0,
"ruigao": 0,
"ruisu": 0,
"nengquanli": 0,
"kangquanli": 0,
"kangquangan": 0,
"baipuli": 0,
"ailunduo": 0,
"weiwo": 0,
"jiaweiti": 0,
"yilijia": 0,
"ruiyixi": 0,
"nengquansu": 0,
"ansu": 0,
"quanansu": 0,
"yilijia100": 0,
"shui": 0,
"anjisuan_ruye": 0,
"yilijia100_ruye": 0,
"quanansu_ruye": 0,
"ansu_ruye": 0,
"nengquansu_ruye": 0,
"ruqingdanbailiang": 0,
"ruianji": 0,
"quanyingda": 0,
"likawen": 0,
"lefanming85": 0,
"lefanming114": 0,
"anjisuan": 0,
"litai": 0,
"lineng": 0,
"liwen": 0,
"youwen": 0,
"yingtuolipite20": 0,
"yingtuolipite30": 0,
"putaotangluhuana5": 0,
"putaotang5": 0,
"putaotang10": 0,
"putaotang50": 0,
"luhuana09": 0,
"luhuana10": 0,
"geliefusi": 0,
"luhuajia": 0,
"baidanbai": 0
}
*/
const inputList: InputValue[] = [
{name: '体重', key: 'weight',},
{name: '24h尿素', key: 'niaosu',},
// 肠内制剂
{name: '瑞能', key: 'ruineng', placeholder: '入液量ml', unit: 'ml', type: 'gut'},
{name: '瑞代', key: 'ruidai', placeholder: '入液量ml', unit: 'ml', type: 'gut'},
{name: '瑞先', key: 'ruixian', placeholder: '入液量ml', unit: 'ml', type: 'gut'},
{name: '瑞高', key: 'ruigao', placeholder: '入液量ml', unit: 'ml', type: 'gut'},
{name: '瑞素', key: 'ruisu', placeholder: '入液量ml', unit: 'ml', type: 'gut'},
{name: '能全力', key: 'nengquanli', placeholder: '入液量ml', unit: 'ml', type: 'gut'},
{name: '康全力', key: 'kangquanli', placeholder: '入液量ml', unit: 'ml', type: 'gut'},
{name: '康全甘', key: 'kangquangan', placeholder: '入液量ml', unit: 'ml', type: 'gut'}, // 新增
{name: '百普力', key: 'baipuli', placeholder: '入液量ml', unit: 'ml', type: 'gut'},
{name: '爱伦多', key: 'ailunduo', placeholder: '入液量ml', unit: 'ml', type: 'gut'},
{name: '维沃', key: 'weiwo', placeholder: '入液量ml', unit: 'ml', type: 'gut'},
// 新增 230409
{name: '佳维体', key: 'jiaweiti', placeholder: '入液量ml', unit: 'ml', type: 'gut'},
{name: '伊力佳', key: 'yilijia', placeholder: '入液量ml', unit: 'ml', type: 'gut'},
{name: '瑞易西', key: 'ruiyixi', placeholder: '入液量ml', unit: 'ml', type: 'gut'},
{
name: '能全素',
key: 'nengquansu',
placeholder: '入量(勺)',
unit: 'ml',
type: 'gut',
addition: {key: 'nengquansu_ruye', placeholder: '入液量ml'}
},
{
name: '安素',
key: 'ansu',
placeholder: '入量(勺)',
unit: '勺',
type: 'gut',
addition: {key: 'ansu_ruye', placeholder: '入液量ml'}
},
{
name: '全安素',
key: 'quanansu',
placeholder: '入量(勺)',
unit: '勺',
type: 'gut',
addition: {key: 'quanansu_ruye', placeholder: '入液量ml'}
},
// 新增 230409
{
name: '益力佳',
key: 'yilijia100',
placeholder: '入量(勺)',
unit: 'ml',
type: 'gut',
addition: {key: 'yilijia100_ruye', placeholder: '入液量ml'}
},
// o water
{
name: '乳清蛋白量(g)',
key: 'ruqingdanbailiang',
placeholder: '入量g',
unit: 'g',
type: 'gut',
addition: {key: 'ruqingdanbailiang_ruye', placeholder: '入液量ml'}
},
// { name: '雀巢Beneprotein', key: 'quecaoBeneprotein', placeholder: '入量g',unit:'g',type: 'gut' },
{name: '瑞安吉', key: 'ruianji', placeholder: '入液量ml', unit: 'ml', type: 'gut'},
// 静脉制剂
// { name: '卡文1440ml', key: 'kawen1440', placeholder: '入液量ml', unit:'ml', type: 'vein' },
// { name: '卡文1920ml', key: 'kawen1920', placeholder: '入液量ml', unit:'ml', type: 'vein' },
// 新增 230409
{name: '水', key: 'shui', placeholder: '入液量ml', unit: 'ml', type: 'gut'},
{name: '全营达', key: 'quanyingda', placeholder: '入液量ml', unit: 'ml', type: 'vein'},
{name: '力卡文', key: 'likawen', placeholder: '入液量ml', unit: 'ml', type: 'vein'},
{name: '乐凡命8.5%', key: 'lefanming85', placeholder: '入液量ml', unit: 'ml', type: 'vein'},
{name: '乐凡命11.4%', key: 'lefanming114', placeholder: '入液量ml', unit: 'ml', type: 'vein'},
{
name: '其他氨基酸',
key: 'anjisuan',
placeholder: '入液量ml',
unit: 'ml',
type: 'vein',
addition: {key: 'anjisuan_ruye', placeholder: '', unit: '%',}
},
{name: '力太', key: 'litai', placeholder: '入液量ml', unit: 'ml', type: 'vein'},
{name: '力能', key: 'lineng', placeholder: '入液量ml', unit: 'ml', type: 'vein'},
{name: '力文', key: 'liwen', placeholder: '入液量ml', unit: 'ml', type: 'vein'},
{name: '尤文', key: 'youwen', placeholder: '入液量ml', unit: 'ml', type: 'vein'},
{name: '英脱利匹特20%', key: 'yingtuolipite20', placeholder: '入液量ml', unit: 'ml', type: 'vein'},// 新增
{name: '英脱利匹特30%', key: 'yingtuolipite30', placeholder: '入液量ml', unit: 'ml', type: 'vein'},// 新增
{name: '5%葡萄糖氯化钠', key: 'putaotangluhuana5', placeholder: '入液量ml', unit: 'ml', type: 'vein'},// 新增
{name: '5%葡萄糖', key: 'putaotang5', placeholder: '入液量ml', unit: 'ml', type: 'vein'},
{name: '10%葡萄糖', key: 'putaotang10', placeholder: '入液量ml', unit: 'ml', type: 'vein'},
{name: '50%葡萄糖', key: 'putaotang50', placeholder: '入液量ml', unit: 'ml', type: 'vein'},
{name: '0.9%氯化钠', key: 'luhuana09', placeholder: '入液量ml', unit: 'ml', type: 'vein'},
{name: '10%氯化钠', key: 'luhuana10', placeholder: '入液量ml', unit: 'ml', type: 'vein'},
{name: '格列福斯', key: 'geliefusi', placeholder: '入液量ml', unit: 'ml', type: 'vein'},
{name: '氯化钾', key: 'luhuajia', placeholder: '入液量ml', unit: 'ml', type: 'vein'},
// { name: '其他液体', key: 'qitayeti', placeholder: '入液量ml', unit:'ml', type: 'vein' }, // 新增
{name: '白蛋白', key: 'baidanbai', placeholder: '入液量g', unit: 'ml', type: 'vein'}, // 新增
]
export function getInputValue() {
const list: ResultVarItem[] = [];
inputList.forEach(item => {
if (item.addition) {
const additionExt = item.addition.placeholder || item.addition.unit
list.push({
label: `${item.name}${additionExt ? '-' + additionExt : ''}`,
text: `${item.name}${additionExt ? '-' + additionExt : ''}`,
value: `input.${item.addition.key}`,
})
}
const ext = item.placeholder || item.unit
list.push({
label: `${item.name}${ext ? '-' + ext : ''}`,
text: `${item.name}${ext ? '-' + ext : ''}`,
value: `input.${item.key}`,
})
})
return list
}
const ValueKeys = [
'power',
'protein',
'glu',
'fat',
'cellulose',
'na',
'k',
'ca',
'p',
'mg',
]
export function getAllProduct() {
const list = ref<{
[key: string]: ProductInfoModel
}>([])
getList({
page: 1,
limit: 10000
}).then(res => {
const data: {
[key: string]: ProductInfoModel
} = {}
res.list.forEach(it => {
data[it.alias] = it;
})
list.value = data
});
return list
}
export function getProductValue() {
const list = ref<ResultVarItem[]>(getInputValue())
getList({
page: 1,
limit: 10000
}).then(res => {
const _list: ResultVarItem[] = [];
res.list.forEach(it => {
ValueKeys.forEach(key => {
const label = `${it.name}-${key}`
_list.push({
label,
value: `${it.alias}.${key}`,
text: label,
})
// _list.push({
// label: `${it.name}.${key}`,
// value: `${it.alias}.${key}`,
// text: label,
// })
})
})
list.value = [
...list.value,
..._list,
];
})
return list;
}

View File

@ -62,7 +62,6 @@ export const useUserStore = defineStore('counter', () => {
console.log('401 show login')
}
}).finally(() => {
console.log('onMounted inited')
setTimeout(() => {
userInit.value = true
}, 500)

View File

@ -34,4 +34,25 @@ declare type ResultExpression = {
created_at: Date | string;
updated_at: Date | string;
status: number;
}
}
declare type ResultVarItem = {
label: string;
value: string;
text?: string;
}
declare type TiptapContentValue = {
type:string;
content: {
type:string;
content: ExpressionValue[];
}[];
}
interface ExpressionValue {
type: 'text'|'mention';
attrs: {
id: string;
label: string;
}
text: string;
}

View File

@ -509,6 +509,11 @@
prosemirror-transform "^1.10.2"
prosemirror-view "^1.37.0"
"@tiptap/suggestion@^2.10.4":
version "2.10.4"
resolved "https://registry.npmmirror.com/@tiptap/suggestion/-/suggestion-2.10.4.tgz#732320b65bc55bcc2a33a8f32072f9478ffb7436"
integrity sha512-7Bzcn1REA7OmVRxiMF2kVK9EhosXotdLAGaEvSbn4zQtHCJG0tREuYvPy53LGzVuPkBDR6Pf6sp1QbGvSne/8g==
"@tiptap/vue-3@^2.10.4":
version "2.10.4"
resolved "https://registry.npmmirror.com/@tiptap/vue-3/-/vue-3-2.10.4.tgz#9d44e4191d6e512673e44f760aceb909dd568132"