first commit
This commit is contained in:
commit
23dc8df4a9
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
||||
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
|
||||
charset = utf-8
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
end_of_line = lf
|
||||
max_line_length = 100
|
||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
# 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?
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
test-results/
|
||||
playwright-report/
|
||||
6
.prettierrc.json
Normal file
6
.prettierrc.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100
|
||||
}
|
||||
10
.vscode/extensions.json
vendored
Normal file
10
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"Vue.volar",
|
||||
"vitest.explorer",
|
||||
"ms-playwright.playwright",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"EditorConfig.EditorConfig",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
||||
64
README.md
Normal file
64
README.md
Normal file
@ -0,0 +1,64 @@
|
||||
# .
|
||||
|
||||
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).
|
||||
|
||||
## 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 [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vite.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 [Playwright](https://playwright.dev)
|
||||
|
||||
```sh
|
||||
# Install browsers for the first run
|
||||
npx playwright install
|
||||
|
||||
# When testing on CI, must build the project first
|
||||
npm run build
|
||||
|
||||
# Runs the end-to-end tests
|
||||
npm run test:e2e
|
||||
# Runs the tests only on Chromium
|
||||
npm run test:e2e -- --project=chromium
|
||||
# Runs the tests of a specific file
|
||||
npm run test:e2e -- tests/example.spec.ts
|
||||
# Runs the tests in debug mode
|
||||
npm run test:e2e -- --debug
|
||||
```
|
||||
|
||||
### Lint with [ESLint](https://eslint.org/)
|
||||
|
||||
```sh
|
||||
npm run lint
|
||||
```
|
||||
4
e2e/tsconfig.json
Normal file
4
e2e/tsconfig.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "@tsconfig/node22/tsconfig.json",
|
||||
"include": ["./**/*"]
|
||||
}
|
||||
8
e2e/vue.spec.ts
Normal file
8
e2e/vue.spec.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
// See here how to get started:
|
||||
// https://playwright.dev/docs/intro
|
||||
test('visits the app root url', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page.locator('h1')).toHaveText('You did it!');
|
||||
})
|
||||
34
eslint.config.ts
Normal file
34
eslint.config.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { globalIgnores } from 'eslint/config'
|
||||
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
import pluginVitest from '@vitest/eslint-plugin'
|
||||
import pluginPlaywright from 'eslint-plugin-playwright'
|
||||
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
|
||||
|
||||
// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
|
||||
// import { configureVueProject } from '@vue/eslint-config-typescript'
|
||||
// configureVueProject({ scriptLangs: ['ts', 'tsx'] })
|
||||
// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup
|
||||
|
||||
export default defineConfigWithVueTs(
|
||||
{
|
||||
name: 'app/files-to-lint',
|
||||
files: ['**/*.{ts,mts,tsx,vue}'],
|
||||
},
|
||||
|
||||
globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
|
||||
|
||||
pluginVue.configs['flat/essential'],
|
||||
vueTsConfigs.recommended,
|
||||
|
||||
{
|
||||
...pluginVitest.configs.recommended,
|
||||
files: ['src/**/__tests__/*'],
|
||||
},
|
||||
|
||||
{
|
||||
...pluginPlaywright.configs['flat/recommended'],
|
||||
files: ['e2e/**/*.{test,spec}.{js,ts,jsx,tsx}'],
|
||||
},
|
||||
skipFormatting,
|
||||
)
|
||||
30
index.html
Normal file
30
index.html
Normal file
@ -0,0 +1,30 @@
|
||||
<!doctype html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Crypto AI - 加密货币工具与社区平台</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
body {
|
||||
background-color: #0f1318;
|
||||
color: #ffffff;
|
||||
}
|
||||
#app {
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
47
package.json
Normal file
47
package.json
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "icrypto",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"test:unit": "vitest",
|
||||
"test:e2e": "playwright test",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build",
|
||||
"lint": "eslint . --fix",
|
||||
"format": "prettier --write src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"pinia": "^3.0.1",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.51.1",
|
||||
"@tsconfig/node22": "^22.0.1",
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/node": "^22.14.0",
|
||||
"@vitejs/plugin-vue": "^5.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "^4.1.2",
|
||||
"@vitest/eslint-plugin": "^1.1.39",
|
||||
"@vue/eslint-config-prettier": "^10.2.0",
|
||||
"@vue/eslint-config-typescript": "^14.5.0",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"eslint": "^9.22.0",
|
||||
"eslint-plugin-playwright": "^2.2.0",
|
||||
"eslint-plugin-vue": "~10.0.0",
|
||||
"jiti": "^2.4.2",
|
||||
"jsdom": "^26.0.0",
|
||||
"npm-run-all2": "^7.0.2",
|
||||
"prettier": "3.5.3",
|
||||
"typescript": "~5.8.0",
|
||||
"vite": "^6.2.4",
|
||||
"vite-plugin-vue-devtools": "^7.7.2",
|
||||
"vitest": "^3.1.1",
|
||||
"vue-tsc": "^2.2.8"
|
||||
}
|
||||
}
|
||||
110
playwright.config.ts
Normal file
110
playwright.config.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import process from 'node:process'
|
||||
import { defineConfig, devices } from '@playwright/test'
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: './e2e',
|
||||
/* Maximum time one test can run for. */
|
||||
timeout: 30 * 1000,
|
||||
expect: {
|
||||
/**
|
||||
* Maximum time expect() should wait for the condition to be met.
|
||||
* For example in `await expect(locator).toHaveText();`
|
||||
*/
|
||||
timeout: 5000,
|
||||
},
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: 'html',
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
|
||||
actionTimeout: 0,
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: process.env.CI ? 'http://localhost:4173' : 'http://localhost:5173',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
|
||||
/* Only on CI systems run the tests headless */
|
||||
headless: !!process.env.CI,
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'firefox',
|
||||
use: {
|
||||
...devices['Desktop Firefox'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'webkit',
|
||||
use: {
|
||||
...devices['Desktop Safari'],
|
||||
},
|
||||
},
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: {
|
||||
// ...devices['Pixel 5'],
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: {
|
||||
// ...devices['iPhone 12'],
|
||||
// },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: {
|
||||
// channel: 'msedge',
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: {
|
||||
// channel: 'chrome',
|
||||
// },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
|
||||
// outputDir: 'test-results/',
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: {
|
||||
/**
|
||||
* Use the dev server by default for faster feedback loop.
|
||||
* Use the preview server on CI for more realistic testing.
|
||||
* Playwright will re-use the local server if there is already a dev-server running.
|
||||
*/
|
||||
command: process.env.CI ? 'npm run preview' : 'npm run dev',
|
||||
port: process.env.CI ? 4173 : 5173,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
})
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
342
src/App.vue
Normal file
342
src/App.vue
Normal file
@ -0,0 +1,342 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterLink, RouterView } from 'vue-router'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<header class="app-header">
|
||||
<div class="header-content">
|
||||
<div class="logo">Crypto.AI</div>
|
||||
<nav class="main-nav">
|
||||
<RouterLink to="/" class="nav-link">首页</RouterLink>
|
||||
<RouterLink to="/tools" class="nav-link">工具集合</RouterLink>
|
||||
<RouterLink to="/ai-agent" class="nav-link">AI Agent</RouterLink>
|
||||
<RouterLink to="/community" class="nav-link">社区</RouterLink>
|
||||
</nav>
|
||||
<div class="user-actions">
|
||||
<button class="btn-connect">连接钱包</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="app-content">
|
||||
<div class="content-container">
|
||||
<RouterView />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="app-footer">
|
||||
<div class="footer-content">
|
||||
<p>© 2024 Crypto.AI - 加密货币工具与社区平台</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--color-bg-primary: #000000;
|
||||
--color-bg-secondary: #111111;
|
||||
--color-bg-elevated: #1a1a1a;
|
||||
--color-bg-card: #0a0a0a;
|
||||
|
||||
--color-text-primary: rgba(255, 255, 255, 1);
|
||||
--color-text-secondary: rgba(255, 255, 255, 0.7);
|
||||
--color-text-tertiary: rgba(255, 255, 255, 0.5);
|
||||
--color-text-disabled: rgba(255, 255, 255, 0.3);
|
||||
|
||||
--color-divider: rgba(255, 255, 255, 0.1);
|
||||
--color-border: rgba(255, 255, 255, 0.15);
|
||||
--color-border-hover: rgba(255, 255, 255, 0.25);
|
||||
|
||||
--color-accent: rgba(255, 255, 255, 0.9);
|
||||
--color-accent-hover: rgba(255, 255, 255, 1);
|
||||
--color-accent-light: rgba(255, 255, 255, 0.1);
|
||||
|
||||
--font-weight-light: 300;
|
||||
--font-weight-regular: 400;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-bold: 700;
|
||||
|
||||
--header-height: 70px;
|
||||
--border-radius: 8px;
|
||||
--max-content-width: 1280px;
|
||||
--content-padding: 1rem;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', 'Helvetica Neue', Arial, sans-serif;
|
||||
background-color: var(--color-bg-primary);
|
||||
color: var(--color-text-primary);
|
||||
line-height: 1.6;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
font-weight: var(--font-weight-regular);
|
||||
}
|
||||
|
||||
.app-container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-x: hidden;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.app-header {
|
||||
background-color: var(--color-bg-secondary);
|
||||
box-shadow: 0 1px 0 var(--color-divider);
|
||||
height: var(--header-height);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
backdrop-filter: blur(8px);
|
||||
width: 100vw;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
left: 0;
|
||||
right: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
width: 100%;
|
||||
max-width: var(--max-content-width);
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--content-padding);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 1.8rem;
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.main-nav {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: var(--color-text-secondary);
|
||||
text-decoration: none;
|
||||
font-weight: var(--font-weight-medium);
|
||||
transition: all 0.2s ease;
|
||||
padding: 0 0.5rem;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.router-link-active {
|
||||
color: var(--color-text-primary);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.router-link-active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: var(--color-accent);
|
||||
}
|
||||
|
||||
.user-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn-connect {
|
||||
background-color: var(--color-bg-elevated);
|
||||
color: var(--color-text-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
padding: 0.7rem 1.4rem;
|
||||
border-radius: var(--border-radius);
|
||||
font-weight: var(--font-weight-medium);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-connect:hover {
|
||||
background-color: var(--color-bg-secondary);
|
||||
border-color: var(--color-border-hover);
|
||||
}
|
||||
|
||||
.app-content {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
width: 100%;
|
||||
max-width: var(--max-content-width);
|
||||
padding: 0 var(--content-padding);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.app-footer {
|
||||
background-color: var(--color-bg-secondary);
|
||||
border-top: 1px solid var(--color-divider);
|
||||
font-size: 0.9rem;
|
||||
width: 100vw;
|
||||
margin: 0;
|
||||
padding: 1.5rem 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
left: 0;
|
||||
right: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
width: 100%;
|
||||
max-width: var(--max-content-width);
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--content-padding);
|
||||
text-align: center;
|
||||
color: var(--color-text-tertiary);
|
||||
}
|
||||
|
||||
/* 全局按钮样式 */
|
||||
.btn {
|
||||
padding: 0.8rem 1.6rem;
|
||||
border-radius: var(--border-radius);
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--color-bg-elevated);
|
||||
color: var(--color-text-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--color-bg-secondary);
|
||||
border-color: var(--color-border-hover);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: transparent;
|
||||
color: var(--color-text-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: var(--color-bg-elevated);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
/* 全局卡片样式 */
|
||||
.card {
|
||||
background-color: var(--color-bg-card);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 1.5rem;
|
||||
border: 1px solid var(--color-border);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
border-color: var(--color-border-hover);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 1280px) {
|
||||
:root {
|
||||
--content-padding: 2rem;
|
||||
}
|
||||
|
||||
.content-container,
|
||||
.header-content,
|
||||
.footer-content {
|
||||
width: 100%;
|
||||
max-width: var(--max-content-width);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
:root {
|
||||
--content-padding: 1rem;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
padding: 1rem var(--content-padding);
|
||||
}
|
||||
|
||||
.app-header {
|
||||
height: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.main-nav {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
margin: 1rem 0;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
padding: 0.75rem 0;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.user-actions {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.app-content {
|
||||
padding: 1rem 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
98
src/assets/base.css
Normal file
98
src/assets/base.css
Normal file
@ -0,0 +1,98 @@
|
||||
/* color palette from <https://github.com/vuejs/theme> */
|
||||
:root {
|
||||
--vt-c-white: #ffffff;
|
||||
--vt-c-white-soft: rgba(255, 255, 255, 0.8);
|
||||
--vt-c-white-mute: rgba(255, 255, 255, 0.5);
|
||||
|
||||
--vt-c-black: #000000;
|
||||
--vt-c-black-soft: #0a0a0a;
|
||||
--vt-c-black-mute: #111111;
|
||||
|
||||
--vt-c-divider-light-1: rgba(255, 255, 255, 0.15);
|
||||
--vt-c-divider-light-2: rgba(255, 255, 255, 0.1);
|
||||
--vt-c-divider-dark-1: rgba(255, 255, 255, 0.15);
|
||||
--vt-c-divider-dark-2: rgba(255, 255, 255, 0.1);
|
||||
|
||||
--vt-c-text-light-1: var(--vt-c-white);
|
||||
--vt-c-text-light-2: var(--vt-c-white-soft);
|
||||
--vt-c-text-dark-1: var(--vt-c-white);
|
||||
--vt-c-text-dark-2: var(--vt-c-white-soft);
|
||||
}
|
||||
|
||||
/* semantic color variables for this project */
|
||||
: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-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;
|
||||
padding: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
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;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
1
src/assets/logo.svg
Normal file
1
src/assets/logo.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><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>
|
||||
|
After Width: | Height: | Size: 276 B |
37
src/assets/main.css
Normal file
37
src/assets/main.css
Normal file
@ -0,0 +1,37 @@
|
||||
@import './base.css';
|
||||
|
||||
#app {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: normal;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
41
src/components/HelloWorld.vue
Normal file
41
src/components/HelloWorld.vue
Normal file
@ -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://vite.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;
|
||||
position: relative;
|
||||
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>
|
||||
94
src/components/TheWelcome.vue
Normal file
94
src/components/TheWelcome.vue
Normal file
@ -0,0 +1,94 @@
|
||||
<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'
|
||||
|
||||
const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md')
|
||||
</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://vite.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/vuejs/language-tools" target="_blank" rel="noopener">Vue - Official</a>. If
|
||||
you need to test your components and web pages, check out
|
||||
<a href="https://vitest.dev/" target="_blank" rel="noopener">Vitest</a>
|
||||
and
|
||||
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a>
|
||||
/
|
||||
<a href="https://playwright.dev/" target="_blank" rel="noopener">Playwright</a>.
|
||||
|
||||
<br />
|
||||
|
||||
More instructions are available in
|
||||
<a href="javascript:void(0)" @click="openReadmeInEditor"><code>README.md</code></a
|
||||
>.
|
||||
</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 follow the official
|
||||
<a href="https://bsky.app/profile/vuejs.org" target="_blank" rel="noopener">@vuejs.org</a>
|
||||
Bluesky account or the
|
||||
<a href="https://x.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
|
||||
X 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>
|
||||
87
src/components/WelcomeItem.vue
Normal file
87
src/components/WelcomeItem.vue
Normal file
@ -0,0 +1,87 @@
|
||||
<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;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.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
src/components/__tests__/HelloWorld.spec.ts
Normal file
11
src/components/__tests__/HelloWorld.spec.ts
Normal file
@ -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')
|
||||
})
|
||||
})
|
||||
7
src/components/icons/IconCommunity.vue
Normal file
7
src/components/icons/IconCommunity.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
||||
<path
|
||||
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
7
src/components/icons/IconDocumentation.vue
Normal file
7
src/components/icons/IconDocumentation.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
|
||||
<path
|
||||
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
7
src/components/icons/IconEcosystem.vue
Normal file
7
src/components/icons/IconEcosystem.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
|
||||
<path
|
||||
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
7
src/components/icons/IconSupport.vue
Normal file
7
src/components/icons/IconSupport.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<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
src/components/icons/IconTooling.vue
Normal file
19
src/components/icons/IconTooling.vue
Normal file
@ -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>
|
||||
14
src/main.ts
Normal file
14
src/main.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import './assets/main.css'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
|
||||
app.mount('#app')
|
||||
30
src/router/index.ts
Normal file
30
src/router/index.ts
Normal file
@ -0,0 +1,30 @@
|
||||
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: '/tools',
|
||||
name: 'tools',
|
||||
component: () => import('../views/ToolsView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/ai-agent',
|
||||
name: 'ai-agent',
|
||||
component: () => import('../views/AIAgentView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/community',
|
||||
name: 'community',
|
||||
component: () => import('../views/CommunityView.vue'),
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export default router
|
||||
12
src/stores/counter.ts
Normal file
12
src/stores/counter.ts
Normal file
@ -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 }
|
||||
})
|
||||
410
src/views/AIAgentView.vue
Normal file
410
src/views/AIAgentView.vue
Normal file
@ -0,0 +1,410 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const activeAgent = ref('crypto-doctor')
|
||||
const userInput = ref('')
|
||||
const chatHistory = ref([
|
||||
{
|
||||
role: 'assistant',
|
||||
content:
|
||||
'你好!我是加密项目医生,可以帮你分析任何加密项目的健康状况。请告诉我你想了解的项目名称或合约地址。',
|
||||
},
|
||||
])
|
||||
|
||||
const sendMessage = () => {
|
||||
if (!userInput.value.trim()) return
|
||||
|
||||
// 添加用户消息到历史记录
|
||||
chatHistory.value.push({
|
||||
role: 'user',
|
||||
content: userInput.value,
|
||||
})
|
||||
|
||||
// 模拟AI回复(实际项目中这里应该调用后端API)
|
||||
setTimeout(() => {
|
||||
if (activeAgent.value === 'crypto-doctor') {
|
||||
chatHistory.value.push({
|
||||
role: 'assistant',
|
||||
content:
|
||||
'我正在分析这个项目,这可能需要一些时间。基于初步分析,我建议关注以下几个方面:\n\n1. 团队背景和透明度\n2. 代码审计情况\n3. 社区活跃度和增长\n4. 代币分配机制\n5. 市场流动性\n\n您想了解哪方面的详细信息?',
|
||||
})
|
||||
} else {
|
||||
chatHistory.value.push({
|
||||
role: 'assistant',
|
||||
content:
|
||||
'根据最近的市场数据和技术指标分析,我注意到该代币的趋势如下:\n\n- 价格走势:呈现上升通道,但接近阻力位\n- 交易量:逐渐增加,表明兴趣上升\n- RSI指标:当前处于60左右,未进入超买区域\n- MACD:短期均线刚刚穿过长期均线,形成黄金交叉\n\n需要注意的是,市场可能受到宏观经济因素的影响。您希望了解更多具体的技术指标分析吗?',
|
||||
})
|
||||
}
|
||||
userInput.value = ''
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const switchAgent = (agent: string) => {
|
||||
activeAgent.value = agent
|
||||
chatHistory.value = [
|
||||
{
|
||||
role: 'assistant',
|
||||
content:
|
||||
agent === 'crypto-doctor'
|
||||
? '你好!我是加密项目医生,可以帮你分析任何加密项目的健康状况。请告诉我你想了解的项目名称或合约地址。'
|
||||
: '你好!我是币种技术分析师,可以为你提供币种的技术面分析和趋势预测。请告诉我你想分析的币种名称或代号。',
|
||||
},
|
||||
]
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="ai-agent-view">
|
||||
<h1 class="page-title">AI Agent</h1>
|
||||
<p class="page-description">智能AI助手,为您提供加密项目分析和技术指标解读</p>
|
||||
|
||||
<div class="agent-container">
|
||||
<div class="agent-sidebar">
|
||||
<div class="agent-selector">
|
||||
<button
|
||||
class="agent-option"
|
||||
:class="{ active: activeAgent === 'crypto-doctor' }"
|
||||
@click="switchAgent('crypto-doctor')"
|
||||
>
|
||||
<span class="agent-icon">🔍</span>
|
||||
<div class="agent-info">
|
||||
<h3>加密项目医生</h3>
|
||||
<p>项目风险分析与评估</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="agent-option"
|
||||
:class="{ active: activeAgent === 'technical-analysis' }"
|
||||
@click="switchAgent('technical-analysis')"
|
||||
>
|
||||
<span class="agent-icon">📊</span>
|
||||
<div class="agent-info">
|
||||
<h3>币种技术分析</h3>
|
||||
<p>技术指标与趋势预测</p>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-info">
|
||||
<h3>使用提示</h3>
|
||||
<ul class="tips-list">
|
||||
<li>提供详细的项目信息以获得更准确的分析</li>
|
||||
<li>可以直接输入合约地址进行分析</li>
|
||||
<li>分析结果仅供参考,不构成投资建议</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat-container">
|
||||
<div class="chat-header">
|
||||
<h2>{{ activeAgent === 'crypto-doctor' ? '加密项目医生' : '币种技术分析' }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="chat-messages">
|
||||
<div
|
||||
v-for="(message, index) in chatHistory"
|
||||
:key="index"
|
||||
class="message"
|
||||
:class="{
|
||||
'user-message': message.role === 'user',
|
||||
'ai-message': message.role === 'assistant',
|
||||
}"
|
||||
>
|
||||
<div class="message-content">
|
||||
<p v-for="(line, i) in message.content.split('\n')" :key="i">
|
||||
{{ line }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat-input">
|
||||
<input
|
||||
type="text"
|
||||
v-model="userInput"
|
||||
@keyup.enter="sendMessage"
|
||||
placeholder="输入项目名称、合约地址或问题..."
|
||||
class="input-field"
|
||||
/>
|
||||
<button class="send-button" @click="sendMessage">发送</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.ai-agent-view {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 2.2rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.page-description {
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.agent-container {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
height: calc(80vh - 100px);
|
||||
min-height: 500px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.agent-sidebar {
|
||||
width: 300px;
|
||||
background-color: var(--color-bg-card);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.agent-selector {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.agent-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.8rem;
|
||||
padding: 1rem;
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--color-bg-primary);
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.agent-option:hover {
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
.agent-option.active {
|
||||
border-color: var(--color-accent);
|
||||
background-color: var(--color-accent-light);
|
||||
}
|
||||
|
||||
.agent-icon {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.agent-info h3 {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.2rem;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.agent-info p {
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.agent-option.active .agent-info h3 {
|
||||
color: var(--color-accent-hover);
|
||||
}
|
||||
|
||||
.sidebar-info {
|
||||
margin-top: auto;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.sidebar-info h3 {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
.tips-list {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tips-list li {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.5rem;
|
||||
padding-left: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tips-list li::before {
|
||||
content: '•';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--color-bg-card);
|
||||
border-radius: var(--border-radius);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
padding: 1rem 1.5rem;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.chat-header h2 {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.message {
|
||||
max-width: 80%;
|
||||
padding: 1rem;
|
||||
border-radius: var(--border-radius);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.user-message {
|
||||
align-self: flex-end;
|
||||
background-color: var(--color-accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.ai-message {
|
||||
align-self: flex-start;
|
||||
background-color: var(--color-bg-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.message-content p {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.message-content p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
display: flex;
|
||||
padding: 1rem 1.5rem;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.input-field {
|
||||
flex: 1;
|
||||
padding: 0.8rem 1rem;
|
||||
border-radius: var(--border-radius);
|
||||
border: 1px solid var(--color-border);
|
||||
background-color: var(--color-bg-primary);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.input-field:focus {
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: 0 0 0 2px var(--color-accent-light);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.send-button {
|
||||
background-color: var(--color-accent);
|
||||
color: var(--color-bg-primary);
|
||||
border: none;
|
||||
border-radius: var(--border-radius);
|
||||
padding: 0 1.2rem;
|
||||
margin-left: 0.8rem;
|
||||
font-weight: var(--font-weight-bold);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.send-button:hover {
|
||||
background-color: var(--color-accent-hover);
|
||||
}
|
||||
|
||||
.btn-connect,
|
||||
.btn-primary,
|
||||
.btn-secondary {
|
||||
color: var(--color-bg-primary);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.agent-container {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.agent-sidebar {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
flex-basis: calc(100% - 270px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.agent-container {
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.agent-sidebar {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.agent-selector {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.agent-option {
|
||||
flex: 1;
|
||||
min-width: 140px;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.agent-info h3 {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.agent-info p {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar-info {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
height: 70vh;
|
||||
flex-basis: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
15
src/views/AboutView.vue
Normal file
15
src/views/AboutView.vue
Normal file
@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div class="about">
|
||||
<h1>This is an about page</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@media (min-width: 1024px) {
|
||||
.about {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
716
src/views/CommunityView.vue
Normal file
716
src/views/CommunityView.vue
Normal file
@ -0,0 +1,716 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
interface Post {
|
||||
id: number
|
||||
author: {
|
||||
name: string
|
||||
avatar: string
|
||||
verified: boolean
|
||||
}
|
||||
content: string
|
||||
timestamp: string
|
||||
likes: number
|
||||
comments: number
|
||||
reposts: number
|
||||
isLiked: boolean
|
||||
images?: string[]
|
||||
tags?: string[]
|
||||
}
|
||||
|
||||
const newPostContent = ref('')
|
||||
const filter = ref('for-you')
|
||||
|
||||
const posts = ref<Post[]>([
|
||||
{
|
||||
id: 1,
|
||||
author: {
|
||||
name: 'CryptoInsight',
|
||||
avatar: 'https://api.dicebear.com/7.x/shapes/svg?seed=CryptoInsight',
|
||||
verified: true,
|
||||
},
|
||||
content:
|
||||
'比特币减半已经完成!这对市场来说是一个重要的里程碑。你们认为这会对价格产生什么影响? #比特币 #减半 #加密货币',
|
||||
timestamp: '1小时前',
|
||||
likes: 128,
|
||||
comments: 43,
|
||||
reposts: 21,
|
||||
isLiked: false,
|
||||
tags: ['比特币', '减半', '加密货币'],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
author: {
|
||||
name: 'ETH_Developer',
|
||||
avatar: 'https://api.dicebear.com/7.x/shapes/svg?seed=ETH_Developer',
|
||||
verified: true,
|
||||
},
|
||||
content: '以太坊过去24小时的Gas费用达到了近期最低点。现在是进行链上交易的好时机!',
|
||||
timestamp: '3小时前',
|
||||
likes: 76,
|
||||
comments: 12,
|
||||
reposts: 8,
|
||||
isLiked: true,
|
||||
tags: ['以太坊', 'Gas费用'],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
author: {
|
||||
name: 'DeFi_Master',
|
||||
avatar: 'https://api.dicebear.com/7.x/shapes/svg?seed=DeFi_Master',
|
||||
verified: false,
|
||||
},
|
||||
content:
|
||||
'刚刚研究了一个新的DeFi项目,提供了非常具有竞争力的流动性挖矿回报。大家有兴趣了解更多吗?',
|
||||
timestamp: '5小时前',
|
||||
likes: 54,
|
||||
comments: 31,
|
||||
reposts: 4,
|
||||
isLiked: false,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
author: {
|
||||
name: 'NFT_Collector',
|
||||
avatar: 'https://api.dicebear.com/7.x/shapes/svg?seed=NFT_Collector',
|
||||
verified: true,
|
||||
},
|
||||
content:
|
||||
'刚刚收购了一个稀有的蓝筹NFT!这是我收藏的最新成员。艺术和区块链的结合真的很令人兴奋!',
|
||||
timestamp: '6小时前',
|
||||
likes: 92,
|
||||
comments: 14,
|
||||
reposts: 7,
|
||||
isLiked: false,
|
||||
images: ['https://picsum.photos/id/29/600/400'],
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
author: {
|
||||
name: 'Crypto_News',
|
||||
avatar: 'https://api.dicebear.com/7.x/shapes/svg?seed=Crypto_News',
|
||||
verified: true,
|
||||
},
|
||||
content:
|
||||
'重大新闻:SEC批准了第一个比特币现货ETF!这对加密市场的监管和机构采用是一个重要的进展。 #比特币ETF #SEC #加密监管',
|
||||
timestamp: '12小时前',
|
||||
likes: 215,
|
||||
comments: 87,
|
||||
reposts: 56,
|
||||
isLiked: true,
|
||||
tags: ['比特币ETF', 'SEC', '加密监管'],
|
||||
},
|
||||
])
|
||||
|
||||
const toggleLike = (postId: number) => {
|
||||
const post = posts.value.find((p) => p.id === postId)
|
||||
if (post) {
|
||||
post.isLiked = !post.isLiked
|
||||
post.likes += post.isLiked ? 1 : -1
|
||||
}
|
||||
}
|
||||
|
||||
const createPost = () => {
|
||||
if (!newPostContent.value.trim()) return
|
||||
|
||||
const newPost: Post = {
|
||||
id: posts.value.length + 1,
|
||||
author: {
|
||||
name: '当前用户',
|
||||
avatar: 'https://api.dicebear.com/7.x/shapes/svg?seed=CurrentUser',
|
||||
verified: false,
|
||||
},
|
||||
content: newPostContent.value,
|
||||
timestamp: '刚刚',
|
||||
likes: 0,
|
||||
comments: 0,
|
||||
reposts: 0,
|
||||
isLiked: false,
|
||||
}
|
||||
|
||||
posts.value.unshift(newPost)
|
||||
newPostContent.value = ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="community-view">
|
||||
<h1 class="page-title">社区</h1>
|
||||
<p class="page-description">与加密货币爱好者交流,分享见解和最新动态</p>
|
||||
|
||||
<div class="community-layout">
|
||||
<div class="main-content">
|
||||
<div class="feed-tabs">
|
||||
<button
|
||||
class="tab-btn"
|
||||
:class="{ active: filter === 'for-you' }"
|
||||
@click="filter = 'for-you'"
|
||||
>
|
||||
为你推荐
|
||||
</button>
|
||||
<button
|
||||
class="tab-btn"
|
||||
:class="{ active: filter === 'following' }"
|
||||
@click="filter = 'following'"
|
||||
>
|
||||
关注
|
||||
</button>
|
||||
<button
|
||||
class="tab-btn"
|
||||
:class="{ active: filter === 'trending' }"
|
||||
@click="filter = 'trending'"
|
||||
>
|
||||
热门话题
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="post-creator">
|
||||
<div class="post-input-container">
|
||||
<img
|
||||
src="https://api.dicebear.com/7.x/shapes/svg?seed=CurrentUser"
|
||||
alt="User Avatar"
|
||||
class="user-avatar"
|
||||
/>
|
||||
<textarea
|
||||
v-model="newPostContent"
|
||||
placeholder="分享你的加密见解..."
|
||||
class="post-input"
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="post-actions">
|
||||
<div class="post-tools">
|
||||
<button class="tool-btn">📷</button>
|
||||
<button class="tool-btn">🔗</button>
|
||||
<button class="tool-btn">#️⃣</button>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary post-btn"
|
||||
@click="createPost"
|
||||
:disabled="!newPostContent.trim()"
|
||||
>
|
||||
发布
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="posts-feed">
|
||||
<div v-for="post in posts" :key="post.id" class="post-card">
|
||||
<div class="post-header">
|
||||
<img :src="post.author.avatar" alt="User Avatar" class="post-avatar" />
|
||||
<div class="post-author">
|
||||
<div class="author-name">
|
||||
{{ post.author.name }}
|
||||
<span v-if="post.author.verified" class="verified-badge">✓</span>
|
||||
</div>
|
||||
<span class="post-time">{{ post.timestamp }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="post-content">
|
||||
<p>{{ post.content }}</p>
|
||||
<div v-if="post.images && post.images.length" class="post-images">
|
||||
<img
|
||||
v-for="(img, index) in post.images"
|
||||
:key="index"
|
||||
:src="img"
|
||||
alt="Post image"
|
||||
class="post-image"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="post.tags && post.tags.length" class="post-tags">
|
||||
<span v-for="(tag, index) in post.tags" :key="index" class="post-tag">
|
||||
#{{ tag }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="post-footer">
|
||||
<button
|
||||
class="post-action"
|
||||
:class="{ active: post.isLiked }"
|
||||
@click="toggleLike(post.id)"
|
||||
>
|
||||
<span class="action-icon">❤️</span>
|
||||
<span class="action-count">{{ post.likes }}</span>
|
||||
</button>
|
||||
<button class="post-action">
|
||||
<span class="action-icon">💬</span>
|
||||
<span class="action-count">{{ post.comments }}</span>
|
||||
</button>
|
||||
<button class="post-action">
|
||||
<span class="action-icon">🔄</span>
|
||||
<span class="action-count">{{ post.reposts }}</span>
|
||||
</button>
|
||||
<button class="post-action">
|
||||
<span class="action-icon">📤</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-section">
|
||||
<h3 class="sidebar-title">热门话题</h3>
|
||||
<ul class="trending-list">
|
||||
<li class="trending-item">
|
||||
<div class="trending-tag">#比特币减半</div>
|
||||
<div class="trending-stats">12.5K 帖子</div>
|
||||
</li>
|
||||
<li class="trending-item">
|
||||
<div class="trending-tag">#以太坊2.0</div>
|
||||
<div class="trending-stats">8.3K 帖子</div>
|
||||
</li>
|
||||
<li class="trending-item">
|
||||
<div class="trending-tag">#DeFi夏天</div>
|
||||
<div class="trending-stats">6.7K 帖子</div>
|
||||
</li>
|
||||
<li class="trending-item">
|
||||
<div class="trending-tag">#NFT艺术</div>
|
||||
<div class="trending-stats">5.2K 帖子</div>
|
||||
</li>
|
||||
<li class="trending-item">
|
||||
<div class="trending-tag">#Layer2扩容</div>
|
||||
<div class="trending-stats">3.9K 帖子</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-section">
|
||||
<h3 class="sidebar-title">推荐关注</h3>
|
||||
<ul class="suggested-users">
|
||||
<li class="user-item">
|
||||
<img
|
||||
src="https://api.dicebear.com/7.x/shapes/svg?seed=VitalikB"
|
||||
alt="User Avatar"
|
||||
class="user-avatar-small"
|
||||
/>
|
||||
<div class="user-info">
|
||||
<div class="user-name">
|
||||
Vitalik.eth
|
||||
<span class="verified-badge-small">✓</span>
|
||||
</div>
|
||||
<div class="user-handle">@VitalikButerin</div>
|
||||
</div>
|
||||
<button class="btn btn-secondary btn-small">关注</button>
|
||||
</li>
|
||||
<li class="user-item">
|
||||
<img
|
||||
src="https://api.dicebear.com/7.x/shapes/svg?seed=SBF"
|
||||
alt="User Avatar"
|
||||
class="user-avatar-small"
|
||||
/>
|
||||
<div class="user-info">
|
||||
<div class="user-name">CZ Binance</div>
|
||||
<div class="user-handle">@cz_binance</div>
|
||||
</div>
|
||||
<button class="btn btn-secondary btn-small">关注</button>
|
||||
</li>
|
||||
<li class="user-item">
|
||||
<img
|
||||
src="https://api.dicebear.com/7.x/shapes/svg?seed=ElonM"
|
||||
alt="User Avatar"
|
||||
class="user-avatar-small"
|
||||
/>
|
||||
<div class="user-info">
|
||||
<div class="user-name">
|
||||
Elon Musk
|
||||
<span class="verified-badge-small">✓</span>
|
||||
</div>
|
||||
<div class="user-handle">@elonmusk</div>
|
||||
</div>
|
||||
<button class="btn btn-secondary btn-small">关注</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.community-view {
|
||||
width: 100%;
|
||||
max-width: 1440px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 350px;
|
||||
gap: 2rem;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 2.2rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.page-description {
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.community-layout {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
flex-basis: 65%;
|
||||
}
|
||||
|
||||
.feed-tabs {
|
||||
display: flex;
|
||||
margin-bottom: 1.5rem;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 1rem 1.5rem;
|
||||
font-size: 1rem;
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tab-btn:hover {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
color: var(--color-accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tab-btn.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: var(--color-accent);
|
||||
}
|
||||
|
||||
.post-creator {
|
||||
background-color: var(--color-bg-card);
|
||||
padding: 1.5rem;
|
||||
border-radius: var(--border-radius);
|
||||
margin-bottom: 2rem;
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.post-input-container {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.post-input {
|
||||
flex: 1;
|
||||
background-color: var(--color-bg-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 0.8rem;
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1rem;
|
||||
min-height: 100px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.post-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.post-tools {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.tool-btn {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.tool-btn:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.post-btn {
|
||||
padding: 0.6rem 1.5rem;
|
||||
}
|
||||
|
||||
.posts-feed {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.post-card {
|
||||
background-color: var(--color-bg-card);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 1.5rem;
|
||||
border: 1px solid var(--color-border);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.post-card:hover {
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.post-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.post-avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.post-author {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.author-name {
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
.verified-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-color: var(--color-accent);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.post-time {
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.post-content {
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.post-content p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.post-images {
|
||||
margin-top: 1rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
border-radius: var(--border-radius);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.post-image {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
object-fit: cover;
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.post-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.post-tag {
|
||||
color: var(--color-accent);
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.post-tag:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.post-footer {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.post-action {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.post-action:hover,
|
||||
.post-action.active {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
/* Sidebar Styling */
|
||||
.sidebar {
|
||||
width: 300px;
|
||||
position: sticky;
|
||||
top: calc(var(--header-height) + 1rem);
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.sidebar-section {
|
||||
background-color: var(--color-bg-card);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.sidebar-title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1.2rem;
|
||||
padding-bottom: 0.8rem;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.trending-list,
|
||||
.suggested-users {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.trending-item {
|
||||
padding: 0.8rem 0;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.trending-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.trending-tag {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
.trending-stats {
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.user-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.8rem;
|
||||
padding: 0.8rem 0;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.user-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.user-avatar-small {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
.verified-badge-small {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background-color: var(--color-accent);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
|
||||
.user-handle {
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
padding: 0.3rem 0.8rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.community-view {
|
||||
grid-template-columns: 1fr 300px;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.community-view {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
336
src/views/HomeView.vue
Normal file
336
src/views/HomeView.vue
Normal file
@ -0,0 +1,336 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div class="home-view">
|
||||
<section class="hero-section">
|
||||
<div class="hero-content">
|
||||
<h1 class="hero-title">Crypto.AI <span class="accent">加密货币工具平台</span></h1>
|
||||
<p class="hero-subtitle">一站式加密货币工具集合、AI分析和社区互动平台</p>
|
||||
<div class="hero-actions">
|
||||
<button class="btn btn-primary">探索工具</button>
|
||||
<button class="btn btn-secondary">加入社区</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="features-section">
|
||||
<div class="feature-card card">
|
||||
<div class="feature-icon">🛠️</div>
|
||||
<h3 class="feature-title">工具集合</h3>
|
||||
<p class="feature-desc">批量钱包创建、钱包归集等实用工具,助您高效管理加密资产</p>
|
||||
<button class="btn-action">了解更多</button>
|
||||
</div>
|
||||
|
||||
<div class="feature-card card">
|
||||
<div class="feature-icon">🤖</div>
|
||||
<h3 class="feature-title">AI Agent</h3>
|
||||
<p class="feature-desc">加密项目医生与币种技术分析,让您的投资决策更明智</p>
|
||||
<button class="btn-action">了解更多</button>
|
||||
</div>
|
||||
|
||||
<div class="feature-card card">
|
||||
<div class="feature-icon">👥</div>
|
||||
<h3 class="feature-title">社区互动</h3>
|
||||
<p class="feature-desc">类似微博的信息流,实时了解行业动态,分享您的见解</p>
|
||||
<button class="btn-action">了解更多</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="stats-section">
|
||||
<div class="stats-header">
|
||||
<h2>平台数据</h2>
|
||||
<p>实时数据</p>
|
||||
</div>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card card">
|
||||
<div class="stat-value">10,000+</div>
|
||||
<div class="stat-label">钱包已创建</div>
|
||||
</div>
|
||||
<div class="stat-card card">
|
||||
<div class="stat-value">5,000+</div>
|
||||
<div class="stat-label">项目已分析</div>
|
||||
</div>
|
||||
<div class="stat-card card">
|
||||
<div class="stat-value">20,000+</div>
|
||||
<div class="stat-label">社区成员</div>
|
||||
</div>
|
||||
<div class="stat-card card">
|
||||
<div class="stat-value">99.9%</div>
|
||||
<div class="stat-label">正常运行时间</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.home-view {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hero-section {
|
||||
text-align: center;
|
||||
padding: 4rem 2rem;
|
||||
margin-bottom: 4rem;
|
||||
background-color: var(--color-bg-secondary);
|
||||
border-radius: var(--border-radius);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hero-section::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: radial-gradient(circle at top right, rgba(59, 130, 246, 0.1), transparent 50%);
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 3.5rem;
|
||||
font-weight: 800;
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.2;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.accent {
|
||||
color: var(--color-accent);
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.accent::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background-color: var(--color-accent-light);
|
||||
z-index: -1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: 1.2rem;
|
||||
color: var(--color-text-secondary);
|
||||
max-width: 700px;
|
||||
margin: 0 auto 2.5rem;
|
||||
}
|
||||
|
||||
.hero-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.8rem 1.6rem;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--color-accent);
|
||||
color: var(--color-bg-primary);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--color-accent-hover);
|
||||
color: var(--color-bg-primary);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: transparent;
|
||||
color: var(--color-text-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.features-section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2rem;
|
||||
margin-bottom: 4rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
flex: 1;
|
||||
min-width: 300px;
|
||||
padding: 2.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition:
|
||||
transform 0.3s ease,
|
||||
box-shadow 0.3s ease;
|
||||
border: 1px solid var(--color-border);
|
||||
height: 100%;
|
||||
background-color: var(--color-bg-card);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1.5rem;
|
||||
background-color: var(--color-accent-light);
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.feature-title {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.feature-desc {
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.5;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.btn-action {
|
||||
align-self: flex-start;
|
||||
background: transparent;
|
||||
color: var(--color-accent);
|
||||
border: none;
|
||||
padding: 0.5rem 0;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-action::after {
|
||||
content: '→';
|
||||
margin-left: 0.5rem;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-action:hover::after {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
.stats-section {
|
||||
margin-bottom: 4rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stats-header {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.stats-header h2 {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.stats-header p {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
flex: 1;
|
||||
min-width: 240px;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid var(--color-border);
|
||||
background-color: var(--color-bg-card);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-accent);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.feature-card {
|
||||
min-width: calc(50% - 1rem);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.hero-title {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.hero-actions {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
min-width: calc(50% - 1rem);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.stat-card {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.hero-section {
|
||||
padding: 3rem 1rem;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
392
src/views/ToolsView.vue
Normal file
392
src/views/ToolsView.vue
Normal file
@ -0,0 +1,392 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const activeTab = ref('wallet-creation')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tools-view">
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">工具集合</h1>
|
||||
<p class="page-description">一系列实用的加密货币工具,助您高效管理数字资产</p>
|
||||
</div>
|
||||
|
||||
<div class="tools-tabs">
|
||||
<button
|
||||
class="tab-button"
|
||||
:class="{ active: activeTab === 'wallet-creation' }"
|
||||
@click="activeTab = 'wallet-creation'"
|
||||
>
|
||||
批量钱包创建
|
||||
</button>
|
||||
<button
|
||||
class="tab-button"
|
||||
:class="{ active: activeTab === 'wallet-collection' }"
|
||||
@click="activeTab = 'wallet-collection'"
|
||||
>
|
||||
钱包归集
|
||||
</button>
|
||||
<button
|
||||
class="tab-button"
|
||||
:class="{ active: activeTab === 'other-tools' }"
|
||||
@click="activeTab = 'other-tools'"
|
||||
>
|
||||
其他工具
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="tool-content card">
|
||||
<!-- 批量钱包创建工具 -->
|
||||
<div v-if="activeTab === 'wallet-creation'" class="tool-panel">
|
||||
<h2 class="tool-title">批量钱包创建</h2>
|
||||
<p class="tool-desc">批量创建以太坊兼容的钱包地址和私钥</p>
|
||||
|
||||
<div class="tool-form">
|
||||
<div class="form-group">
|
||||
<label>创建钱包数量</label>
|
||||
<input
|
||||
type="number"
|
||||
class="form-input"
|
||||
placeholder="输入需要创建的钱包数量"
|
||||
min="1"
|
||||
max="100"
|
||||
value="10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>钱包类型</label>
|
||||
<select class="form-select">
|
||||
<option value="eth">以太坊 (ETH)</option>
|
||||
<option value="bsc">币安智能链 (BSC)</option>
|
||||
<option value="polygon">Polygon</option>
|
||||
<option value="arbitrum">Arbitrum</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>导出格式</label>
|
||||
<select class="form-select">
|
||||
<option value="json">JSON</option>
|
||||
<option value="csv">CSV</option>
|
||||
<option value="txt">TXT</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary btn-block">开始创建</button>
|
||||
</div>
|
||||
|
||||
<div class="results-preview">
|
||||
<h3>预览结果</h3>
|
||||
<div class="preview-content">
|
||||
<p>暂无数据,请先创建钱包</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 钱包归集工具 -->
|
||||
<div v-if="activeTab === 'wallet-collection'" class="tool-panel">
|
||||
<h2 class="tool-title">钱包归集</h2>
|
||||
<p class="tool-desc">将多个钱包中的资产归集到一个目标钱包</p>
|
||||
|
||||
<div class="tool-form">
|
||||
<div class="form-group">
|
||||
<label>目标钱包地址</label>
|
||||
<input type="text" class="form-input" placeholder="输入归集目标钱包地址" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>源钱包私钥列表</label>
|
||||
<textarea class="form-textarea" placeholder="每行输入一个私钥..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>网络</label>
|
||||
<select class="form-select">
|
||||
<option value="eth">以太坊 (ETH)</option>
|
||||
<option value="bsc">币安智能链 (BSC)</option>
|
||||
<option value="polygon">Polygon</option>
|
||||
<option value="arbitrum">Arbitrum</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>GAS设置</label>
|
||||
<div class="gas-settings">
|
||||
<input type="text" class="form-input" placeholder="Gas Price (Gwei)" />
|
||||
<input type="text" class="form-input" placeholder="Gas Limit" value="21000" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary btn-block">开始归集</button>
|
||||
</div>
|
||||
|
||||
<div class="results-preview">
|
||||
<h3>归集状态</h3>
|
||||
<div class="preview-content">
|
||||
<p>暂无数据,请先开始归集</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 其他工具 -->
|
||||
<div v-if="activeTab === 'other-tools'" class="tool-panel">
|
||||
<h2 class="tool-title">其他工具</h2>
|
||||
<p class="tool-desc">更多实用的加密货币工具</p>
|
||||
|
||||
<div class="tools-grid">
|
||||
<div class="tool-card card">
|
||||
<h3>Token授权查询</h3>
|
||||
<p>查询并撤销钱包中的Token授权</p>
|
||||
<button class="btn btn-secondary">使用工具</button>
|
||||
</div>
|
||||
|
||||
<div class="tool-card card">
|
||||
<h3>交易解码</h3>
|
||||
<p>解码链上交易数据,了解交易详情</p>
|
||||
<button class="btn btn-secondary">使用工具</button>
|
||||
</div>
|
||||
|
||||
<div class="tool-card card">
|
||||
<h3>Gas计算器</h3>
|
||||
<p>计算不同网络的Gas费用</p>
|
||||
<button class="btn btn-secondary">使用工具</button>
|
||||
</div>
|
||||
|
||||
<div class="tool-card card">
|
||||
<h3>Merkle Tree生成器</h3>
|
||||
<p>为空投活动生成Merkle Tree</p>
|
||||
<button class="btn btn-secondary">使用工具</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.tools-view {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 2rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 2.2rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.page-description {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.tools-tabs {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
background-color: var(--color-bg-secondary);
|
||||
padding: 0.5rem;
|
||||
border-radius: var(--border-radius);
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--color-text-secondary);
|
||||
padding: 0.8rem 1.5rem;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
border-radius: var(--border-radius);
|
||||
transition: all 0.2s ease;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tab-button:hover {
|
||||
color: var(--color-text-primary);
|
||||
background-color: var(--color-bg-elevated);
|
||||
}
|
||||
|
||||
.tab-button.active {
|
||||
color: var(--color-text-primary);
|
||||
background-color: var(--color-bg-card);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tool-content {
|
||||
background-color: var(--color-bg-card);
|
||||
border: 1px solid var(--color-border);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tool-panel {
|
||||
padding: 2rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tool-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.tool-desc {
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.tool-form {
|
||||
margin-bottom: 2rem;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-select,
|
||||
.form-textarea {
|
||||
width: 100%;
|
||||
padding: 0.8rem 1rem;
|
||||
background-color: var(--color-bg-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1rem;
|
||||
transition:
|
||||
border-color 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.form-input:focus,
|
||||
.form-select:focus,
|
||||
.form-textarea:focus {
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: 0 0 0 2px var(--color-accent-light);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
min-height: 120px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.gas-settings {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.gas-settings .form-input {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.btn-block {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.results-preview {
|
||||
background-color: var(--color-bg-primary);
|
||||
padding: 1.5rem;
|
||||
border-radius: var(--border-radius);
|
||||
border: 1px solid var(--color-border);
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.results-preview h3 {
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.preview-content {
|
||||
color: var(--color-text-secondary);
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.tools-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tool-card {
|
||||
flex: 1;
|
||||
min-width: 280px;
|
||||
background-color: var(--color-bg-primary);
|
||||
padding: 1.5rem;
|
||||
border: 1px solid var(--color-border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.tool-card:hover {
|
||||
border-color: var(--color-accent);
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 7px 14px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.tool-card h3 {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tool-card p {
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: 1.2rem;
|
||||
font-size: 0.9rem;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.tool-card {
|
||||
min-width: calc(50% - 1rem);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.tools-tabs {
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
text-align: left;
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.tool-card {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.tool-panel {
|
||||
padding: 1.5rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.page-title {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
12
tsconfig.app.json
Normal file
12
tsconfig.app.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
14
tsconfig.json
Normal file
14
tsconfig.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.vitest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
19
tsconfig.node.json
Normal file
19
tsconfig.node.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "@tsconfig/node22/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*",
|
||||
"eslint.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
11
tsconfig.vitest.json
Normal file
11
tsconfig.vitest.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "./tsconfig.app.json",
|
||||
"include": ["src/**/__tests__/*", "env.d.ts"],
|
||||
"exclude": [],
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo",
|
||||
|
||||
"lib": [],
|
||||
"types": ["node", "jsdom"]
|
||||
}
|
||||
}
|
||||
20
vite.config.ts
Normal file
20
vite.config.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
vueJsx(),
|
||||
vueDevTools(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
},
|
||||
},
|
||||
})
|
||||
14
vitest.config.ts
Normal file
14
vitest.config.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { mergeConfig, defineConfig, configDefaults } from 'vitest/config'
|
||||
import viteConfig from './vite.config'
|
||||
|
||||
export default mergeConfig(
|
||||
viteConfig,
|
||||
defineConfig({
|
||||
test: {
|
||||
environment: 'jsdom',
|
||||
exclude: [...configDefaults.exclude, 'e2e/**'],
|
||||
root: fileURLToPath(new URL('./', import.meta.url)),
|
||||
},
|
||||
}),
|
||||
)
|
||||
Loading…
Reference in New Issue
Block a user