Kevin Jiang лет назад: 3
Сommit
9c2fa8df1b

+ 20 - 0
.eslintrc.cjs

@@ -0,0 +1,20 @@
+/* eslint-env node */
+require("@rushstack/eslint-patch/modern-module-resolution");
+
+module.exports = {
+  root: true,
+  extends: [
+    "plugin:vue/vue3-essential",
+    "eslint:recommended",
+    "@vue/eslint-config-typescript",
+  ],
+  overrides: [
+    {
+      files: ["cypress/e2e/**.{cy,spec}.{js,ts,jsx,tsx}"],
+      extends: ["plugin:cypress/recommended"],
+    },
+  ],
+  parserOptions: {
+    ecmaVersion: "latest",
+  },
+};

+ 28 - 0
.gitignore

@@ -0,0 +1,28 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+dist-ssr
+coverage
+*.local
+
+/cypress/videos/
+/cypress/screenshots/
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 3 - 0
.vscode/extensions.json

@@ -0,0 +1,3 @@
+{
+  "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
+}

+ 59 - 0
README.md

@@ -0,0 +1,59 @@
+# front
+
+This template should help get you started developing with Vue 3 in Vite.
+
+## Recommended IDE Setup
+
+[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
+
+## Type Support for `.vue` Imports in TS
+
+TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
+
+If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
+
+1. Disable the built-in TypeScript Extension
+    1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette
+    2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
+2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
+
+## Customize configuration
+
+See [Vite Configuration Reference](https://vitejs.dev/config/).
+
+## Project Setup
+
+```sh
+npm install
+```
+
+### Compile and Hot-Reload for Development
+
+```sh
+npm run dev
+```
+
+### Type-Check, Compile and Minify for Production
+
+```sh
+npm run build
+```
+
+### Run Unit Tests with [Vitest](https://vitest.dev/)
+
+```sh
+npm run test:unit
+```
+
+### Run End-to-End Tests with [Cypress](https://www.cypress.io/)
+
+```sh
+npm run build
+npm run test:e2e # or `npm run test:e2e:ci` for headless testing
+```
+
+### Lint with [ESLint](https://eslint.org/)
+
+```sh
+npm run lint
+```

+ 8 - 0
cypress.config.ts

@@ -0,0 +1,8 @@
+import { defineConfig } from "cypress";
+
+export default defineConfig({
+  e2e: {
+    specPattern: "cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}",
+    baseUrl: "http://localhost:4173",
+  },
+});

+ 8 - 0
cypress/e2e/example.cy.ts

@@ -0,0 +1,8 @@
+// https://docs.cypress.io/api/introduction/api.html
+
+describe("My First Test", () => {
+  it("visits the app root url", () => {
+    cy.visit("/");
+    cy.contains("h1", "You did it!");
+  });
+});

+ 10 - 0
cypress/e2e/tsconfig.json

@@ -0,0 +1,10 @@
+{
+  "extends": "@vue/tsconfig/tsconfig.web.json",
+  "include": ["./**/*", "../support/**/*"],
+  "compilerOptions": {
+    "isolatedModules": false,
+    "target": "es5",
+    "lib": ["es5", "dom"],
+    "types": ["cypress"]
+  }
+}

+ 5 - 0
cypress/fixtures/example.json

@@ -0,0 +1,5 @@
+{
+  "name": "Using fixtures to represent data",
+  "email": "hello@cypress.io",
+  "body": "Fixtures are a great way to mock data for responses to routes"
+}

+ 39 - 0
cypress/support/commands.ts

@@ -0,0 +1,39 @@
+/// <reference types="cypress" />
+// ***********************************************
+// This example commands.ts shows you how to
+// create various custom commands and overwrite
+// existing commands.
+//
+// For more comprehensive examples of custom
+// commands please read more here:
+// https://on.cypress.io/custom-commands
+// ***********************************************
+//
+//
+// -- This is a parent command --
+// Cypress.Commands.add('login', (email, password) => { ... })
+//
+//
+// -- This is a child command --
+// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
+//
+//
+// -- This is a dual command --
+// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
+//
+//
+// -- This will overwrite an existing command --
+// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
+//
+// declare global {
+//   namespace Cypress {
+//     interface Chainable {
+//       login(email: string, password: string): Chainable<void>
+//       drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
+//       dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
+//       visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
+//     }
+//   }
+// }
+
+export {};

+ 20 - 0
cypress/support/e2e.ts

@@ -0,0 +1,20 @@
+// ***********************************************************
+// This example support/index.js is processed and
+// loaded automatically before your test files.
+//
+// This is a great place to put global configuration and
+// behavior that modifies Cypress.
+//
+// You can change the location of this file or turn off
+// automatically serving support files with the
+// 'supportFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/configuration
+// ***********************************************************
+
+// Import commands.js using ES2015 syntax:
+import "./commands";
+
+// Alternatively you can use CommonJS syntax:
+// require('./commands')

+ 1 - 0
env.d.ts

@@ -0,0 +1 @@
+/// <reference types="vite/client" />

+ 13 - 0
index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" href="/favicon.ico" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>技淘平台智慧产权大脑</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.ts"></script>
+  </body>
+</html>

Разница между файлами не показана из-за своего большого размера
+ 12618 - 0
package-lock.json


+ 44 - 0
package.json

@@ -0,0 +1,44 @@
+{
+  "name": "front",
+  "version": "0.0.0",
+  "scripts": {
+    "dev": "vite",
+    "build": "run-p type-check build-only",
+    "preview": "vite preview --port 4173",
+    "test:unit": "vitest --environment jsdom",
+    "test:e2e": "start-server-and-test preview http://localhost:4173/ 'cypress open --e2e'",
+    "test:e2e:ci": "start-server-and-test preview http://localhost:4173/ 'cypress run --e2e'",
+    "build-only": "vite build",
+    "type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
+    "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
+  },
+  "dependencies": {
+    "ant-design-vue": "^3.2.13",
+    "pinia": "^2.0.21",
+    "vue": "^3.2.38",
+    "vue-router": "^4.1.5"
+  },
+  "devDependencies": {
+    "@rushstack/eslint-patch": "^1.1.4",
+    "@types/jsdom": "^20.0.0",
+    "@types/node": "^16.11.56",
+    "@vitejs/plugin-vue": "^3.0.3",
+    "@vitejs/plugin-vue-jsx": "^2.0.1",
+    "@vue/eslint-config-typescript": "^11.0.0",
+    "@vue/test-utils": "^2.0.2",
+    "@vue/tsconfig": "^0.1.3",
+    "cypress": "^10.7.0",
+    "eslint": "^8.22.0",
+    "eslint-plugin-cypress": "^2.12.1",
+    "eslint-plugin-vue": "^9.3.0",
+    "jsdom": "^20.0.0",
+    "less": "^4.1.3",
+    "npm-run-all": "^4.1.5",
+    "sass": "^1.56.0",
+    "start-server-and-test": "^1.14.0",
+    "typescript": "~4.7.4",
+    "vite": "^3.0.9",
+    "vitest": "^0.23.0",
+    "vue-tsc": "^0.40.7"
+  }
+}

BIN
public/favicon.ico


+ 7 - 0
src/App.vue

@@ -0,0 +1,7 @@
+<script setup lang="ts">
+import { RouterView } from "vue-router";
+</script>
+
+<template>
+  <RouterView />
+</template>

+ 74 - 0
src/assets/base.css

@@ -0,0 +1,74 @@
+/* color palette from <https://github.com/vuejs/theme> */
+/* :root {
+  --vt-c-white: #ffffff;
+  --vt-c-white-soft: #f8f8f8;
+  --vt-c-white-mute: #f2f2f2;
+
+  --vt-c-black: #181818;
+  --vt-c-black-soft: #222222;
+  --vt-c-black-mute: #282828;
+
+  --vt-c-indigo: #2c3e50;
+
+  --vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
+  --vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
+  --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
+  --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
+
+  --vt-c-text-light-1: var(--vt-c-indigo);
+  --vt-c-text-light-2: rgba(60, 60, 60, 0.66);
+  --vt-c-text-dark-1: var(--vt-c-white);
+  --vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
+} */
+
+/* semantic color variables for this project */
+/* :root {
+  --color-background: var(--vt-c-white);
+  --color-background-soft: var(--vt-c-white-soft);
+  --color-background-mute: var(--vt-c-white-mute);
+
+  --color-border: var(--vt-c-divider-light-2);
+  --color-border-hover: var(--vt-c-divider-light-1);
+
+  --color-heading: var(--vt-c-text-light-1);
+  --color-text: var(--vt-c-text-light-1);
+
+  --section-gap: 160px;
+}
+
+@media (prefers-color-scheme: dark) {
+  :root {
+    --color-background: var(--vt-c-black);
+    --color-background-soft: var(--vt-c-black-soft);
+    --color-background-mute: var(--vt-c-black-mute);
+
+    --color-border: var(--vt-c-divider-dark-2);
+    --color-border-hover: var(--vt-c-divider-dark-1);
+
+    --color-heading: var(--vt-c-text-dark-1);
+    --color-text: var(--vt-c-text-dark-2);
+  }
+}
+
+*,
+*::before,
+*::after {
+  box-sizing: border-box;
+  margin: 0;
+  position: relative;
+  font-weight: normal;
+}
+
+body {
+  min-height: 100vh;
+  color: var(--color-text);
+  background: var(--color-background);
+  transition: color 0.5s, background-color 0.5s;
+  line-height: 1.6;
+  font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
+    Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
+  font-size: 15px;
+  text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+} */

BIN
src/assets/imgs/login-banner@1x.png


BIN
src/assets/imgs/login-banner@2x.png


+ 1 - 0
src/assets/logo.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"  xmlns:v="https://vecta.io/nano"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

+ 1 - 0
src/assets/main.css

@@ -0,0 +1 @@
+@import "./base.css";

+ 41 - 0
src/components/HelloWorld.vue

@@ -0,0 +1,41 @@
+<script setup lang="ts">
+defineProps<{
+  msg: string;
+}>();
+</script>
+
+<template>
+  <div class="greetings">
+    <h1 class="green">{{ msg }}</h1>
+    <h3>
+      You’ve successfully created a project with
+      <a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
+      <a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>.
+      What's next?
+    </h3>
+  </div>
+</template>
+
+<style scoped>
+h1 {
+  font-weight: 500;
+  font-size: 2.6rem;
+  top: -10px;
+}
+
+h3 {
+  font-size: 1.2rem;
+}
+
+.greetings h1,
+.greetings h3 {
+  text-align: center;
+}
+
+@media (min-width: 1024px) {
+  .greetings h1,
+  .greetings h3 {
+    text-align: left;
+  }
+}
+</style>

+ 53 - 0
src/components/NavMenu.vue

@@ -0,0 +1,53 @@
+<script setup lang="ts">
+import { FilePptOutlined, FileSearchOutlined, NodeIndexOutlined } from '@ant-design/icons-vue';
+import type { MenuProps } from 'ant-design-vue';
+import { ref, watch } from 'vue';
+
+const selectedKeys = ref<string[]>(['1']);
+const openKeys = ref<string[]>(['sub1']);
+const handleClick: MenuProps['onClick'] = e => {
+  console.log('click', e);
+};
+const titleClick = (e: Event) => {
+  console.log('titleClick', e);
+};
+watch(
+  () => openKeys,
+  val => {
+    console.log('openKeys', val);
+  },
+);
+</script>
+
+<template>
+  <a-menu
+    v-model:openKeys="openKeys"
+    v-model:selectedKeys="selectedKeys"
+    mode="inline"
+    @click="handleClick"
+  >
+    <a-sub-menu key="sub1" @titleClick="titleClick">
+      <template #icon>
+        <FilePptOutlined />
+      </template>
+      <template #title>报告管理</template>
+      <a-menu-item key="1">我的报告</a-menu-item>
+      <a-menu-item key="2">模板管理</a-menu-item>
+    </a-sub-menu>
+    <a-sub-menu key="sub2" @titleClick="titleClick">
+      <template #icon>
+        <FileSearchOutlined />
+      </template>
+      <template #title>搜索</template>
+      <a-menu-item key="5">智能搜索</a-menu-item>
+    </a-sub-menu>
+    <a-sub-menu key="sub4">
+      <template #icon>
+        <NodeIndexOutlined />
+      </template>
+      <template #title>知识导航</template>
+      <a-menu-item key="9">建筑工程科技导航</a-menu-item>
+    </a-sub-menu>
+  </a-menu>
+</template>
+

+ 121 - 0
src/components/TheWelcome.vue

@@ -0,0 +1,121 @@
+<script setup lang="ts">
+import WelcomeItem from "./WelcomeItem.vue";
+import DocumentationIcon from "./icons/IconDocumentation.vue";
+import ToolingIcon from "./icons/IconTooling.vue";
+import EcosystemIcon from "./icons/IconEcosystem.vue";
+import CommunityIcon from "./icons/IconCommunity.vue";
+import SupportIcon from "./icons/IconSupport.vue";
+</script>
+
+<template>
+  <WelcomeItem>
+    <template #icon>
+      <DocumentationIcon />
+    </template>
+    <template #heading>Documentation</template>
+
+    Vue’s
+    <a href="https://vuejs.org/" target="_blank" rel="noopener"
+      >official documentation</a
+    >
+    provides you with all information you need to get started.
+  </WelcomeItem>
+
+  <WelcomeItem>
+    <template #icon>
+      <ToolingIcon />
+    </template>
+    <template #heading>Tooling</template>
+
+    This project is served and bundled with
+    <a
+      href="https://vitejs.dev/guide/features.html"
+      target="_blank"
+      rel="noopener"
+      >Vite</a
+    >. The recommended IDE setup is
+    <a href="https://code.visualstudio.com/" target="_blank" rel="noopener"
+      >VSCode</a
+    >
+    +
+    <a
+      href="https://github.com/johnsoncodehk/volar"
+      target="_blank"
+      rel="noopener"
+      >Volar</a
+    >. If you need to test your components and web pages, check out
+    <a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a>
+    and
+    <a href="https://on.cypress.io/component" target="_blank"
+      >Cypress Component Testing</a
+    >.
+
+    <br />
+
+    More instructions are available in <code>README.md</code>.
+  </WelcomeItem>
+
+  <WelcomeItem>
+    <template #icon>
+      <EcosystemIcon />
+    </template>
+    <template #heading>Ecosystem</template>
+
+    Get official tools and libraries for your project:
+    <a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
+    <a href="https://router.vuejs.org/" target="_blank" rel="noopener"
+      >Vue Router</a
+    >,
+    <a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener"
+      >Vue Test Utils</a
+    >, and
+    <a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener"
+      >Vue Dev Tools</a
+    >. If you need more resources, we suggest paying
+    <a
+      href="https://github.com/vuejs/awesome-vue"
+      target="_blank"
+      rel="noopener"
+      >Awesome Vue</a
+    >
+    a visit.
+  </WelcomeItem>
+
+  <WelcomeItem>
+    <template #icon>
+      <CommunityIcon />
+    </template>
+    <template #heading>Community</template>
+
+    Got stuck? Ask your question on
+    <a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a
+    >, our official Discord server, or
+    <a
+      href="https://stackoverflow.com/questions/tagged/vue.js"
+      target="_blank"
+      rel="noopener"
+      >StackOverflow</a
+    >. You should also subscribe to
+    <a href="https://news.vuejs.org" target="_blank" rel="noopener"
+      >our mailing list</a
+    >
+    and follow the official
+    <a href="https://twitter.com/vuejs" target="_blank" rel="noopener"
+      >@vuejs</a
+    >
+    twitter account for latest news in the Vue world.
+  </WelcomeItem>
+
+  <WelcomeItem>
+    <template #icon>
+      <SupportIcon />
+    </template>
+    <template #heading>Support Vue</template>
+
+    As an independent project, Vue relies on community backing for its
+    sustainability. You can help us by
+    <a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener"
+      >becoming a sponsor</a
+    >.
+  </WelcomeItem>
+</template>

+ 86 - 0
src/components/WelcomeItem.vue

@@ -0,0 +1,86 @@
+<template>
+  <div class="item">
+    <i>
+      <slot name="icon"></slot>
+    </i>
+    <div class="details">
+      <h3>
+        <slot name="heading"></slot>
+      </h3>
+      <slot></slot>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.item {
+  margin-top: 2rem;
+  display: flex;
+}
+
+.details {
+  flex: 1;
+  margin-left: 1rem;
+}
+
+i {
+  display: flex;
+  place-items: center;
+  place-content: center;
+  width: 32px;
+  height: 32px;
+
+  color: var(--color-text);
+}
+
+h3 {
+  font-size: 1.2rem;
+  font-weight: 500;
+  margin-bottom: 0.4rem;
+  color: var(--color-heading);
+}
+
+@media (min-width: 1024px) {
+  .item {
+    margin-top: 0;
+    padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
+  }
+
+  i {
+    top: calc(50% - 25px);
+    left: -26px;
+    position: absolute;
+    border: 1px solid var(--color-border);
+    background: var(--color-background);
+    border-radius: 8px;
+    width: 50px;
+    height: 50px;
+  }
+
+  .item:before {
+    content: " ";
+    border-left: 1px solid var(--color-border);
+    position: absolute;
+    left: 0;
+    bottom: calc(50% + 25px);
+    height: calc(50% - 25px);
+  }
+
+  .item:after {
+    content: " ";
+    border-left: 1px solid var(--color-border);
+    position: absolute;
+    left: 0;
+    top: calc(50% + 25px);
+    height: calc(50% - 25px);
+  }
+
+  .item:first-of-type:before {
+    display: none;
+  }
+
+  .item:last-of-type:after {
+    display: none;
+  }
+}
+</style>

+ 11 - 0
src/components/__tests__/HelloWorld.spec.ts

@@ -0,0 +1,11 @@
+import { describe, it, expect } from "vitest";
+
+import { mount } from "@vue/test-utils";
+import HelloWorld from "../HelloWorld.vue";
+
+describe("HelloWorld", () => {
+  it("renders properly", () => {
+    const wrapper = mount(HelloWorld, { props: { msg: "Hello Vitest" } });
+    expect(wrapper.text()).toContain("Hello Vitest");
+  });
+});

Разница между файлами не показана из-за своего большого размера
+ 12 - 0
src/components/icons/IconCommunity.vue


Разница между файлами не показана из-за своего большого размера
+ 12 - 0
src/components/icons/IconDocumentation.vue


Разница между файлами не показана из-за своего большого размера
+ 12 - 0
src/components/icons/IconEcosystem.vue


+ 12 - 0
src/components/icons/IconSupport.vue

@@ -0,0 +1,12 @@
+<template>
+  <svg
+    xmlns="http://www.w3.org/2000/svg"
+    width="20"
+    height="20"
+    fill="currentColor"
+  >
+    <path
+      d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
+    />
+  </svg>
+</template>

+ 19 - 0
src/components/icons/IconTooling.vue

@@ -0,0 +1,19 @@
+<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
+<template>
+  <svg
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+    aria-hidden="true"
+    role="img"
+    class="iconify iconify--mdi"
+    width="24"
+    height="24"
+    preserveAspectRatio="xMidYMid meet"
+    viewBox="0 0 24 24"
+  >
+    <path
+      d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
+      fill="currentColor"
+    ></path>
+  </svg>
+</template>

+ 18 - 0
src/main.ts

@@ -0,0 +1,18 @@
+import { createApp } from "vue";
+import { createPinia } from "pinia";
+import Antd from "ant-design-vue";
+
+import App from "./App.vue";
+import router from "./router";
+
+import "ant-design-vue/dist/antd.less";
+
+import "./assets/main.css";
+
+const app = createApp(App);
+
+app.use(Antd);
+app.use(createPinia());
+app.use(router);
+
+app.mount("#app");

+ 23 - 0
src/router/index.ts

@@ -0,0 +1,23 @@
+import { createRouter, createWebHistory } from "vue-router";
+import HomeView from "../views/HomeView.vue";
+
+const router = createRouter({
+  history: createWebHistory(import.meta.env.BASE_URL),
+  routes: [
+    {
+      path: "/",
+      name: "home",
+      component: HomeView,
+    },
+    {
+      path: "/login",
+      name: "login",
+      // route level code-splitting
+      // this generates a separate chunk (About.[hash].js) for this route
+      // which is lazy-loaded when the route is visited.
+      component: () => import("../views/LoginView.vue"),
+    },
+  ],
+});
+
+export default router;

+ 12 - 0
src/stores/counter.ts

@@ -0,0 +1,12 @@
+import { ref, computed } from "vue";
+import { defineStore } from "pinia";
+
+export const useCounterStore = defineStore("counter", () => {
+  const count = ref(0);
+  const doubleCount = computed(() => count.value * 2);
+  function increment() {
+    count.value++;
+  }
+
+  return { count, doubleCount, increment };
+});

+ 42 - 0
src/views/HomeView.vue

@@ -0,0 +1,42 @@
+<script setup lang="ts">
+import { ref } from "vue";
+import NavMenu from "@/components/NavMenu.vue";
+
+const collapsed = ref(false);
+</script>
+
+<template>
+  <a-layout>
+    <a-layout-sider
+      class="sider-nav-bar"
+      width="260"
+      theme="light"
+      v-model:collapsed="collapsed"
+      collapsible
+    >
+      <div class="logo">Logo & Brand</div>
+      <NavMenu />
+    </a-layout-sider>
+    <a-layout>
+      <a-layout-header theme="light">Header</a-layout-header>
+      <a-layout-content>Content</a-layout-content>
+      <a-layout-footer>Footer</a-layout-footer>
+    </a-layout>
+  </a-layout>
+</template>
+
+<style scoped lang="scss">
+.logo {
+  height: 32px;
+  background: rgba(128, 128, 128, 0.5);
+  margin: 16px;
+}
+
+.ant-layout-header {
+  background: #ffffff;
+}
+
+.sider-nav-bar {
+  height: 100vh;
+}
+</style>

+ 124 - 0
src/views/LoginView.vue

@@ -0,0 +1,124 @@
+<script setup lang="ts">import { reactive } from 'vue';
+
+interface FormState {
+  username: string;
+  password: string;
+  remember: boolean;
+}
+
+const formState = reactive<FormState>({
+  username: '',
+  password: '',
+  remember: true,
+});
+const onFinish = (values: any) => {
+  console.log('Success:', values);
+};
+
+const onFinishFailed = (errorInfo: any) => {
+  console.log('Failed:', errorInfo);
+};
+
+</script>
+
+<template>
+  <div class="login-main">
+    <a-row type="flex">
+      <a-col class="left" :flex="1">
+        <h1 class="login-title">技淘平台智慧产权大脑</h1>
+        <p class="login-banner">
+          <img src="@/assets/imgs/login-banner@2x.png" />
+        </p>
+      </a-col>
+      <a-col class="right" :flex="1">
+        <h1>用户登录</h1>
+        <a-divider></a-divider>
+        <div class="login-form-wrapper">
+          <a-form
+            :model="formState"
+            name="basic"
+            :label-col="{ span: 4 }"
+            :wrapper-col="{ span: 20 }"
+            autocomplete="off"
+            @finish="onFinish"
+            @finishFailed="onFinishFailed"
+          >
+            <a-form-item
+              label="用户名"
+              name="username"
+              :rules="[{ required: true, message: '请输入用户名' }]"
+            >
+              <a-input v-model:value="formState.username" />
+            </a-form-item>
+
+            <a-form-item
+              label="密码"
+              name="password"
+              :rules="[{ required: true, message: '请输入密码' }]"
+            >
+              <a-input-password v-model:value="formState.password" />
+            </a-form-item>
+
+            <a-form-item :wrapper-col="{sm: {span: 20, offset: 4}, xs: {span: 24, offset: 0}}">
+              <a-button type="primary" html-type="submit" block>登&nbsp;&nbsp;&nbsp;&nbsp;录</a-button>
+            </a-form-item>
+          </a-form>
+          <p class="forgot-password-wrap">
+            <a href="#">忘记密码?</a>
+          </p>
+        </div>
+      </a-col>
+    </a-row>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.login-main {
+  height: 100vh;
+  background-color: #00A09A;
+
+  .left {
+    text-align: center;
+    padding-top: 100px;
+
+    .login-title {
+      color: #ffffff;
+    }
+
+    .login-banner {
+      margin: 0 auto;
+      width: 300px;
+
+      img {
+        margin-top: 50px;
+        width: 100%;
+      }
+    }
+  }
+
+  .right {
+    height: 100vh;
+    padding-top: 100px;
+    padding-left: 60px;
+    border-top-left-radius: 1em;
+    border-bottom-left-radius: 1em;
+    background-color: #ffffff;
+
+    .login-form-wrapper {
+      width: 400px;
+    }
+  }
+}
+
+.forgot-password-wrap {
+  text-align: right;
+
+  // a {
+  //   color: #ffffff;
+
+  //   &:hover {
+  //     text-decoration: underline;
+  //   }
+  // }
+}
+</style>

+ 12 - 0
tsconfig.app.json

@@ -0,0 +1,12 @@
+{
+  "extends": "@vue/tsconfig/tsconfig.web.json",
+  "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
+  "exclude": ["src/**/__tests__/*"],
+  "compilerOptions": {
+    "composite": true,
+    "baseUrl": ".",
+    "paths": {
+      "@/*": ["./src/*"]
+    }
+  }
+}

+ 8 - 0
tsconfig.config.json

@@ -0,0 +1,8 @@
+{
+  "extends": "@vue/tsconfig/tsconfig.node.json",
+  "include": ["vite.config.*", "vitest.config.*", "cypress.config.*"],
+  "compilerOptions": {
+    "composite": true,
+    "types": ["node"]
+  }
+}

+ 14 - 0
tsconfig.json

@@ -0,0 +1,14 @@
+{
+  "files": [],
+  "references": [
+    {
+      "path": "./tsconfig.config.json"
+    },
+    {
+      "path": "./tsconfig.app.json"
+    },
+    {
+      "path": "./tsconfig.vitest.json"
+    }
+  ]
+}

+ 9 - 0
tsconfig.vitest.json

@@ -0,0 +1,9 @@
+{
+  "extends": "./tsconfig.app.json",
+  "exclude": [],
+  "compilerOptions": {
+    "composite": true,
+    "lib": [],
+    "types": ["node", "jsdom"]
+  }
+}

+ 26 - 0
vite.config.ts

@@ -0,0 +1,26 @@
+import { fileURLToPath, URL } from "node:url";
+
+import { defineConfig } from "vite";
+import vue from "@vitejs/plugin-vue";
+import vueJsx from "@vitejs/plugin-vue-jsx";
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  plugins: [vue(), vueJsx()],
+  resolve: {
+    alias: {
+      "@": fileURLToPath(new URL("./src", import.meta.url)),
+    },
+  },
+  css: {
+    preprocessorOptions: {
+      less: {
+        modifyVars: {
+          // "primary-color": "#1DA57A",
+          // "link-color": "#1DA57A",
+        },
+        javascriptEnabled: true,
+      },
+    },
+  },
+});