提交饱和编辑的相关设计,及检验代码
This commit is contained in:
5
interactive/frontend/README.md
Normal file
5
interactive/frontend/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Vue 3 + TypeScript + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).
|
||||
77
interactive/frontend/auto-imports.d.ts
vendored
Normal file
77
interactive/frontend/auto-imports.d.ts
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
// biome-ignore lint: disable
|
||||
export {}
|
||||
declare global {
|
||||
const EffectScope: typeof import('vue').EffectScope
|
||||
const computed: typeof import('vue').computed
|
||||
const createApp: typeof import('vue').createApp
|
||||
const customRef: typeof import('vue').customRef
|
||||
const defineAsyncComponent: typeof import('vue').defineAsyncComponent
|
||||
const defineComponent: typeof import('vue').defineComponent
|
||||
const effectScope: typeof import('vue').effectScope
|
||||
const getCurrentInstance: typeof import('vue').getCurrentInstance
|
||||
const getCurrentScope: typeof import('vue').getCurrentScope
|
||||
const getCurrentWatcher: typeof import('vue').getCurrentWatcher
|
||||
const h: typeof import('vue').h
|
||||
const inject: typeof import('vue').inject
|
||||
const isProxy: typeof import('vue').isProxy
|
||||
const isReactive: typeof import('vue').isReactive
|
||||
const isReadonly: typeof import('vue').isReadonly
|
||||
const isRef: typeof import('vue').isRef
|
||||
const isShallow: typeof import('vue').isShallow
|
||||
const markRaw: typeof import('vue').markRaw
|
||||
const nextTick: typeof import('vue').nextTick
|
||||
const onActivated: typeof import('vue').onActivated
|
||||
const onBeforeMount: typeof import('vue').onBeforeMount
|
||||
const onBeforeUnmount: typeof import('vue').onBeforeUnmount
|
||||
const onBeforeUpdate: typeof import('vue').onBeforeUpdate
|
||||
const onDeactivated: typeof import('vue').onDeactivated
|
||||
const onErrorCaptured: typeof import('vue').onErrorCaptured
|
||||
const onMounted: typeof import('vue').onMounted
|
||||
const onRenderTracked: typeof import('vue').onRenderTracked
|
||||
const onRenderTriggered: typeof import('vue').onRenderTriggered
|
||||
const onScopeDispose: typeof import('vue').onScopeDispose
|
||||
const onServerPrefetch: typeof import('vue').onServerPrefetch
|
||||
const onUnmounted: typeof import('vue').onUnmounted
|
||||
const onUpdated: typeof import('vue').onUpdated
|
||||
const onWatcherCleanup: typeof import('vue').onWatcherCleanup
|
||||
const provide: typeof import('vue').provide
|
||||
const reactive: typeof import('vue').reactive
|
||||
const readonly: typeof import('vue').readonly
|
||||
const ref: typeof import('vue').ref
|
||||
const resolveComponent: typeof import('vue').resolveComponent
|
||||
const shallowReactive: typeof import('vue').shallowReactive
|
||||
const shallowReadonly: typeof import('vue').shallowReadonly
|
||||
const shallowRef: typeof import('vue').shallowRef
|
||||
const toRaw: typeof import('vue').toRaw
|
||||
const toRef: typeof import('vue').toRef
|
||||
const toRefs: typeof import('vue').toRefs
|
||||
const toValue: typeof import('vue').toValue
|
||||
const triggerRef: typeof import('vue').triggerRef
|
||||
const unref: typeof import('vue').unref
|
||||
const useAttrs: typeof import('vue').useAttrs
|
||||
const useCssModule: typeof import('vue').useCssModule
|
||||
const useCssVars: typeof import('vue').useCssVars
|
||||
const useDialog: typeof import('naive-ui').useDialog
|
||||
const useId: typeof import('vue').useId
|
||||
const useLoadingBar: typeof import('naive-ui').useLoadingBar
|
||||
const useMessage: typeof import('naive-ui').useMessage
|
||||
const useModel: typeof import('vue').useModel
|
||||
const useNotification: typeof import('naive-ui').useNotification
|
||||
const useSlots: typeof import('vue').useSlots
|
||||
const useTemplateRef: typeof import('vue').useTemplateRef
|
||||
const watch: typeof import('vue').watch
|
||||
const watchEffect: typeof import('vue').watchEffect
|
||||
const watchPostEffect: typeof import('vue').watchPostEffect
|
||||
const watchSyncEffect: typeof import('vue').watchSyncEffect
|
||||
}
|
||||
// for type re-export
|
||||
declare global {
|
||||
// @ts-ignore
|
||||
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||
import('vue')
|
||||
}
|
||||
30
interactive/frontend/components.d.ts
vendored
Normal file
30
interactive/frontend/components.d.ts
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
// biome-ignore lint: disable
|
||||
// oxlint-disable
|
||||
// ------
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
|
||||
export {}
|
||||
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
|
||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||
NDataTable: typeof import('naive-ui')['NDataTable']
|
||||
NFlex: typeof import('naive-ui')['NFlex']
|
||||
NForm: typeof import('naive-ui')['NForm']
|
||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||
NGi: typeof import('naive-ui')['NGi']
|
||||
NGrid: typeof import('naive-ui')['NGrid']
|
||||
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
||||
NLayout: typeof import('naive-ui')['NLayout']
|
||||
NLayoutContent: typeof import('naive-ui')['NLayoutContent']
|
||||
NLayoutHeader: typeof import('naive-ui')['NLayoutHeader']
|
||||
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
||||
NPagination: typeof import('naive-ui')['NPagination']
|
||||
NSelect: typeof import('naive-ui')['NSelect']
|
||||
}
|
||||
}
|
||||
13
interactive/frontend/index.html
Normal file
13
interactive/frontend/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>frontend</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
26
interactive/frontend/package.json
Normal file
26
interactive/frontend/package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc -b && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.13.5",
|
||||
"naive-ui": "^2.43.2",
|
||||
"unplugin-auto-import": "^21.0.0",
|
||||
"unplugin-vue-components": "^31.0.0",
|
||||
"vue": "^3.5.25"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.10.1",
|
||||
"@vitejs/plugin-vue": "^6.0.2",
|
||||
"@vue/tsconfig": "^0.8.1",
|
||||
"typescript": "~5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
"vue-tsc": "^3.1.5"
|
||||
}
|
||||
}
|
||||
1554
interactive/frontend/pnpm-lock.yaml
generated
Normal file
1554
interactive/frontend/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
1
interactive/frontend/public/vite.svg
Normal file
1
interactive/frontend/public/vite.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
26
interactive/frontend/src/App.vue
Normal file
26
interactive/frontend/src/App.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import HelloWorld from './components/HelloWorld.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-config-provider>
|
||||
<n-message-provider>
|
||||
<HelloWorld />
|
||||
</n-message-provider>
|
||||
</n-config-provider>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo.vue:hover {
|
||||
filter: drop-shadow(0 0 2em #42b883aa);
|
||||
}
|
||||
</style>
|
||||
1
interactive/frontend/src/assets/vue.svg
Normal file
1
interactive/frontend/src/assets/vue.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 496 B |
375
interactive/frontend/src/components/HelloWorld.vue
Normal file
375
interactive/frontend/src/components/HelloWorld.vue
Normal file
@@ -0,0 +1,375 @@
|
||||
<script setup lang="ts">
|
||||
import {onMounted, ref, h, watch} from 'vue'
|
||||
import axios, { AxiosError, type AxiosResponse } from "axios";
|
||||
import type {SelectOption, DataTableSortState} from "naive-ui"
|
||||
import { useMessage, NEllipsis, NButton, NPopover } from "naive-ui"
|
||||
|
||||
const BASE_URL = "/api" // http://10.126.126.11:5555/api
|
||||
|
||||
const APIs = {
|
||||
unique: `${BASE_URL}/gene`,
|
||||
content: `${BASE_URL}/records`,
|
||||
};
|
||||
|
||||
const message = useMessage();
|
||||
const genes = ref<SelectOption[]|null>(null);
|
||||
const tables = [
|
||||
{"value": "pridict2", "label": "Pridict2"},
|
||||
{"value": "prime_design", "label": "Prime Design"},
|
||||
]
|
||||
|
||||
interface FormData {
|
||||
gene: string | null
|
||||
source: string
|
||||
pbs_len: number | null
|
||||
rtt_len: number | null
|
||||
}
|
||||
|
||||
const formData = ref<FormData>({
|
||||
source: "pridict2",
|
||||
gene: null,
|
||||
pbs_len: 0,
|
||||
rtt_len: 0,
|
||||
})
|
||||
|
||||
|
||||
interface Pagination {
|
||||
order: string;
|
||||
order_by: string;
|
||||
total: number;
|
||||
page: number;
|
||||
length: number;
|
||||
}
|
||||
|
||||
const pagination = ref<Pagination>({
|
||||
order: "desc",
|
||||
order_by: "gene",
|
||||
total: 10,
|
||||
length: 10,
|
||||
page: 1,
|
||||
});
|
||||
|
||||
|
||||
function processSorter(
|
||||
options: DataTableSortState | DataTableSortState[] | null,
|
||||
) {
|
||||
if (options !== null) {
|
||||
options = options as DataTableSortState;
|
||||
pagination.value.order_by = options.columnKey.toString();
|
||||
pagination.value.order =
|
||||
typeof options.order === "boolean" ? "asc" : options.order;
|
||||
} else {
|
||||
pagination.value.order_by = "id";
|
||||
pagination.value.order = "descend";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
interface RowData {
|
||||
id: number;
|
||||
gene: string
|
||||
aa: number
|
||||
sequence: string
|
||||
src: string
|
||||
dst: string
|
||||
k562: number|null
|
||||
hek: number|null
|
||||
k562_rank: number|null
|
||||
hek_rank: number|null
|
||||
template: string
|
||||
strand: string
|
||||
pbs_len: number
|
||||
rtt_len: number
|
||||
spacer: string|null
|
||||
scaffold: string|null
|
||||
pegrna: string|null
|
||||
pbs: string
|
||||
rtt: string
|
||||
extension: string|null
|
||||
before_spacer: string|null
|
||||
after_spacer: string|null
|
||||
before_pegnra_ext: string|null
|
||||
after_pegnra_ext: string|null
|
||||
}
|
||||
|
||||
|
||||
let columns = [
|
||||
{
|
||||
title: "Gene", key: "gene", defaultSortOrder: "descend", width: 60, resizable: true,
|
||||
},
|
||||
{
|
||||
title: "AA (n)", key: "aa", defaultSortOrder: "ascend", width: 50, resizable: true, sorter: true,
|
||||
},
|
||||
{
|
||||
title: "name", key: "sequence", defaultSortOrder: "ascend", width: 100, resizable: true, sorter: true,
|
||||
},
|
||||
{
|
||||
title: "原序列", key: "src", defaultSortOrder: "ascend", width: 60, resizable: true, sorter: true,
|
||||
},
|
||||
{
|
||||
title: "编辑后", key: "dst", defaultSortOrder: "ascend", width: 60, resizable: true, sorter: true,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
let post_columns = [
|
||||
{title: "strand", key: "strand", defaultSortOrder: "ascend", maxWidth: 20, resizable: true,},
|
||||
{title: "PBS len", key: "pbs_len", defaultSortOrder: "ascend", maxWidth: 20, resizable: true, sorter: true,},
|
||||
{title: "RTT len", key: "rtt_len", defaultSortOrder: "ascend", maxWidth: 20, resizable: true, sorter: true,},
|
||||
{title: "PBS", key: "pbs", defaultSortOrder: "ascend", width: 120, resizable: true,},
|
||||
{title: "RTT", key: "rtt", defaultSortOrder: "ascend", width: 120, resizable: true,}
|
||||
]
|
||||
|
||||
|
||||
const copyText = (text: string) => {
|
||||
const textArea = document.createElement("textarea");
|
||||
textArea.value = text;
|
||||
|
||||
textArea.style.top = "0";
|
||||
textArea.style.left = "0";
|
||||
textArea.style.position = "fixed";
|
||||
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
|
||||
try {
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(textArea);
|
||||
return Promise.resolve();
|
||||
} catch (err) {
|
||||
document.body.removeChild(textArea);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const createColumns = () => {
|
||||
|
||||
let real_columns = [...columns]
|
||||
|
||||
let rest_columns = ["spacer", "extension", "before_spacer", "after_spacer", "before_pegnra_ext", "after_pegnra_ext"]
|
||||
if (formData.value.source === "pridict2") {
|
||||
rest_columns = ["template", "spacer", "scaffold", "pegrna"]
|
||||
|
||||
for (let i of ["k562", "hek"]) {
|
||||
real_columns.push(
|
||||
{
|
||||
title: i, key: i, width: 60, resizable: true, sorter: true,
|
||||
render: function(row: RowData) {
|
||||
return h(
|
||||
NPopover, {
|
||||
trigger: "hover"
|
||||
}, {
|
||||
trigger: () => `${parseFloat(row[i].toFixed(2))} (${row[i + "_rank"]})`,
|
||||
default: () => {`Score: ${row[i]}; Rank=${row[i + "_rank"]}`}
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
real_columns = real_columns.concat(post_columns)
|
||||
|
||||
for (let i of rest_columns) {
|
||||
real_columns.push({
|
||||
title:i,
|
||||
key: i,
|
||||
width: 240,
|
||||
resizable: true,
|
||||
render: (row: RowData) => {
|
||||
return h(
|
||||
NButton, {
|
||||
size:"small", type: "primary", dashed: true,
|
||||
onClick: () => {copyText(row[i])}
|
||||
},
|
||||
{
|
||||
default: () => {
|
||||
return h(
|
||||
NEllipsis, {
|
||||
style: "max-width: 200px",
|
||||
tooltip: {
|
||||
style: {
|
||||
maxWidth: '300px',
|
||||
whiteSpace: 'pre-wrap', // 关键:允许换行
|
||||
wordBreak: 'break-word' // 允许单词内换行
|
||||
}
|
||||
}
|
||||
},
|
||||
{default: () => row[i]}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
return real_columns
|
||||
};
|
||||
const loading = ref(false)
|
||||
|
||||
const data = ref<RowData[]>([]);
|
||||
|
||||
const getRecords = () => {
|
||||
loading.value = true;
|
||||
let params = {
|
||||
gene: formData.value.gene,
|
||||
pbs_len: formData.value.pbs_len,
|
||||
rtt_len: formData.value.rtt_len,
|
||||
source: formData.value.source,
|
||||
offset: pagination.value.page,
|
||||
length: pagination.value.length,
|
||||
order_by: pagination.value.order_by,
|
||||
order: pagination.value.order,
|
||||
}
|
||||
axios
|
||||
.get(APIs.content, { params: params })
|
||||
.then((response: AxiosResponse) => {
|
||||
let resp = response.data;
|
||||
data.value = resp.data;
|
||||
|
||||
if (resp.total !== pagination.value.total) {
|
||||
pagination.value.total = resp.total;
|
||||
}
|
||||
|
||||
if (resp.length !== pagination.value.length) {
|
||||
pagination.value.length = resp.length;
|
||||
}
|
||||
|
||||
if (pagination.value.page > Math.ceil(resp.total / resp.length)) {
|
||||
pagination.value.page = Math.ceil(resp.total / resp.length);
|
||||
}
|
||||
})
|
||||
.catch((error: Error | AxiosError) => {
|
||||
message.error(error.message);
|
||||
}).finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
axios.get(APIs.unique).then((response: AxiosResponse) => {
|
||||
let res = []
|
||||
|
||||
for (let i of response.data) {
|
||||
res.push({"value": i, "label": i})
|
||||
}
|
||||
genes.value = res
|
||||
formData.value.gene = res[0].value
|
||||
}).finally(() => {
|
||||
getRecords()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
watch(
|
||||
() => [formData, pagination],
|
||||
(_) => {
|
||||
axios.get(APIs.unique, {params: {source: formData.value.source}}).then((response: AxiosResponse) => {
|
||||
let res = []
|
||||
|
||||
for (let i of response.data) {
|
||||
res.push({"value": i, "label": i})
|
||||
}
|
||||
genes.value = res
|
||||
}).finally(() => {
|
||||
getRecords()
|
||||
})
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-grid cols="24" :y-gap="8" item-responsive>
|
||||
<n-gi span="0 400:1 800:2" responsive="self" />
|
||||
<n-gi span="24 400:22 600:20" responsive="self">
|
||||
<n-layout>
|
||||
<n-layout-header style="min-height: 30px; padding: 10px" bordered>
|
||||
<n-form label-placement="left">
|
||||
<n-flex justify="space-around" style="margin-right: 10px">
|
||||
<!-- 查询界面 -->
|
||||
<n-grid cols="4" :x-gap="12" :y-gap="8" item-responsive>
|
||||
<n-gi span="4 400:2 800:1" responsive="self">
|
||||
<n-form-item label="表">
|
||||
<n-select v-model:value="formData.source" :options="tables" filterable clearable/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="4 400:2 800:1" responsive="self">
|
||||
<n-form-item label="基因">
|
||||
<n-select v-model:value="formData.gene" :options="genes" filterable clearable/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="4 400:2 800:1" responsive="self">
|
||||
<n-form-item label="PBS len <= ">
|
||||
<n-input-number v-model:value="formData.pbs_len" clearable/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="4 400:2 800:1" responsive="self">
|
||||
<n-form-item label="RTT len <= ">
|
||||
<n-input-number v-model:value="formData.rtt_len" clearable/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</n-flex>
|
||||
</n-form>
|
||||
</n-layout-header>
|
||||
|
||||
<n-layout-content style="padding-top: 10px; padding-left: 5px" bordered>
|
||||
<n-flex justify="center">
|
||||
<n-pagination
|
||||
v-model:page="pagination.page"
|
||||
:page-sizes="[10, 20, 30, 40]"
|
||||
:item-count="pagination.total"
|
||||
v-model:page-size="pagination.length"
|
||||
show-quick-jumper
|
||||
show-size-picker
|
||||
style="padding: 5px"
|
||||
/>
|
||||
</n-flex>
|
||||
|
||||
<n-data-table
|
||||
:columns="createColumns()"
|
||||
:data="data"
|
||||
:loading="loading"
|
||||
:scroll-x="1800"
|
||||
width="100%"
|
||||
:max-height="600"
|
||||
:row-key="
|
||||
(row: RowData) => (row.id)
|
||||
"
|
||||
striped
|
||||
bordered
|
||||
@update:sorter="processSorter"
|
||||
sticky-expanded-rows
|
||||
/>
|
||||
|
||||
<n-flex justify="center">
|
||||
<n-pagination
|
||||
v-model:page="pagination.page"
|
||||
:page-sizes="[10, 20, 30, 40]"
|
||||
:item-count="pagination.total"
|
||||
v-model:page-size="pagination.length"
|
||||
show-quick-jumper
|
||||
show-size-picker
|
||||
style="padding: 5px"
|
||||
/>
|
||||
</n-flex>
|
||||
</n-layout-content>
|
||||
</n-layout>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 关键:为表格容器设置固定宽度和溢出控制 */
|
||||
.table-container {
|
||||
width: 100%; /* 或者固定宽度,如 1200px */
|
||||
overflow-x: auto; /* 确保容器可水平滚动 */
|
||||
}
|
||||
|
||||
</style>
|
||||
4
interactive/frontend/src/main.ts
Normal file
4
interactive/frontend/src/main.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
|
||||
createApp(App).mount("#app");
|
||||
79
interactive/frontend/src/style.css
Normal file
79
interactive/frontend/src/style.css
Normal file
@@ -0,0 +1,79 @@
|
||||
:root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
16
interactive/frontend/tsconfig.app.json
Normal file
16
interactive/frontend/tsconfig.app.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"types": ["vite/client"],
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||
}
|
||||
7
interactive/frontend/tsconfig.json
Normal file
7
interactive/frontend/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
26
interactive/frontend/tsconfig.node.json
Normal file
26
interactive/frontend/tsconfig.node.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2023",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"types": ["node"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
38
interactive/frontend/vite.config.ts
Normal file
38
interactive/frontend/vite.config.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import AutoImport from "unplugin-auto-import/vite";
|
||||
import { NaiveUiResolver } from "unplugin-vue-components/resolvers";
|
||||
import Components from "unplugin-vue-components/vite";
|
||||
// vite.config.ts
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
server: {
|
||||
watch: {
|
||||
// 使用轮询模式,避免文件描述符问题
|
||||
usePolling: true,
|
||||
interval: 1000,
|
||||
// 忽略不需要监视的目录
|
||||
ignored: ["**/node_modules/**", "**/.git/**", "**/.next/**"],
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
AutoImport({
|
||||
imports: [
|
||||
"vue",
|
||||
{
|
||||
"naive-ui": [
|
||||
"useDialog",
|
||||
"useMessage",
|
||||
"useNotification",
|
||||
"useLoadingBar",
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
Components({
|
||||
resolvers: [NaiveUiResolver()],
|
||||
}),
|
||||
],
|
||||
});
|
||||
Reference in New Issue
Block a user