第 0 阶段:项目初始化

This commit is contained in:
yml
2026-05-22 15:05:28 +08:00
commit 8e7f1f17f0
55 changed files with 4519 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
FROM node:24-alpine AS build
WORKDIR /src
COPY frontend/package*.json ./
RUN npm install
COPY frontend/ .
RUN npm run build
FROM nginx:1.29-alpine
COPY --from=build /src/dist /usr/share/nginx/html
COPY deploy/nginx/default.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
+12
View File
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<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>
+2201
View File
File diff suppressed because it is too large Load Diff
+28
View File
@@ -0,0 +1,28 @@
{
"name": "hfb-sys-frontend",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite --host 0.0.0.0",
"build": "vue-tsc -b && vite build",
"preview": "vite preview --host 0.0.0.0",
"typecheck": "vue-tsc -b"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"axios": "^1.13.2",
"element-plus": "^2.11.9",
"pinia": "^3.0.4",
"vue": "^3.5.24",
"vue-router": "^4.6.3"
},
"devDependencies": {
"@types/node": "^24.10.1",
"@vitejs/plugin-vue": "^6.0.2",
"@vue/tsconfig": "^0.8.1",
"typescript": "~5.9.3",
"vite": "^7.2.4",
"vue-tsc": "^3.1.5"
}
}
+11
View File
@@ -0,0 +1,11 @@
<script setup lang="ts">
import { RouterView } from 'vue-router'
import AppLayout from './layouts/AppLayout.vue'
</script>
<template>
<AppLayout>
<RouterView />
</AppLayout>
</template>
+6
View File
@@ -0,0 +1,6 @@
import axios from 'axios'
export const apiClient = axios.create({
baseURL: '/api',
timeout: 10000,
})
+32
View File
@@ -0,0 +1,32 @@
<script setup lang="ts">
import { Bell, House, Shop, Tickets, UserFilled, Wallet } from '@element-plus/icons-vue'
const navItems = [
{ label: '首页', to: '/', icon: House },
{ label: '租号', to: '/listings', icon: Shop },
{ label: '订单', to: '/orders', icon: Tickets },
{ label: '钱包', to: '/wallet', icon: Wallet },
{ label: '通知', to: '/notifications', icon: Bell },
{ label: '实名', to: '/realname', icon: UserFilled },
]
</script>
<template>
<div class="app-shell">
<aside class="sidebar">
<RouterLink class="brand" to="/">
<span class="brand-mark">H</span>
<span>哈夫币租号</span>
</RouterLink>
<nav class="nav">
<RouterLink v-for="item in navItems" :key="item.to" class="nav-link" :to="item.to">
<el-icon><component :is="item.icon" /></el-icon>
<span>{{ item.label }}</span>
</RouterLink>
</nav>
</aside>
<main class="main">
<slot />
</main>
</div>
</template>
+11
View File
@@ -0,0 +1,11 @@
import 'element-plus/dist/index.css'
import './styles/base.css'
import ElementPlus from 'element-plus'
import { createPinia } from 'pinia'
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App).use(createPinia()).use(router).use(ElementPlus).mount('#app')
+24
View File
@@ -0,0 +1,24 @@
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', name: 'home', component: () => import('@/views/public/HomeView.vue') },
{ path: '/listings', name: 'listings', component: () => import('@/views/public/ListingsView.vue') },
{ path: '/listings/:id', name: 'listing-detail', component: () => import('@/views/public/ListingDetailView.vue') },
{ path: '/orders/create', name: 'order-create', component: () => import('@/views/account/OrderCreateView.vue') },
{ path: '/orders', name: 'orders', component: () => import('@/views/account/OrdersView.vue') },
{ path: '/orders/:id', name: 'order-detail', component: () => import('@/views/account/OrderDetailView.vue') },
{ path: '/wallet', name: 'wallet', component: () => import('@/views/account/WalletView.vue') },
{ path: '/notifications', name: 'notifications', component: () => import('@/views/account/NotificationsView.vue') },
{ path: '/realname', name: 'realname', component: () => import('@/views/account/RealnameView.vue') },
{ path: '/seller/listings', name: 'seller-listings', component: () => import('@/views/seller/SellerListingsView.vue') },
{ path: '/seller/handoffs', name: 'seller-handoffs', component: () => import('@/views/seller/SellerHandoffsView.vue') },
{ path: '/seller/earnings', name: 'seller-earnings', component: () => import('@/views/seller/SellerEarningsView.vue') },
{ path: '/admin/dashboard', name: 'admin-dashboard', component: () => import('@/views/admin/AdminDashboardView.vue') },
{ path: '/admin/disputes', name: 'admin-disputes', component: () => import('@/views/admin/AdminDisputesView.vue') },
{ path: '/admin/system-configs', name: 'admin-system-configs', component: () => import('@/views/admin/AdminSystemConfigsView.vue') },
],
})
export default router
+9
View File
@@ -0,0 +1,9 @@
import { defineStore } from 'pinia'
export const useSessionStore = defineStore('session', {
state: () => ({
token: '',
phone: '',
realnameStatus: 'unknown',
}),
})
+164
View File
@@ -0,0 +1,164 @@
:root {
color: #1f2933;
background: #f6f8fb;
font-family:
Inter, "PingFang SC", "Microsoft YaHei", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
sans-serif;
font-synthesis: none;
text-rendering: optimizeLegibility;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
min-width: 320px;
min-height: 100vh;
}
a {
color: inherit;
text-decoration: none;
}
.app-shell {
display: grid;
min-height: 100vh;
grid-template-columns: 248px 1fr;
}
.sidebar {
border-right: 1px solid #e4e7ed;
background: #ffffff;
padding: 20px 16px;
}
.brand {
display: flex;
align-items: center;
gap: 10px;
min-height: 44px;
font-weight: 700;
}
.brand-mark {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 8px;
background: #0f766e;
color: #ffffff;
}
.nav {
display: grid;
gap: 6px;
margin-top: 24px;
}
.nav-link {
display: flex;
align-items: center;
gap: 10px;
min-height: 40px;
border-radius: 8px;
padding: 0 12px;
color: #52616f;
}
.nav-link.router-link-active {
background: #e8f5f2;
color: #0f766e;
font-weight: 600;
}
.main {
min-width: 0;
padding: 32px;
}
.page {
max-width: 1120px;
}
.page-header {
max-width: 720px;
}
.eyebrow {
margin: 0 0 8px;
color: #0f766e;
font-size: 13px;
font-weight: 700;
text-transform: uppercase;
}
h1 {
margin: 0;
font-size: 32px;
line-height: 1.2;
}
.page-header p:last-child {
margin: 12px 0 0;
color: #52616f;
line-height: 1.7;
}
.metric-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
margin-top: 28px;
}
.metric-card {
border: 1px solid #e4e7ed;
border-radius: 8px;
background: #ffffff;
padding: 18px;
}
.metric-card span {
display: block;
color: #6b7785;
font-size: 13px;
}
.metric-card strong {
display: block;
margin-top: 8px;
font-size: 20px;
}
@media (max-width: 760px) {
.app-shell {
grid-template-columns: 1fr;
}
.sidebar {
position: sticky;
top: 0;
z-index: 10;
border-right: 0;
border-bottom: 1px solid #e4e7ed;
}
.nav {
grid-auto-flow: column;
grid-auto-columns: max-content;
overflow-x: auto;
}
.main {
padding: 24px 16px;
}
.metric-grid {
grid-template-columns: 1fr;
}
}
@@ -0,0 +1,9 @@
<template>
<section class="page">
<div class="page-header">
<p class="eyebrow">Notifications</p>
<h1>站内信</h1>
<p>接收审核交接归还申诉和仲裁结果通知</p>
</div>
</section>
</template>
@@ -0,0 +1,9 @@
<template>
<section class="page">
<div class="page-header">
<p class="eyebrow">Create Order</p>
<h1>创建订单</h1>
<p>确认租期租金押金和账号资产快照</p>
</div>
</section>
</template>
@@ -0,0 +1,9 @@
<template>
<section class="page">
<div class="page-header">
<p class="eyebrow">Order Timeline</p>
<h1>订单详情</h1>
<p>展示状态机时间线交接记录归还入口和申诉入口</p>
</div>
</section>
</template>
@@ -0,0 +1,9 @@
<template>
<section class="page">
<div class="page-header">
<p class="eyebrow">Orders</p>
<h1>我的订单</h1>
<p>跟踪待交接租赁中待归还申诉中和已完成订单</p>
</div>
</section>
</template>
@@ -0,0 +1,9 @@
<template>
<section class="page">
<div class="page-header">
<p class="eyebrow">Realname</p>
<h1>实名认证</h1>
<p>号主发布前必须完成实名认证</p>
</div>
</section>
</template>
@@ -0,0 +1,9 @@
<template>
<section class="page">
<div class="page-header">
<p class="eyebrow">Wallet</p>
<h1>钱包</h1>
<p>查看可用余额冻结余额和不可变资金流水</p>
</div>
</section>
</template>
@@ -0,0 +1,9 @@
<template>
<section class="page">
<div class="page-header">
<p class="eyebrow">Admin</p>
<h1>后台仪表盘</h1>
<p>待审核待仲裁订单和流水概览</p>
</div>
</section>
</template>
@@ -0,0 +1,9 @@
<template>
<section class="page">
<div class="page-header">
<p class="eyebrow">Arbitration</p>
<h1>仲裁中心</h1>
<p>处理无法登录资产损失哈夫币争议和超时归还</p>
</div>
</section>
</template>
@@ -0,0 +1,9 @@
<template>
<section class="page">
<div class="page-header">
<p class="eyebrow">Configs</p>
<h1>系统配置</h1>
<p>管理交接超时归还超时短信限流最低押金和抽成比例</p>
</div>
</section>
</template>
+23
View File
@@ -0,0 +1,23 @@
<template>
<section class="page">
<div class="page-header">
<p class="eyebrow">Delta Force Rental</p>
<h1>哈夫币租号平台</h1>
<p>先把账号发布交接订单押金和仲裁链路打牢再接真实支付和更复杂的风控</p>
</div>
<div class="metric-grid">
<div class="metric-card">
<span>一期路线</span>
<strong>自研业务核心</strong>
</div>
<div class="metric-card">
<span>交接模式</span>
<strong>号主手动交接</strong>
</div>
<div class="metric-card">
<span>资金模型</span>
<strong>先做账务</strong>
</div>
</div>
</section>
</template>
@@ -0,0 +1,9 @@
<template>
<section class="page">
<div class="page-header">
<p class="eyebrow">Listing Detail</p>
<h1>租号详情</h1>
<p>展示账号资产哈夫币快照租金押金号主信用和风险提示</p>
</div>
</section>
</template>
@@ -0,0 +1,9 @@
<template>
<section class="page">
<div class="page-header">
<p class="eyebrow">Listings</p>
<h1>租号列表</h1>
<p>后续支持区服平台价格押金段位和哈夫币数量筛选</p>
</div>
</section>
</template>
@@ -0,0 +1,9 @@
<template>
<section class="page">
<div class="page-header">
<p class="eyebrow">Earnings</p>
<h1>收益流水</h1>
<p>查看租金平台抽成结算和冻结金额</p>
</div>
</section>
</template>
@@ -0,0 +1,9 @@
<template>
<section class="page">
<div class="page-header">
<p class="eyebrow">Handoffs</p>
<h1>交接管理</h1>
<p>处理待交接待归还确认和超时订单</p>
</div>
</section>
</template>
@@ -0,0 +1,9 @@
<template>
<section class="page">
<div class="page-header">
<p class="eyebrow">Seller</p>
<h1>我的发布</h1>
<p>管理账号发布审核状态上架和下架</p>
</div>
</section>
</template>
+12
View File
@@ -0,0 +1,12 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
"types": ["vite/client"],
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}
+15
View File
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2023",
"lib": ["ES2023"],
"module": "ESNext",
"types": ["node"],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"noEmit": true,
"strict": true
},
"include": ["vite.config.ts"]
}
+19
View File
@@ -0,0 +1,19 @@
import { fileURLToPath, URL } from 'node:url'
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
server: {
port: 5173,
proxy: {
'/api': 'http://127.0.0.1:8080',
},
},
})