Skip to content

Commit 4c58549

Browse files
committed
基本输入模块与样式
1 parent 90ee882 commit 4c58549

7 files changed

Lines changed: 325 additions & 142 deletions

File tree

package-lock.json

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
},
1212
"dependencies": {
1313
"function-plot": "^2.0.0-0",
14+
"lodash-es": "^4.17.21",
1415
"vue": "^3.5.13"
1516
},
1617
"devDependencies": {
18+
"@types/lodash-es": "^4.17.12",
1719
"@vitejs/plugin-vue": "^5.2.1",
1820
"patch-package": "^8.0.0",
1921
"typescript": "~5.6.2",

src/App.vue

Lines changed: 13 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,61 +2,26 @@
22
<Navbar />
33
<div id="content">
44
<div id="editor">
5-
<div v-for="dataItem in graphData" class="plot-data">
6-
<select
7-
v-model="dataItem.fnType"
8-
@change="
9-
getFnType(dataItem.fnType).notAllowedInIntervel &&
10-
!dataItem.graphType &&
11-
(dataItem.graphType = 'polyline')
12-
"
13-
>
14-
<option :value="undefined">{{ fnTypeArr[0].label }}</option>
15-
<option v-for="type in fnTypeArr.slice(1)" :value="type.value">
16-
{{ type.label }}
17-
</option>
18-
</select>
19-
20-
<select v-model="dataItem.graphType">
21-
<option
22-
v-if="!getFnType(dataItem.fnType).notAllowedInIntervel"
23-
:value="undefined"
24-
>
25-
{{ graphTypeArr[0].label }}
26-
</option>
27-
<option v-for="type in graphTypeArr.slice(1)" :value="type.value">
28-
{{ type.label }}
29-
</option>
30-
</select>
31-
<div class="input-wrapper">
32-
<input
33-
v-for="input in inputArr.filter(({ value }) => {
34-
const inputs = getFnType(dataItem.fnType).inputs;
35-
if (inputs.includes(value)) return true;
36-
else delete dataItem[value];
37-
})"
38-
type="text"
39-
v-model="dataItem[input.value]"
40-
/>
41-
</div>
42-
</div>
43-
<div class="add-data" @click="graphData.push({})">+ 添加</div>
44-
{{ graphData }}
5+
<DataBlock
6+
v-for="(_dataItem, i) in graphData"
7+
v-model="graphData[i]"
8+
@delete="graphData.splice(i, 1)"
9+
/>
10+
<div class="plot-data add-data" @click="graphData.push({})">+ 添加</div>
11+
{{ graphData }}<br />
4512
</div>
46-
<Graph :graphData="graphData" />
13+
<Graph :graphData="graphData" ref="graphRef" />
4714
</div>
4815
</template>
4916

5017
<script setup lang="ts">
5118
import Navbar from "./components/nav.vue";
5219
import Graph from "./components/graph.vue";
20+
import DataBlock from "./components/dataBlock.vue";
5321
import type { FunctionPlotDatum } from "function-plot";
54-
import { reactive } from "vue";
55-
import { fnTypeArr, graphTypeArr, inputArr } from "./consts";
56-
import type { FnType, ValueLabel } from "./consts";
22+
import { reactive, ref } from "vue";
23+
const graphRef = ref<InstanceType<typeof Graph>>();
5724
const graphData = reactive<FunctionPlotDatum[]>([{ fn: "x^2" }]);
58-
const getFnType = (fnType?: string) =>
59-
<FnType>fnTypeArr.find(({ value }) => value === (fnType || "linear"));
6025
</script>
6126

6227
<style>
@@ -83,12 +48,12 @@ const getFnType = (fnType?: string) =>
8348
background: #fff3;
8449
position: relative;
8550
}
86-
8751
#editor {
8852
width: 40vw;
53+
border-right: var(--c-hr) 1px solid;
8954
}
9055
#graph {
9156
width: 60vw;
9257
position: relative;
9358
}
94-
</style>
59+
</style>

src/components/dataBlock.vue

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
<template>
2+
<div class="plot-data" v-if="dataItem">
3+
<div class="selectors">
4+
<select v-model="dataItem.fnType" @change="fnTypeChange(dataItem)">
5+
<option :value="undefined">{{ fnTypeArr[0].label }}</option>
6+
<option v-for="type in fnTypeArr.slice(1)" :value="type.value">
7+
{{ type.label }}
8+
</option>
9+
</select>
10+
<select v-model="dataItem.graphType">
11+
<option
12+
v-if="!getFnType(dataItem.fnType).notAllowedInIntervel"
13+
:value="undefined"
14+
>
15+
{{ graphTypeArr[0].label }}
16+
</option>
17+
<option
18+
v-if="dataItem.fnType !== 'implicit'"
19+
v-for="type in graphTypeArr.slice(1)"
20+
:value="type.value"
21+
>
22+
{{ type.label }}
23+
</option>
24+
</select>
25+
<button class="delete" @click="emit('delete')">删除</button>
26+
</div>
27+
28+
<div class="inputs">
29+
<div v-for="input in getFnType(dataItem.fnType).inputs" class="input-box">
30+
<span class="input-title">{{ input.title }}</span>
31+
<input
32+
type="text"
33+
v-model="dataItem[input.value]"
34+
:placeholder="input.placeholder"
35+
/>
36+
</div>
37+
<template v-if="getFnType(dataItem.fnType).coord">
38+
<div
39+
v-for="input in getFnType(dataItem.fnType).coord"
40+
class="input-box coord"
41+
>
42+
<span class="coord-label">{{ input.label }}</span>
43+
<input
44+
type="number"
45+
@input="handleCoordInput(dataItem!, input.value, 0, $event)"
46+
:placeholder="input.placeholder1"
47+
/>
48+
<span class="coord-label">{{ input.sep }}</span>
49+
<input
50+
type="number"
51+
@input="handleCoordInput(dataItem!, input.value, 1, $event)"
52+
:placeholder="input.placeholder2"
53+
/>
54+
<span class="coord-label">{{ input.fin }}</span>
55+
</div>
56+
</template>
57+
</div>
58+
</div>
59+
</template>
60+
<script setup lang="ts">
61+
import type { FunctionPlotDatum } from "function-plot";
62+
import { fnTypeArr, graphTypeArr, inputTypeArr,getFnType } from "../consts";
63+
import type { CoordType } from "../consts";
64+
const emit = defineEmits(["delete"]);
65+
const dataItem = defineModel<FunctionPlotDatum>();
66+
67+
function fnTypeChange(dataItem: FunctionPlotDatum) {
68+
if (getFnType(dataItem.fnType).notAllowedInIntervel && !dataItem.graphType)
69+
dataItem.graphType = "polyline";
70+
if (dataItem.fnType === "implicit") delete dataItem.graphType;
71+
inputTypeArr.forEach((key) => delete dataItem[key]);
72+
if (dataItem.fnType === "vector") dataItem.vector = [0, 0];
73+
}
74+
function handleCoordInput(
75+
dataItem: FunctionPlotDatum,
76+
key: CoordType["value"],
77+
index: 0 | 1,
78+
event: Event
79+
) {
80+
const raw = (<HTMLInputElement>event.target).value;
81+
const newVal = Number(raw);
82+
if (!dataItem[key]) {
83+
const coord: [number, number] = [0, 0];
84+
coord[index] = newVal;
85+
dataItem[key] = coord;
86+
} else {
87+
if (raw === "") {
88+
if (dataItem[key][(index + 1) % 2]) dataItem[key][index] = 0;
89+
else delete dataItem[key];
90+
} else {
91+
dataItem[key][index] = newVal;
92+
}
93+
}
94+
}
95+
</script>
96+
97+
<style>
98+
.plot-data {
99+
border-bottom: var(--c-border) 1px solid;
100+
background: var(--c-bk2);
101+
position: relative;
102+
padding: 20px 15px;
103+
}
104+
.delete {
105+
position: absolute;
106+
top: 0;
107+
bottom: 0;
108+
right: 0;
109+
color: var(--c-text);
110+
padding: 8px 15px;
111+
border: none;
112+
background: var(--c-red);
113+
transition: all 0.2s;
114+
border-radius: 5px;
115+
opacity: 0.75;
116+
}
117+
.delete:hover {
118+
opacity: 1;
119+
}
120+
.delete:active {
121+
opacity: 0.2;
122+
transition: none;
123+
}
124+
.selectors {
125+
margin-bottom: 10px;
126+
position: relative;
127+
}
128+
.selectors select {
129+
border: var(--c-border) 1px solid;
130+
background: var(--c-bk3);
131+
border-radius: 5px;
132+
width: 30%;
133+
padding: 8px 8px;
134+
margin-right: 15px;
135+
color: var(--text);
136+
font-size: 15px;
137+
}
138+
.selectors select:focus {
139+
border-color: var(--c-accent);
140+
}
141+
.inputs {
142+
display: flex;
143+
flex-direction: column;
144+
gap: 10px;
145+
}
146+
.inputs .input-box {
147+
position: relative;
148+
display: flex;
149+
}
150+
.inputs .input-box .input-title {
151+
font-family: "JetBrains Mono";
152+
font-style: italic;
153+
letter-spacing: 5px;
154+
font-size: 20px;
155+
font-weight: bold;
156+
margin: auto 5px;
157+
height: fit-content;
158+
}
159+
.inputs input {
160+
color: var(--c-text);
161+
background: var(--c-bk1);
162+
border: var(--c-border) 1px solid;
163+
height: 100%;
164+
line-height: 100%;
165+
font-size: 20px;
166+
border-radius: 5px;
167+
display: block;
168+
width: 100%;
169+
text-indent: 10px;
170+
font-family: "JetBrains Mono";
171+
padding: 10px 0;
172+
}
173+
.inputs input:placeholder-shown {
174+
border-color: var(--c-red);
175+
}
176+
.inputs input:focus {
177+
border-color: var(--c-accent);
178+
}
179+
.add-data {
180+
padding-top: 10px;
181+
padding-bottom: 10px;
182+
}
183+
.add-data:hover {
184+
background: var(--c-bk3);
185+
}
186+
.add-data:active {
187+
background: var(--c-bk1);
188+
}
189+
</style>

src/components/graph.vue

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88
<script setup lang="ts">
99
import { onMounted, onUnmounted, ref, watch } from "vue";
1010
import functionPlot from "function-plot";
11+
import { throttle } from "lodash-es";
1112
import type { FunctionPlotDatum } from "function-plot";
13+
import { getFnType } from "../consts";
1214
const { graphData } = defineProps<{ graphData: FunctionPlotDatum[] }>();
15+
1316
const shellRef = ref<HTMLDivElement | null>(null);
1417
const plotRef = ref<HTMLDivElement | null>(null);
1518
const width = ref(0),
@@ -25,20 +28,28 @@ onMounted(() => {
2528
window.addEventListener("resize", handleResize);
2629
watch(
2730
[width, height, () => graphData],
28-
() => {
31+
throttle(() => {
32+
const data = <FunctionPlotDatum[]>JSON.parse(JSON.stringify(graphData));
33+
for (const dataItem of data) {
34+
const fnType = getFnType(dataItem.fnType);
35+
if (fnType.notAllowedInIntervel && !dataItem.graphType) return;
36+
for (const input of fnType.inputs)
37+
if (!dataItem[input.value]) return;
38+
for (const coord of fnType.coord ?? [])
39+
if (!dataItem[coord.value] && !coord.optional) return;
40+
}
2941
if (plotRef.value && width.value && height.value)
3042
functionPlot({
31-
data: JSON.parse(JSON.stringify(graphData)),
43+
data,
3244
target: plotRef.value,
3345
width: width.value,
3446
height: height.value,
3547
});
36-
},
48+
}, 200),
3749
{ immediate: true, deep: true }
3850
);
3951
});
4052
onUnmounted(() => window.removeEventListener("resize", handleResize));
41-
4253
</script>
4354

4455
<style>
@@ -48,5 +59,7 @@ onUnmounted(() => window.removeEventListener("resize", handleResize));
4859
right: 0;
4960
left: 0;
5061
bottom: 0;
62+
filter: invert() hue-rotate(180deg);
63+
color: black;
5164
}
5265
</style>

0 commit comments

Comments
 (0)