20260610173132
This commit is contained in:
1 parent
65685d1a6a
commit
6cb198203d
56 files changed
+4265
-1210
No files matched your search
@@ -0,0 +1,425 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue";
|
||||
import { recognizeDocument, type UploadedFile, uploadFile } from "./api/file";
|
||||
|
||||
type PreviewType = "image" | "pdf" | "unsupported";
|
||||
type DocumentCategory = "customs_declaration" | "booking_note";
|
||||
|
||||
const fileInputRef = ref<HTMLInputElement | null>(null);
|
||||
const selectedFile = ref<File | null>(null);
|
||||
const uploadedFile = ref<UploadedFile | null>(null);
|
||||
const selectedDocumentCategory = ref<DocumentCategory | null>(null);
|
||||
const recognitionResult = ref<Record<string, unknown> | null>(null);
|
||||
const uploadError = ref("");
|
||||
const isUploading = ref(false);
|
||||
const isRecognizing = ref(false);
|
||||
|
||||
const documentCategoryOptions = [
|
||||
{
|
||||
label: "报关单",
|
||||
value: "customs_declaration",
|
||||
},
|
||||
{
|
||||
label: "托书",
|
||||
value: "booking_note",
|
||||
},
|
||||
] satisfies Array<{ label: string; value: DocumentCategory }>;
|
||||
|
||||
const selectedDocumentCategoryLabel = computed(() => {
|
||||
return documentCategoryOptions.find((item) => item.value === selectedDocumentCategory.value)?.label || "";
|
||||
});
|
||||
|
||||
const previewType = computed<PreviewType>(() => {
|
||||
const file = uploadedFile.value;
|
||||
|
||||
if (!file) {
|
||||
return "unsupported";
|
||||
}
|
||||
|
||||
if (file.contentType?.startsWith("image/")) {
|
||||
return "image";
|
||||
}
|
||||
|
||||
if (file.contentType === "application/pdf" || file.fileName.toLowerCase().endsWith(".pdf")) {
|
||||
return "pdf";
|
||||
}
|
||||
|
||||
return "unsupported";
|
||||
});
|
||||
|
||||
const formattedResult = computed(() => {
|
||||
if (!recognitionResult.value) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return JSON.stringify(recognitionResult.value, null, 2);
|
||||
});
|
||||
|
||||
const canRecognize = computed(() => {
|
||||
return Boolean(uploadedFile.value && selectedDocumentCategory.value) && !isRecognizing.value && !isUploading.value;
|
||||
});
|
||||
|
||||
function openFilePicker() {
|
||||
fileInputRef.value?.click();
|
||||
}
|
||||
|
||||
function handleCategoryChange() {
|
||||
recognitionResult.value = null;
|
||||
}
|
||||
|
||||
async function handleFileChange(event: Event) {
|
||||
const input = event.target as HTMLInputElement;
|
||||
const file = input.files?.[0];
|
||||
input.value = "";
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedFile.value = file;
|
||||
uploadedFile.value = null;
|
||||
recognitionResult.value = null;
|
||||
uploadError.value = "";
|
||||
isUploading.value = true;
|
||||
isRecognizing.value = false;
|
||||
|
||||
try {
|
||||
uploadedFile.value = await uploadFile(file);
|
||||
} catch (error) {
|
||||
selectedFile.value = null;
|
||||
uploadedFile.value = null;
|
||||
uploadError.value = error instanceof Error ? error.message : "文件上传失败";
|
||||
} finally {
|
||||
isUploading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function formatFileSize(size: number) {
|
||||
if (size < 1024) {
|
||||
return `${size} B`;
|
||||
}
|
||||
|
||||
if (size < 1024 * 1024) {
|
||||
return `${(size / 1024).toFixed(1)} KB`;
|
||||
}
|
||||
|
||||
return `${(size / 1024 / 1024).toFixed(1)} MB`;
|
||||
}
|
||||
|
||||
async function startRecognition() {
|
||||
const file = uploadedFile.value;
|
||||
const category = selectedDocumentCategory.value;
|
||||
|
||||
if (!file || !category || isRecognizing.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
isRecognizing.value = true;
|
||||
recognitionResult.value = null;
|
||||
|
||||
try {
|
||||
const result = await recognizeDocument({
|
||||
url: file.url,
|
||||
type: category,
|
||||
});
|
||||
console.log("PP-StructureV3 recognition result:", result);
|
||||
recognitionResult.value = result as Record<string, unknown>;
|
||||
} catch (error) {
|
||||
recognitionResult.value = {
|
||||
status: "failed",
|
||||
message: error instanceof Error ? error.message : "识别失败",
|
||||
url: file.url,
|
||||
type: category,
|
||||
};
|
||||
} finally {
|
||||
isRecognizing.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="declaration-demo">
|
||||
<header class="topbar">
|
||||
<div class="topbar-actions">
|
||||
<input
|
||||
ref="fileInputRef"
|
||||
class="file-input"
|
||||
type="file"
|
||||
accept="application/pdf,image/*"
|
||||
@change="handleFileChange"
|
||||
/>
|
||||
<n-button type="primary" :loading="isUploading" :disabled="isUploading" @click="openFilePicker">
|
||||
上传文件
|
||||
</n-button>
|
||||
<n-select
|
||||
v-model:value="selectedDocumentCategory"
|
||||
class="category-select"
|
||||
clearable
|
||||
placeholder="选择分类"
|
||||
:options="documentCategoryOptions"
|
||||
@update:value="handleCategoryChange"
|
||||
/>
|
||||
<n-button
|
||||
type="info"
|
||||
:disabled="!canRecognize"
|
||||
:loading="isRecognizing"
|
||||
@click="startRecognition"
|
||||
>
|
||||
开始识别
|
||||
</n-button>
|
||||
</div>
|
||||
|
||||
<div class="topbar-title">
|
||||
<h1>报关单 / 托书识别 Demo</h1>
|
||||
<p>上传 PDF 或图片,选择分类后在右侧查看模拟识别 JSON。</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="bottom">
|
||||
<section class="preview-panel" aria-label="文件预览">
|
||||
<div class="panel-header">
|
||||
<div>
|
||||
<h2>文件预览</h2>
|
||||
<p v-if="isUploading && selectedFile">
|
||||
正在上传 {{ selectedFile.name }} · {{ formatFileSize(selectedFile.size) }}
|
||||
</p>
|
||||
<p v-else-if="uploadedFile">
|
||||
{{ uploadedFile.originalName }} · {{ formatFileSize(uploadedFile.size) }}
|
||||
</p>
|
||||
<p v-else>支持 PDF、JPG、PNG 等格式</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="preview-surface">
|
||||
<n-spin v-if="isUploading" size="medium">
|
||||
<div class="recognizing-placeholder">正在上传文件...</div>
|
||||
</n-spin>
|
||||
|
||||
<template v-else-if="uploadedFile && previewType === 'image'">
|
||||
<img class="preview-image" :src="uploadedFile.url" :alt="uploadedFile.originalName" />
|
||||
</template>
|
||||
|
||||
<template v-else-if="uploadedFile && previewType === 'pdf'">
|
||||
<iframe class="preview-frame" :src="uploadedFile.url" :title="uploadedFile.originalName"></iframe>
|
||||
</template>
|
||||
|
||||
<n-empty
|
||||
v-else-if="uploadError"
|
||||
:description="uploadError"
|
||||
class="empty-state"
|
||||
/>
|
||||
|
||||
<n-empty
|
||||
v-else-if="!uploadedFile"
|
||||
description="请先上传报关单或托书文件"
|
||||
class="empty-state"
|
||||
/>
|
||||
|
||||
<n-empty v-else description="当前文件格式暂不支持预览" class="empty-state" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="result-panel" aria-label="识别内容">
|
||||
<div class="panel-header">
|
||||
<div>
|
||||
<h2>识别内容</h2>
|
||||
<p>{{ selectedDocumentCategoryLabel || "请选择报关单或托书分类" }} · JSON 结构化结果</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="result-surface">
|
||||
<n-spin v-if="isRecognizing" size="medium">
|
||||
<div class="recognizing-placeholder">正在生成模拟识别结果...</div>
|
||||
</n-spin>
|
||||
|
||||
<pre v-else-if="recognitionResult" class="json-output">{{ formattedResult }}</pre>
|
||||
|
||||
<n-empty v-else description="上传文件并选择分类后点击开始识别" class="empty-state" />
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.declaration-demo {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: #f4f6f8;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
min-height: 76px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 24px;
|
||||
padding: 14px 24px;
|
||||
border-bottom: 1px solid #dfe5ec;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.topbar-title h1 {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
line-height: 1.3;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.topbar-title p,
|
||||
.panel-header p {
|
||||
margin: 4px 0 0;
|
||||
color: #667085;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.topbar-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.file-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.category-select {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(360px, 1.05fr) minmax(360px, 0.95fr);
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.preview-panel,
|
||||
.result-panel {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid #dfe5ec;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
min-height: 62px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #e8edf3;
|
||||
background: #fbfcfe;
|
||||
}
|
||||
|
||||
.panel-header h2 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.preview-surface,
|
||||
.result-surface {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-content: stretch;
|
||||
background: #f8fafc;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.preview-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.preview-frame {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.result-surface {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.json-output {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
margin: 0;
|
||||
padding: 16px;
|
||||
background: #0b1220;
|
||||
color: #dbeafe;
|
||||
font-family:
|
||||
"Cascadia Mono", "SFMono-Regular", Consolas, "Liberation Mono", monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.empty-state,
|
||||
.recognizing-placeholder {
|
||||
width: 100%;
|
||||
min-height: 240px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.recognizing-placeholder {
|
||||
color: #667085;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
@media (max-width: 860px) {
|
||||
.topbar {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.topbar-actions {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.category-select {
|
||||
width: auto;
|
||||
flex: 1.2;
|
||||
}
|
||||
|
||||
.topbar-actions :deep(.n-button) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.preview-panel,
|
||||
.result-panel {
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,62 @@
|
||||
import { createAlova } from "alova";
|
||||
import adapterFetch from "alova/fetch";
|
||||
|
||||
export type UploadedFile = {
|
||||
originalName: string;
|
||||
fileName: string;
|
||||
url: string;
|
||||
size: number;
|
||||
contentType: string | null;
|
||||
};
|
||||
|
||||
export type RecognitionRequest = {
|
||||
url: string;
|
||||
type: string;
|
||||
};
|
||||
|
||||
export type RecognitionResponse = {
|
||||
url: string;
|
||||
type: string;
|
||||
ppStructureResult: unknown;
|
||||
};
|
||||
|
||||
const defaultApiBaseUrl = "http://192.168.1.137:8082";
|
||||
|
||||
function normalizeBaseUrl(baseUrl: string | undefined) {
|
||||
const value = baseUrl?.trim() || defaultApiBaseUrl;
|
||||
return value.endsWith("/") ? value.slice(0, -1) : value;
|
||||
}
|
||||
|
||||
async function readErrorMessage(response: Response) {
|
||||
const fallback = `请求失败,状态码 ${response.status}`;
|
||||
|
||||
try {
|
||||
const data = await response.json();
|
||||
return data.detail || data.message || data.error || fallback;
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
const http = createAlova({
|
||||
baseURL: normalizeBaseUrl(import.meta.env.VITE_API_BASE_URL),
|
||||
requestAdapter: adapterFetch(),
|
||||
responded: async (response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error(await readErrorMessage(response));
|
||||
}
|
||||
|
||||
return response.json();
|
||||
},
|
||||
});
|
||||
|
||||
export function uploadFile(file: File) {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
|
||||
return http.Post<UploadedFile>("/api/files/upload", formData).send();
|
||||
}
|
||||
|
||||
export function recognizeDocument(data: RecognitionRequest) {
|
||||
return http.Post<RecognitionResponse>("/api/recognitions/start", data).send();
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="77" height="47" fill="none" aria-labelledby="vite-logo-title" viewBox="0 0 77 47"><title id="vite-logo-title">Vite</title><style>.parenthesis{fill:#000}@media (prefers-color-scheme:dark){.parenthesis{fill:#fff}}</style><path fill="#9135ff" d="M40.151 45.71c-.663.844-2.02.374-2.02-.699V34.708a2.26 2.26 0 0 0-2.262-2.262H24.493c-.92 0-1.457-1.04-.92-1.788l7.479-10.471c1.07-1.498 0-3.578-1.842-3.578H15.443c-.92 0-1.456-1.04-.92-1.788l9.696-13.576c.213-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.472c-1.07 1.497 0 3.578 1.842 3.578h11.376c.944 0 1.474 1.087.89 1.83L40.153 45.712z"/><mask id="a" width="48" height="47" x="14" y="0" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="#000" d="M40.047 45.71c-.663.843-2.02.374-2.02-.699V34.708a2.26 2.26 0 0 0-2.262-2.262H24.389c-.92 0-1.457-1.04-.92-1.788l7.479-10.472c1.07-1.497 0-3.578-1.842-3.578H15.34c-.92 0-1.456-1.04-.92-1.788l9.696-13.575c.213-.297.556-.474.92-.474H53.93c.92 0 1.456 1.04.92 1.788L47.37 13.03c-1.07 1.498 0 3.578 1.842 3.578h11.376c.944 0 1.474 1.088.89 1.831L40.049 45.712z"/></mask><g mask="url(#a)"><g filter="url(#b)"><ellipse cx="5.508" cy="14.704" fill="#eee6ff" rx="5.508" ry="14.704" transform="rotate(269.814 20.96 11.29)scale(-1 1)"/></g><g filter="url(#c)"><ellipse cx="10.399" cy="29.851" fill="#eee6ff" rx="10.399" ry="29.851" transform="rotate(89.814 -16.902 -8.275)scale(1 -1)"/></g><g filter="url(#d)"><ellipse cx="5.508" cy="30.487" fill="#8900ff" rx="5.508" ry="30.487" transform="rotate(89.814 -19.197 -7.127)scale(1 -1)"/></g><g filter="url(#e)"><ellipse cx="5.508" cy="30.599" fill="#8900ff" rx="5.508" ry="30.599" transform="rotate(89.814 -25.928 4.177)scale(1 -1)"/></g><g filter="url(#f)"><ellipse cx="5.508" cy="30.599" fill="#8900ff" rx="5.508" ry="30.599" transform="rotate(89.814 -25.738 5.52)scale(1 -1)"/></g><g filter="url(#g)"><ellipse cx="14.072" cy="22.078" fill="#eee6ff" rx="14.072" ry="22.078" transform="rotate(93.35 31.245 55.578)scale(-1 1)"/></g><g filter="url(#h)"><ellipse cx="3.47" cy="21.501" fill="#8900ff" rx="3.47" ry="21.501" transform="rotate(89.009 35.419 55.202)scale(-1 1)"/></g><g filter="url(#i)"><ellipse cx="3.47" cy="21.501" fill="#8900ff" rx="3.47" ry="21.501" transform="rotate(89.009 35.419 55.202)scale(-1 1)"/></g><g filter="url(#j)"><ellipse cx="14.592" cy="9.743" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(39.51 14.592 9.743)"/></g><g filter="url(#k)"><ellipse cx="61.728" cy="-5.321" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(37.892 61.728 -5.32)"/></g><g filter="url(#l)"><ellipse cx="55.618" cy="7.104" fill="#00c2ff" rx="5.971" ry="9.665" transform="rotate(37.892 55.618 7.104)"/></g><g filter="url(#m)"><ellipse cx="12.326" cy="39.103" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(37.892 12.326 39.103)"/></g><g filter="url(#n)"><ellipse cx="12.326" cy="39.103" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(37.892 12.326 39.103)"/></g><g filter="url(#o)"><ellipse cx="49.857" cy="30.678" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(37.892 49.857 30.678)"/></g><g filter="url(#p)"><ellipse cx="52.623" cy="33.171" fill="#00c2ff" rx="5.971" ry="15.297" transform="rotate(37.892 52.623 33.17)"/></g></g><path d="M6.919 0c-9.198 13.166-9.252 33.575 0 46.789h6.215c-9.25-13.214-9.196-33.623 0-46.789zm62.424 0h-6.215c9.198 13.166 9.252 33.575 0 46.789h6.215c9.25-13.214 9.196-33.623 0-46.789" class="parenthesis"/><defs><filter id="b" width="60.045" height="41.654" x="-5.564" y="16.92" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="7.659"/></filter><filter id="c" width="90.34" height="51.437" x="-40.407" y="-6.762" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="7.659"/></filter><filter id="d" width="79.355" height="29.4" x="-35.435" y="2.801" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="e" width="79.579" height="29.4" x="-30.84" y="20.8" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="f" width="79.579" height="29.4" x="-29.307" y="21.949" color-interpolation-filters="sRGB" filterUnits="usLine truncated
|
||||
|
After Width: | Height: | Size: 8.5 KiB |
@@ -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 |
@@ -0,0 +1,93 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import viteLogo from "../assets/vite.svg";
|
||||
import heroImg from "../assets/hero.png";
|
||||
import vueLogo from "../assets/vue.svg";
|
||||
|
||||
const count = ref(0);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section id="center">
|
||||
<div class="hero">
|
||||
<img :src="heroImg" class="base" width="170" height="179" alt="" />
|
||||
<img :src="vueLogo" class="framework" alt="Vue logo" />
|
||||
<img :src="viteLogo" class="vite" alt="Vite logo" />
|
||||
</div>
|
||||
<div>
|
||||
<h1>Get started</h1>
|
||||
<p>Edit <code>src/App.vue</code> and save to test <code>HMR</code></p>
|
||||
</div>
|
||||
<button type="button" class="counter" @click="count++">Count is {{ count }}</button>
|
||||
</section>
|
||||
|
||||
<div class="ticks"></div>
|
||||
|
||||
<section id="next-steps">
|
||||
<div id="docs">
|
||||
<svg class="icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#documentation-icon"></use>
|
||||
</svg>
|
||||
<h2>Documentation</h2>
|
||||
<p>Your questions, answered</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://vite.dev/" target="_blank">
|
||||
<img class="logo" :src="viteLogo" alt="" />
|
||||
Explore Vite
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://vuejs.org/" target="_blank">
|
||||
<img class="button-icon" :src="vueLogo" alt="" />
|
||||
Learn more
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="social">
|
||||
<svg class="icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#social-icon"></use>
|
||||
</svg>
|
||||
<h2>Connect with us</h2>
|
||||
<p>Join the Vite community</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://github.com/vitejs/vite" target="_blank">
|
||||
<svg class="button-icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#github-icon"></use>
|
||||
</svg>
|
||||
GitHub
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://chat.vite.dev/" target="_blank">
|
||||
<svg class="button-icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#discord-icon"></use>
|
||||
</svg>
|
||||
Discord
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://x.com/vite_js" target="_blank">
|
||||
<svg class="button-icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#x-icon"></use>
|
||||
</svg>
|
||||
X.com
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://bsky.app/profile/vite.dev" target="_blank">
|
||||
<svg class="button-icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#bluesky-icon"></use>
|
||||
</svg>
|
||||
Bluesky
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="ticks"></div>
|
||||
<section id="spacer"></section>
|
||||
</template>
|
||||
@@ -0,0 +1,6 @@
|
||||
import { createApp } from "vue";
|
||||
import "./style.css";
|
||||
import App from "./App.vue";
|
||||
import naive from "naive-ui";
|
||||
|
||||
createApp(App).use(naive).mount("#app");
|
||||
@@ -0,0 +1,27 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
width: 100%;
|
||||
min-width: 320px;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family:
|
||||
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
background: #f4f6f8;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
font: inherit;
|
||||
}
|
||||
Reference in new issue
Block a user