项目初版完成
This commit is contained in:
commit
0c96d3fa1f
8
.editorconfig
Normal file
8
.editorconfig
Normal file
@ -0,0 +1,8 @@
|
||||
[*.{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
|
||||
30
.gitignore
vendored
Normal file
30
.gitignore
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
# 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
|
||||
114
README.md
Normal file
114
README.md
Normal file
@ -0,0 +1,114 @@
|
||||
# Likei H5 项目
|
||||
|
||||
这是一个基于 Vue 3 + Vite 的移动端 H5 应用,包含申请加入团队和主播中心两个主要页面。
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
src/
|
||||
├── components/
|
||||
│ ├── MobileHeader.vue # 公共头部导航组件
|
||||
│ └── HelloWorld.vue # 示例组件
|
||||
├── views/
|
||||
│ ├── HomeView.vue # 首页(带导航按钮)
|
||||
│ ├── ApplyView.vue # 申请加入团队页面
|
||||
│ ├── HostCenterView.vue # 主播中心页面
|
||||
│ └── AboutView.vue # 关于页面
|
||||
├── router/
|
||||
│ └── index.js # 路由配置
|
||||
├── assets/ # 静态资源
|
||||
├── App.vue # 根组件
|
||||
└── main.js # 入口文件
|
||||
```
|
||||
|
||||
## 页面说明
|
||||
|
||||
### 1. 申请加入团队页面 (`/apply`)
|
||||
- 包含申请表单
|
||||
- 用户可以输入代理商ID
|
||||
- 有帮助提示功能
|
||||
- 支持表单验证
|
||||
|
||||
### 2. 主播中心页面 (`/host-center`)
|
||||
- 显示用户信息和头像
|
||||
- 显示收入统计
|
||||
- 显示今日任务进度
|
||||
- 包含交换金币、转账、提现等功能按钮
|
||||
- 点击Date按钮跳转到历史薪资页面
|
||||
- 点击Exchange按钮跳转到兑换金币页面
|
||||
|
||||
### 3. 主播设置页面 (`/host-setting`)
|
||||
- 显示代理商信息
|
||||
- 银行卡管理
|
||||
- KYC认证信息
|
||||
- 支持离开代理商操作
|
||||
|
||||
### 4. 历史薪资页面 (`/history-salary`)
|
||||
- 按月份查看薪资记录
|
||||
- 详细的收入分解(基础薪资、奖金、佣金、扣款)
|
||||
- 支付状态显示
|
||||
- 月度统计汇总
|
||||
|
||||
### 5. 兑换金币页面 (`/exchange-gold-coins`)
|
||||
- 显示可用余额
|
||||
- 收款人选择和管理
|
||||
- 金币选择网格
|
||||
- 支持多选金币进行转账
|
||||
- 点击收款人可跳转到搜索页面
|
||||
|
||||
### 6. 搜索收款人页面 (`/search-payee`)
|
||||
- 实时搜索功能
|
||||
- 支持按ID或姓名搜索
|
||||
- 显示用户标签(Host、Agency、Salary)
|
||||
- 搜索结果选择和添加
|
||||
|
||||
### 3. 公共组件
|
||||
|
||||
#### MobileHeader.vue
|
||||
可复用的移动端头部导航组件,支持:
|
||||
- 自定义标题
|
||||
- 返回按钮(可选)
|
||||
- 帮助按钮(可选)
|
||||
- 自定义事件处理
|
||||
|
||||
## 技术特点
|
||||
|
||||
1. **Vue 3 Composition API**: 使用最新的 Vue 3 语法
|
||||
2. **响应式设计**: 适配手机和桌面端
|
||||
3. **组件化开发**: 公共组件可复用
|
||||
4. **简洁的样式**: 使用原生CSS,无外部UI库依赖
|
||||
5. **路由管理**: 使用 Vue Router 4
|
||||
|
||||
## 开发运行
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
npm install
|
||||
|
||||
# 开发模式运行
|
||||
npm run dev
|
||||
|
||||
# 构建生产版本
|
||||
npm run build
|
||||
|
||||
# 预览生产版本
|
||||
npm run preview
|
||||
```
|
||||
|
||||
## 代码特点
|
||||
|
||||
- **适合初学者**: 代码结构清晰,注释完整
|
||||
- **无复杂依赖**: 只使用必要的Vue生态工具
|
||||
- **移动端优先**: 专为移动端体验设计
|
||||
- **易于扩展**: 组件化架构便于添加新功能
|
||||
|
||||
## 访问路径
|
||||
|
||||
- 首页: `/`
|
||||
- 申请页面: `/apply`
|
||||
- 主播中心: `/host-center`
|
||||
- 主播设置: `/host-setting`
|
||||
- 历史薪资: `/history-salary`
|
||||
- 兑换金币: `/exchange-gold-coins`
|
||||
- 搜索收款人: `/search-payee`
|
||||
- 关于页面: `/about`
|
||||
85
START.md
Normal file
85
START.md
Normal file
@ -0,0 +1,85 @@
|
||||
# 快速开始
|
||||
|
||||
## 项目说明
|
||||
这是一个Vue3移动端H5项目的演示,包含两个主要页面:
|
||||
1. **申请加入团队页面** - 模拟用户申请成为主播的流程
|
||||
2. **主播中心页面** - 显示主播的收入、任务等信息
|
||||
|
||||
## 启动步骤
|
||||
|
||||
1. **安装依赖**
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
2. **启动开发服务器**
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
3. **访问应用**
|
||||
- 在浏览器中打开显示的本地地址(通常是 http://localhost:5173)
|
||||
- 建议用浏览器的移动端模拟器查看效果
|
||||
|
||||
## 页面预览
|
||||
|
||||
### 首页
|
||||
- 包含三个导航按钮
|
||||
- 点击可以跳转到对应的功能页面
|
||||
|
||||
### 申请页面 (/apply)
|
||||
- 包含申请表单
|
||||
- 输入代理商ID
|
||||
- 点击帮助按钮查看提示
|
||||
|
||||
### 主播中心 (/host-center)
|
||||
- 查看用户信息
|
||||
- 查看收入统计
|
||||
- 查看任务进度
|
||||
- 操作按钮(交换、转账、提现)
|
||||
- Date按钮跳转到历史薪资
|
||||
- Exchange按钮跳转到兑换金币
|
||||
|
||||
### 主播设置 (/host-setting)
|
||||
- 查看代理商信息
|
||||
- 管理银行卡信息
|
||||
- KYC认证管理
|
||||
|
||||
### 历史薪资 (/history-salary)
|
||||
- 按月份查看薪资
|
||||
- 详细的收入分解
|
||||
- 月度统计信息
|
||||
|
||||
### 兑换金币 (/exchange-gold-coins)
|
||||
- 金币选择和转账
|
||||
- 收款人管理
|
||||
- 点击收款人跳转搜索
|
||||
|
||||
### 搜索收款人 (/search-payee)
|
||||
- 实时搜索用户
|
||||
- 按ID或名称搜索
|
||||
|
||||
## 技术亮点
|
||||
|
||||
✅ **Vue 3** - 使用最新的Composition API语法
|
||||
✅ **响应式设计** - 适配移动端和桌面端
|
||||
✅ **组件化** - MobileHeader作为公共组件复用
|
||||
✅ **路由管理** - 使用Vue Router进行页面跳转
|
||||
✅ **简洁样式** - 原生CSS,无复杂依赖
|
||||
✅ **易于理解** - 代码结构清晰,适合Vue3初学者
|
||||
|
||||
## 文件结构
|
||||
```
|
||||
src/
|
||||
├── components/MobileHeader.vue # 公共头部组件
|
||||
├── views/
|
||||
│ ├── HomeView.vue # 首页
|
||||
│ ├── ApplyView.vue # 申请页面
|
||||
│ ├── HostCenterView.vue # 主播中心
|
||||
│ ├── HostSettingView.vue # 主播设置
|
||||
│ ├── HistorySalaryView.vue # 历史薪资
|
||||
│ ├── ExchangeGoldCoinsView.vue # 兑换金币
|
||||
│ └── SearchPayeeView.vue # 搜索收款人
|
||||
├── router/index.js # 路由配置
|
||||
└── App.vue # 根组件
|
||||
```
|
||||
24
eslint.config.js
Normal file
24
eslint.config.js
Normal file
@ -0,0 +1,24 @@
|
||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||
import globals from 'globals'
|
||||
import js from '@eslint/js'
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
name: 'app/files-to-lint',
|
||||
files: ['**/*.{js,mjs,jsx,vue}'],
|
||||
},
|
||||
|
||||
globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
|
||||
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
js.configs.recommended,
|
||||
...pluginVue.configs['flat/essential'],
|
||||
])
|
||||
24
index.html
Normal file
24
index.html
Normal file
@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<title>Likei H5</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
8
jsconfig.json
Normal file
8
jsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
3963
package-lock.json
generated
Normal file
3963
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
package.json
Normal file
28
package.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "likei-h5",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint . --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.5.18",
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.31.0",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"eslint": "^9.31.0",
|
||||
"eslint-plugin-vue": "~10.3.0",
|
||||
"globals": "^16.3.0",
|
||||
"vite": "^7.0.6",
|
||||
"vite-plugin-vue-devtools": "^8.0.0"
|
||||
}
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
36
src/App.vue
Normal file
36
src/App.vue
Normal file
@ -0,0 +1,36 @@
|
||||
<script setup>
|
||||
import { RouterView } from 'vue-router'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="app">
|
||||
<RouterView />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#app {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
64
src/api/wallet.js
Normal file
64
src/api/wallet.js
Normal file
@ -0,0 +1,64 @@
|
||||
import { get } from '../utils/http.js'
|
||||
|
||||
/**
|
||||
* 获取钱包流水详情
|
||||
* @returns {Promise} 返回流水详情数据
|
||||
*/
|
||||
export function getWalletDetails() {
|
||||
return get('/wallet/salary-diamond/balance/water/details')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取银行余额
|
||||
* @returns {Promise} 返回银行余额数据
|
||||
*/
|
||||
export function getBankBalance() {
|
||||
return get('/wallet/bank/balance')
|
||||
}
|
||||
|
||||
/**
|
||||
* 转账角色检查
|
||||
* @returns {Promise} 返回用户角色和权限信息
|
||||
*/
|
||||
export function userBankCheckTransfer() {
|
||||
return get('/wallet/bank/check/transfer')
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索用户资料
|
||||
* @param {string} type - 用户类型 (AGENT/BD/ANCHOR)
|
||||
* @param {string} account - 用户账号/ID
|
||||
* @returns {Promise} 返回用户资料
|
||||
*/
|
||||
export function userBankSearchUserProfile(type, account) {
|
||||
return get(`/wallet/bank/search/user-profile?type=${type}&account=${account}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时间戳为可读格式
|
||||
* @param {number} timestamp - 时间戳(毫秒)
|
||||
* @returns {string} 格式化后的时间字符串
|
||||
*/
|
||||
export function formatDateTime(timestamp) {
|
||||
const date = new Date(timestamp)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
|
||||
return `${day}/${month}/${year} ${hours}:${minutes}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化事件描述
|
||||
* @param {string} eventDescribe - 原始事件描述
|
||||
* @returns {string} 格式化后的事件描述
|
||||
*/
|
||||
export function formatEventDescribe(eventDescribe) {
|
||||
// 移除 [system] 前缀并简化描述
|
||||
return eventDescribe
|
||||
.replace(/^\[system\]\s*/i, '[System] ')
|
||||
.replace(/Settlement Agent Salary/i, 'Create An Account')
|
||||
.replace(/Settlement Member salary/i, 'Transfer')
|
||||
}
|
||||
86
src/assets/base.css
Normal file
86
src/assets/base.css
Normal file
@ -0,0 +1,86 @@
|
||||
/* color palette from <https://github.com/vuejs/theme> */
|
||||
:root {
|
||||
--vt-c-white: #ffffff;
|
||||
--vt-c-white-soft: #f8f8f8;
|
||||
--vt-c-white-mute: #f2f2f2;
|
||||
|
||||
--vt-c-black: #181818;
|
||||
--vt-c-black-soft: #222222;
|
||||
--vt-c-black-mute: #282828;
|
||||
|
||||
--vt-c-indigo: #2c3e50;
|
||||
|
||||
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||
|
||||
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||
--vt-c-text-dark-1: var(--vt-c-white);
|
||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||
}
|
||||
|
||||
/* semantic color variables for this project */
|
||||
:root {
|
||||
--color-background: var(--vt-c-white);
|
||||
--color-background-soft: var(--vt-c-white-soft);
|
||||
--color-background-mute: var(--vt-c-white-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-light-2);
|
||||
--color-border-hover: var(--vt-c-divider-light-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-light-1);
|
||||
--color-text: var(--vt-c-text-light-1);
|
||||
|
||||
--section-gap: 160px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-background: var(--vt-c-black);
|
||||
--color-background-soft: var(--vt-c-black-soft);
|
||||
--color-background-mute: var(--vt-c-black-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-dark-2);
|
||||
--color-border-hover: var(--vt-c-divider-dark-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-dark-1);
|
||||
--color-text: var(--vt-c-text-dark-2);
|
||||
}
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
color: var(--color-text);
|
||||
background: var(--color-background);
|
||||
transition:
|
||||
color 0.5s,
|
||||
background-color 0.5s;
|
||||
line-height: 1.6;
|
||||
font-family:
|
||||
Inter,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
Oxygen,
|
||||
Ubuntu,
|
||||
Cantarell,
|
||||
'Fira Sans',
|
||||
'Droid Sans',
|
||||
'Helvetica Neue',
|
||||
sans-serif;
|
||||
font-size: 15px;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
120
src/assets/gradient-background.css
Normal file
120
src/assets/gradient-background.css
Normal file
@ -0,0 +1,120 @@
|
||||
/* 全局渐变背景样式 */
|
||||
.gradient-background {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 备选渐变背景(更柔和的紫色渐变) */
|
||||
.gradient-background-soft {
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 50%, #4facfe 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 淡紫色渐变背景(接近图片效果) */
|
||||
.gradient-background-light {
|
||||
background: linear-gradient(180deg, #e8d5ff 0%, #f5f1ff 50%, #ffffff 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 圆圈晕染效果背景 */
|
||||
.gradient-background-circles {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(180deg, #e8d5ff 0%, #f5f1ff 35%, #ffffff 100%);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 左上角紫色大圆 */
|
||||
.gradient-background-circles::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -40%;
|
||||
left: -30%;
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(139, 92, 246, 0.35) 0%,
|
||||
rgba(139, 92, 246, 0.2) 25%,
|
||||
rgba(139, 92, 246, 0.1) 45%,
|
||||
rgba(139, 92, 246, 0.05) 65%,
|
||||
transparent 85%
|
||||
);
|
||||
border-radius: 50%;
|
||||
filter: blur(60px);
|
||||
z-index: 0;
|
||||
animation: slowPulse 12s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* 右上角蓝色大圆 */
|
||||
.gradient-background-circles::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -10%;
|
||||
right: -35%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(59, 130, 246, 0.3) 0%,
|
||||
rgba(59, 130, 246, 0.15) 30%,
|
||||
rgba(59, 130, 246, 0.08) 50%,
|
||||
rgba(59, 130, 246, 0.03) 70%,
|
||||
transparent 90%
|
||||
);
|
||||
border-radius: 50%;
|
||||
filter: blur(80px);
|
||||
z-index: 0;
|
||||
animation: rotate 20s linear infinite;
|
||||
}
|
||||
|
||||
|
||||
/* 动画效果 */
|
||||
@keyframes slowPulse {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
transform: rotate(0deg) scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: rotate(180deg) scale(1.03);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translateY(0px) scale(1);
|
||||
opacity: 0.8;
|
||||
}
|
||||
33% {
|
||||
transform: translateY(-15px) scale(1.02);
|
||||
opacity: 1;
|
||||
}
|
||||
66% {
|
||||
transform: translateY(10px) scale(0.98);
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
/* 确保内容在圆圈效果之上 */
|
||||
.gradient-background-circles > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.gradient-background-circles .content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
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 |
36
src/assets/main.css
Normal file
36
src/assets/main.css
Normal file
@ -0,0 +1,36 @@
|
||||
@import './base.css';
|
||||
@import './gradient-background.css';
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
a,
|
||||
.green {
|
||||
text-decoration: none;
|
||||
color: hsla(160, 100%, 37%, 1);
|
||||
transition: 0.4s;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
a:hover {
|
||||
background-color: hsla(160, 100%, 37%, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
body {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
}
|
||||
44
src/components/HelloWorld.vue
Normal file
44
src/components/HelloWorld.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
msg: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
</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>.
|
||||
</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>
|
||||
87
src/components/MobileHeader.vue
Normal file
87
src/components/MobileHeader.vue
Normal file
@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<div class="mobile-header">
|
||||
<button v-if="showBack" class="back-btn" @click="goBack">
|
||||
<span>←</span>
|
||||
</button>
|
||||
<h1 class="title">{{ title }}</h1>
|
||||
<button v-if="showHelp" class="help-btn" @click="$emit('help')">
|
||||
<span>?</span>
|
||||
</button>
|
||||
<div v-else class="placeholder"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
// 定义props
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
showBack: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showHelp: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
// 定义emits
|
||||
defineEmits(['help'])
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const goBack = () => {
|
||||
router.go(-1)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mobile-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
background-color: white;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.back-btn, .help-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
background: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.back-btn:hover, .help-btn:hover {
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
</style>
|
||||
17
src/components/PageContainer.vue
Normal file
17
src/components/PageContainer.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 页面容器组件,提供统一的背景
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-container {
|
||||
background: linear-gradient(180deg, #e8d5ff 0%, #f5f1ff 50%, #ffffff 100%);
|
||||
min-height: 100vh;
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
</style>
|
||||
94
src/components/TheWelcome.vue
Normal file
94
src/components/TheWelcome.vue
Normal file
@ -0,0 +1,94 @@
|
||||
<script setup>
|
||||
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>
|
||||
86
src/components/WelcomeItem.vue
Normal file
86
src/components/WelcomeItem.vue
Normal file
@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<div class="item">
|
||||
<i>
|
||||
<slot name="icon"></slot>
|
||||
</i>
|
||||
<div class="details">
|
||||
<h3>
|
||||
<slot name="heading"></slot>
|
||||
</h3>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.item {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
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>
|
||||
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>
|
||||
11
src/main.js
Normal file
11
src/main.js
Normal file
@ -0,0 +1,11 @@
|
||||
import './assets/main.css'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(router)
|
||||
|
||||
app.mount('#app')
|
||||
105
src/router/index.js
Normal file
105
src/router/index.js
Normal file
@ -0,0 +1,105 @@
|
||||
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: '/about',
|
||||
name: 'about',
|
||||
component: () => import('../views/AboutView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/apply',
|
||||
name: 'apply',
|
||||
component: () => import('../views/ApplyView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/host-center',
|
||||
name: 'host-center',
|
||||
component: () => import('../views/HostCenterView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/host-setting',
|
||||
name: 'host-setting',
|
||||
component: () => import('../views/HostSettingView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/history-salary',
|
||||
name: 'history-salary',
|
||||
component: () => import('../views/HistorySalaryView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/transfer',
|
||||
name: 'transfer',
|
||||
component: () => import('../views/TransferView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/search-payee',
|
||||
name: 'search-payee',
|
||||
component: () => import('../views/SearchPayeeView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/information-details',
|
||||
name: 'information-details',
|
||||
component: () => import('../views/InformationDetailsView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/message',
|
||||
name: 'message',
|
||||
component: () => import('../views/MessageView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/invite-members',
|
||||
name: 'invite-members',
|
||||
component: () => import('../views/InviteMembersView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/team-bill',
|
||||
name: 'team-bill',
|
||||
component: () => import('../views/TeamBillView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/team-member',
|
||||
name: 'team-member',
|
||||
component: () => import('../views/TeamMemberView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/platform-policy',
|
||||
name: 'platform-policy',
|
||||
component: () => import('../views/PlatformPolicyView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/coin-seller',
|
||||
name: 'coin-seller',
|
||||
component: () => import('../views/CoinSellerView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/coin-seller-search',
|
||||
name: 'coin-seller-search',
|
||||
component: () => import('../views/CoinSellerSearchView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/seller-records',
|
||||
name: 'seller-records',
|
||||
component: () => import('../views/SellerRecordsView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/exchange-gold-coins',
|
||||
name: 'exchange-gold-coins',
|
||||
component: () => import('../views/ExchangeGoldCoinsView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/agency-center',
|
||||
name: 'agency-center',
|
||||
component: () => import('../views/AgencyCenterView.vue'),
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export default router
|
||||
27
src/utils/coinSellerStore.js
Normal file
27
src/utils/coinSellerStore.js
Normal file
@ -0,0 +1,27 @@
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
// Coin Seller 全局状态管理
|
||||
const coinSellerStore = reactive({
|
||||
selectedUser: {
|
||||
id: '',
|
||||
name: ''
|
||||
}
|
||||
})
|
||||
|
||||
// 设置选中的用户
|
||||
export function setSelectedUser(user) {
|
||||
coinSellerStore.selectedUser = user
|
||||
}
|
||||
|
||||
// 获取选中的用户
|
||||
export function getSelectedUser() {
|
||||
return coinSellerStore.selectedUser
|
||||
}
|
||||
|
||||
// 清除选中的用户
|
||||
export function clearSelectedUser() {
|
||||
coinSellerStore.selectedUser = {
|
||||
id: '',
|
||||
name: ''
|
||||
}
|
||||
}
|
||||
111
src/utils/http.js
Normal file
111
src/utils/http.js
Normal file
@ -0,0 +1,111 @@
|
||||
// HTTP请求配置
|
||||
const API_BASE_URL = 'https://api.likeichat.com'
|
||||
|
||||
// 公共请求头配置
|
||||
const COMMON_HEADERS = {
|
||||
'authorization': 'Bearer 1850459451220E4F1FABF65F36A43E76.djIlM0ExOTU0MDU5NzkzOTYzMzkzMDI1JTNBTElLRUklM0ExNzU3NjQ4ODMwMjM3JTNBMTc1NTA1NjgzMDIzNw==',
|
||||
'req-app-intel': 'version=1.0.0;build=1;model=SM-G9550;sysVersion=9;channel=Google',
|
||||
'req-client': 'Android',
|
||||
'req-imei': '8fa54d728ab449e04f9292329ed44bda',
|
||||
'req-lang': 'id-ID',
|
||||
'req-sys-origin': 'origin=LIKEI;originChild=LIKEI',
|
||||
'req-version': 'V2',
|
||||
'req-zone': 'Asia/Shanghai',
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用HTTP请求函数
|
||||
* @param {string} url - 请求URL
|
||||
* @param {object} options - 请求选项
|
||||
* @returns {Promise} - 返回Promise对象
|
||||
*/
|
||||
export async function request(url, options = {}) {
|
||||
const {
|
||||
method = 'GET',
|
||||
data = null,
|
||||
headers = {},
|
||||
...otherOptions
|
||||
} = options
|
||||
|
||||
const fullUrl = url.startsWith('http') ? url : `${API_BASE_URL}${url}`
|
||||
|
||||
const config = {
|
||||
method,
|
||||
headers: {
|
||||
...COMMON_HEADERS,
|
||||
...headers
|
||||
},
|
||||
...otherOptions
|
||||
}
|
||||
|
||||
// 如果是POST、PUT等方法且有数据,添加body
|
||||
if (data && ['POST', 'PUT', 'PATCH'].includes(method.toUpperCase())) {
|
||||
config.body = JSON.stringify(data)
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('🚀 API Request:', fullUrl, config)
|
||||
|
||||
const response = await fetch(fullUrl, config)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP Error: ${response.status} ${response.statusText}`)
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
console.log('✅ API Response:', result)
|
||||
|
||||
// 检查业务状态
|
||||
if (result.status === false) {
|
||||
throw new Error(result.message || `API Error: ${result.errorCode}`)
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error('❌ API Error:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET请求
|
||||
* @param {string} url - 请求URL
|
||||
* @param {object} options - 请求选项
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function get(url, options = {}) {
|
||||
return request(url, { ...options, method: 'GET' })
|
||||
}
|
||||
|
||||
/**
|
||||
* POST请求
|
||||
* @param {string} url - 请求URL
|
||||
* @param {object} data - 请求数据
|
||||
* @param {object} options - 请求选项
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function post(url, data, options = {}) {
|
||||
return request(url, { ...options, method: 'POST', data })
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT请求
|
||||
* @param {string} url - 请求URL
|
||||
* @param {object} data - 请求数据
|
||||
* @param {object} options - 请求选项
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function put(url, data, options = {}) {
|
||||
return request(url, { ...options, method: 'PUT', data })
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE请求
|
||||
* @param {string} url - 请求URL
|
||||
* @param {object} options - 请求选项
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function del(url, options = {}) {
|
||||
return request(url, { ...options, method: 'DELETE' })
|
||||
}
|
||||
21
src/utils/payeeStore.js
Normal file
21
src/utils/payeeStore.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
// 全局状态管理
|
||||
const selectedPayeeStore = reactive({
|
||||
payee: null
|
||||
})
|
||||
|
||||
// 设置选中的收款人
|
||||
export function setSelectedPayee(payee) {
|
||||
selectedPayeeStore.payee = payee
|
||||
}
|
||||
|
||||
// 获取选中的收款人
|
||||
export function getSelectedPayee() {
|
||||
return selectedPayeeStore.payee
|
||||
}
|
||||
|
||||
// 清除选中的收款人
|
||||
export function clearSelectedPayee() {
|
||||
selectedPayeeStore.payee = null
|
||||
}
|
||||
27
src/views/AboutView.vue
Normal file
27
src/views/AboutView.vue
Normal file
@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<div class="about gradient-background-circles">
|
||||
<h1>This is an about page</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.about {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.about h1 {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.about {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
753
src/views/AgencyCenterView.vue
Normal file
753
src/views/AgencyCenterView.vue
Normal file
@ -0,0 +1,753 @@
|
||||
<template>
|
||||
<div class="agency-center gradient-background-circles">
|
||||
<MobileHeader title="Host Center" />
|
||||
|
||||
<div class="content">
|
||||
<!-- 用户信息卡片 -->
|
||||
<div class="user-card">
|
||||
<div class="user-info">
|
||||
<div class="avatar">
|
||||
<span>{{ userInfo.name.charAt(0) }}</span>
|
||||
</div>
|
||||
<div class="user-details">
|
||||
<h3>{{ userInfo.name }}</h3>
|
||||
<p>ID: {{ userInfo.id }}</p>
|
||||
</div>
|
||||
<button class="notification-btn" @click="goToNotification">
|
||||
<span>🔔</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 身份切换标签 -->
|
||||
<div class="identity-tabs">
|
||||
<button
|
||||
v-for="identity in identities"
|
||||
:key="identity.value"
|
||||
@click="handleIdentityChange(identity.value)"
|
||||
:class="['identity-tab', { active: currentIdentity === identity.value }]"
|
||||
>
|
||||
{{ identity.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 账户状态标签 -->
|
||||
<div class="account-tags">
|
||||
<span class="tag agency-tag">🏢 Agency</span>
|
||||
<span class="tag salary-tag">💰 Salary</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Host Agency 标签页 -->
|
||||
<div class="host-agency-tabs">
|
||||
<button
|
||||
@click="taskTab = 'host'"
|
||||
:class="['host-agency-tab', { active: taskTab === 'host' }]"
|
||||
>
|
||||
Host
|
||||
</button>
|
||||
<button
|
||||
@click="taskTab = 'agency'"
|
||||
:class="['host-agency-tab', { active: taskTab === 'agency' }]"
|
||||
>
|
||||
Agency
|
||||
<button v-if="taskTab === 'agency'" class="help-btn" @click.stop="goToInviteMembers">
|
||||
?
|
||||
</button>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- My salary 区域 -->
|
||||
<div class="salary-section">
|
||||
<div class="salary-header">
|
||||
<h3>My salary:</h3>
|
||||
<button class="personal-salary-btn" @click="goToPersonalSalary">
|
||||
Personal Salary <span class="arrow">></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="salary-content">
|
||||
<div class="salary-item">
|
||||
<p class="salary-label">History Salary:</p>
|
||||
<p class="salary-amount">${{ salaryInfo.historySalary }}</p>
|
||||
</div>
|
||||
<div class="salary-separator"></div>
|
||||
<div class="salary-item">
|
||||
<p class="salary-label">Current Salary:</p>
|
||||
<p class="salary-amount">${{ salaryInfo.currentSalary }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Host Agency 内容 -->
|
||||
<div class="host-agency-content">
|
||||
<!-- Host 任务内容 -->
|
||||
<div v-if="taskTab === 'host'" class="task-content">
|
||||
<h3>Today's Task:</h3>
|
||||
<!-- 任务表格 -->
|
||||
<div class="task-table">
|
||||
<div class="table-row table-header">
|
||||
<div class="table-cell">Anchor type</div>
|
||||
<div class="table-cell">Minutes</div>
|
||||
<div class="table-cell">Gift Task</div>
|
||||
</div>
|
||||
<div class="table-row table-data">
|
||||
<div class="table-cell">{{ taskInfo.anchorType }}</div>
|
||||
<div class="table-cell">
|
||||
<span class="minutes-progress">{{ taskInfo.minutesProgress }}</span>
|
||||
<span class="minutes-completed">/{{ taskInfo.minutesTotal }}</span>
|
||||
</div>
|
||||
<div class="table-cell">{{ taskInfo.giftTask }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Platform Policy 链接 -->
|
||||
<!-- <div class="policy-link" @click="goToPlatformPolicy">
|
||||
<span>Platform Policy</span>
|
||||
<span class="arrow">></span>
|
||||
</div>-->
|
||||
</div>
|
||||
|
||||
<!-- Agency 任务内容 -->
|
||||
<div v-else class="agency-task-content">
|
||||
<!-- Team Bill 卡片 -->
|
||||
<div class="team-bill-card" @click="goToTeamBill">
|
||||
<div class="card-content">
|
||||
<div class="card-date">{{ teamBillInfo.date }}</div>
|
||||
<div class="card-link">
|
||||
<span>Team Bill</span>
|
||||
<span class="arrow">></span>
|
||||
</div>
|
||||
<div class="card-details">
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Host Salary:</span>
|
||||
<span class="detail-value">{{ teamBillInfo.hostSalary }}</span>
|
||||
<span class="detail-label">Agent Salary:</span>
|
||||
<span class="detail-value">{{ teamBillInfo.agentSalary }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Total:</span>
|
||||
<span class="detail-value">{{ teamBillInfo.total }}</span>
|
||||
<span class="status-badge">{{ teamBillInfo.status }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Team Member 卡片(独立在外面) -->
|
||||
<div class="team-member-section">
|
||||
<div class="team-member-card" @click="goToTeamMember">
|
||||
<div class="card-content">
|
||||
<span class="member-title">Team Member ({{ teamMemberCount }})</span>
|
||||
<span class="arrow">></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="action-buttons">
|
||||
<button class="action-btn exchange-btn" @click="exchangeGoldCoins">
|
||||
Exchange<br>Gold Coins
|
||||
</button>
|
||||
<button class="action-btn transfer-btn" @click="transfer">
|
||||
Transfer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
// 当前身份
|
||||
const currentIdentity = ref('agency')
|
||||
|
||||
// 任务标签
|
||||
const taskTab = ref('agency')
|
||||
|
||||
// 团队成员数量
|
||||
const teamMemberCount = ref(87)
|
||||
|
||||
// 薪资信息
|
||||
const salaryInfo = reactive({
|
||||
historySalary: 12345,
|
||||
currentSalary: 12345
|
||||
})
|
||||
|
||||
// 身份选项
|
||||
const identities = [
|
||||
{ label: 'Host', value: 'host' },
|
||||
{ label: 'Agency', value: 'agency' },
|
||||
{ label: 'Coin Seller', value: 'coin-seller' }
|
||||
]
|
||||
|
||||
// 用户信息
|
||||
const userInfo = reactive({
|
||||
name: 'User1',
|
||||
id: '1234567890'
|
||||
})
|
||||
|
||||
// 任务信息
|
||||
const taskInfo = reactive({
|
||||
anchorType: 'Chat',
|
||||
minutesProgress: '120',
|
||||
minutesTotal: '120',
|
||||
giftTask: 0
|
||||
})
|
||||
|
||||
// Team Bill 信息
|
||||
const teamBillInfo = reactive({
|
||||
date: '2025.08',
|
||||
hostSalary: '–',
|
||||
agentSalary: '–',
|
||||
total: '–',
|
||||
status: 'Pending'
|
||||
})
|
||||
|
||||
// 身份切换处理
|
||||
const handleIdentityChange = (identity) => {
|
||||
switch (identity) {
|
||||
case 'host':
|
||||
router.push('/host-center')
|
||||
break
|
||||
case 'coin-seller':
|
||||
router.push('/coin-seller')
|
||||
break
|
||||
case 'agency':
|
||||
default:
|
||||
// 保持在当前页面
|
||||
currentIdentity.value = identity
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 页面跳转方法
|
||||
const goToNotification = () => {
|
||||
router.push('/message')
|
||||
}
|
||||
|
||||
const goToInviteMembers = () => {
|
||||
router.push('/invite-members')
|
||||
}
|
||||
|
||||
const goToTeamBill = () => {
|
||||
router.push('/team-bill')
|
||||
}
|
||||
|
||||
const goToTeamMember = () => {
|
||||
router.push('/team-member')
|
||||
}
|
||||
|
||||
const goToPlatformPolicy = () => {
|
||||
router.push('/platform-policy')
|
||||
}
|
||||
|
||||
const exchangeGoldCoins = () => {
|
||||
router.push('/exchange-gold-coins')
|
||||
}
|
||||
|
||||
const transfer = () => {
|
||||
router.push('/transfer')
|
||||
}
|
||||
|
||||
const goToPersonalSalary = () => {
|
||||
router.push('/platform-policy')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.agency-center {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 用户信息卡片 */
|
||||
.user-card {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
background-color: #8B5CF6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.user-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-details h3 {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.user-details p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.notification-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 身份切换标签 */
|
||||
.identity-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.identity-tab {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 16px;
|
||||
background-color: white;
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.identity-tab.active {
|
||||
background-color: #8B5CF6;
|
||||
color: white;
|
||||
border-color: #8B5CF6;
|
||||
}
|
||||
|
||||
.identity-tab:not(.active):hover {
|
||||
border-color: #8B5CF6;
|
||||
color: #8B5CF6;
|
||||
}
|
||||
|
||||
.account-tags {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding: 4px 12px;
|
||||
border-radius: 16px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.agency-tag {
|
||||
background-color: #DBEAFE;
|
||||
color: #1E40AF;
|
||||
}
|
||||
|
||||
.salary-tag {
|
||||
background-color: #FEF3C7;
|
||||
color: #D97706;
|
||||
}
|
||||
|
||||
/* Host Agency 标签页 */
|
||||
.host-agency-tabs {
|
||||
display: flex;
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.host-agency-tab {
|
||||
flex: 1;
|
||||
padding: 12px 16px;
|
||||
border: none;
|
||||
background-color: #f8f9fa;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
border-bottom: 3px solid transparent;
|
||||
}
|
||||
|
||||
.host-agency-tab.active {
|
||||
background-color: white;
|
||||
color: #333;
|
||||
border-bottom-color: #8B5CF6;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.host-agency-tab:not(.active):hover {
|
||||
background-color: #e9ecef;
|
||||
color: #8B5CF6;
|
||||
}
|
||||
|
||||
.host-agency-tab .help-btn {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 8px;
|
||||
background-color: #8B5CF6;
|
||||
color: white;
|
||||
border: none;
|
||||
font-size: 10px;
|
||||
cursor: pointer;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
/* My salary 区域 */
|
||||
.salary-section {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.salary-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.salary-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.personal-salary-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #8B5CF6;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.personal-salary-btn:hover {
|
||||
color: #7C3AED;
|
||||
}
|
||||
|
||||
.salary-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.salary-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.salary-label {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.salary-amount {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.salary-separator {
|
||||
width: 1px;
|
||||
height: 40px;
|
||||
background-color: #8B5CF6;
|
||||
}
|
||||
|
||||
/* Host Agency 内容区域 */
|
||||
.host-agency-content {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.task-content h3 {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* Team Member 独立区域 */
|
||||
.team-member-section {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.team-member-card {
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.team-member-card:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.team-member-card .card-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 任务表格 */
|
||||
.task-table {
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
background-color: #f8f9fa;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.table-data {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
padding: 12px 8px;
|
||||
text-align: center;
|
||||
border-right: 1px solid #e5e7eb;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.table-cell:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.table-header .table-cell {
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.table-data .table-cell {
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.minutes-progress {
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
.minutes-completed {
|
||||
color: #10B981;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Agency 卡片区域 */
|
||||
.agency-task-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.team-bill-card {
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border: 2px solid #3B82F6;
|
||||
}
|
||||
|
||||
.team-bill-card:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.team-bill-card .card-content {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 通用卡片内容样式 */
|
||||
.card-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.card-date {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.card-link {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
color: #6B7280;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.card-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto auto auto;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-size: 14px;
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
background-color: #DBEAFE;
|
||||
color: #1D4ED8;
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.member-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
/* Platform Policy 链接 */
|
||||
.policy-link {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
margin-top: 12px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.policy-link:hover {
|
||||
background-color: #e9ecef;
|
||||
border-color: #8B5CF6;
|
||||
}
|
||||
|
||||
.policy-link:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.policy-link span:first-child {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 操作按钮 */
|
||||
.action-buttons {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 14px 12px;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
transition: all 0.2s;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action-btn:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.exchange-btn {
|
||||
background-color: #FDE68A;
|
||||
color: #D97706;
|
||||
}
|
||||
|
||||
.transfer-btn {
|
||||
background-color: #8B5CF6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 通用箭头样式 */
|
||||
.arrow {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #8B5CF6;
|
||||
}
|
||||
</style>
|
||||
224
src/views/ApplyView.vue
Normal file
224
src/views/ApplyView.vue
Normal file
@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<div class="apply-page gradient-background-circles">
|
||||
<!-- 顶部导航 -->
|
||||
<MobileHeader
|
||||
title="Apply to join the team"
|
||||
:show-help="true"
|
||||
@help="showHelp"
|
||||
/>
|
||||
|
||||
<!-- 主要内容 -->
|
||||
<div class="content">
|
||||
<!-- 提醒信息 -->
|
||||
<div class="reminder">
|
||||
<h3>Reminder before application:</h3>
|
||||
<p>Enter the ID of your agent, and you will become the host after the agency is approved. After becoming an anchor, your income will be entrusted to your agent</p>
|
||||
</div>
|
||||
|
||||
<!-- 申请表单 -->
|
||||
<div class="apply-form">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Apply ID:</label>
|
||||
<div class="input-group">
|
||||
<input
|
||||
v-model="agentId"
|
||||
type="text"
|
||||
placeholder="Please enter the agent ID"
|
||||
class="form-input"
|
||||
/>
|
||||
<button class="apply-btn" @click="submitApplication" :disabled="!agentId">
|
||||
Apply
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 帮助弹窗 -->
|
||||
<div v-if="showHelpModal" class="modal-overlay" @click="showHelpModal = false">
|
||||
<div class="modal" @click.stop>
|
||||
<h3>Help Information</h3>
|
||||
<p>Please contact your agent to get the correct Agent ID before applying.</p>
|
||||
<button @click="showHelpModal = false">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const agentId = ref('')
|
||||
const showHelpModal = ref(false)
|
||||
|
||||
// 显示帮助
|
||||
const showHelp = () => {
|
||||
showHelpModal.value = true
|
||||
}
|
||||
|
||||
// 提交申请
|
||||
const submitApplication = () => {
|
||||
if (!agentId.value.trim()) {
|
||||
alert('Please enter agent ID')
|
||||
return
|
||||
}
|
||||
|
||||
// 这里可以添加实际的提交逻辑
|
||||
alert(`Application submitted with Agent ID: ${agentId.value}`)
|
||||
// 提交成功后可以跳转到其他页面
|
||||
// router.push('/host-center')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.apply-page {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.reminder {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.reminder h3 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.reminder p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.apply-form {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
flex: 1;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
outline: none;
|
||||
border-color: #007AFF;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.apply-btn {
|
||||
padding: 12px 24px;
|
||||
background-color: #8B5CF6;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.apply-btn:hover:not(:disabled) {
|
||||
background-color: #7C3AED;
|
||||
}
|
||||
|
||||
.apply-btn:disabled {
|
||||
background-color: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.modal {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal h3 {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.modal p {
|
||||
margin: 0 0 20px 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.modal button {
|
||||
padding: 8px 20px;
|
||||
background-color: #007AFF;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 手机适配 */
|
||||
.input-group {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.apply-btn {
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
}
|
||||
</style>
|
||||
373
src/views/CoinSellerSearchView.vue
Normal file
373
src/views/CoinSellerSearchView.vue
Normal file
@ -0,0 +1,373 @@
|
||||
<template>
|
||||
<div class="coin-seller-search gradient-background-circles">
|
||||
<MobileHeader title="Search user" />
|
||||
|
||||
<div class="content">
|
||||
<!-- 搜索框 -->
|
||||
<div class="search-section">
|
||||
<div class="search-box">
|
||||
<span class="search-icon">🔍</span>
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
placeholder="Please enter the host ID"
|
||||
class="search-input"
|
||||
@keyup.enter="performSearch"
|
||||
/>
|
||||
<button
|
||||
v-if="searchQuery"
|
||||
@click="clearSearch"
|
||||
class="clear-btn"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<button
|
||||
class="action-btn"
|
||||
@click="searchQuery ? confirmUser : cancelSearch"
|
||||
>
|
||||
{{ searchQuery ? 'Confirm' : 'Cancel' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索结果 -->
|
||||
<div v-if="searchResults.length > 0" class="results-section">
|
||||
<div
|
||||
v-for="user in searchResults"
|
||||
:key="user.id"
|
||||
class="user-item"
|
||||
@click="selectUser(user)"
|
||||
>
|
||||
<div class="user-info">
|
||||
<div class="user-avatar">
|
||||
<span>{{ user.name.charAt(0) }}</span>
|
||||
</div>
|
||||
<div class="user-details">
|
||||
<h4>{{ user.name }}</h4>
|
||||
<p>ID: {{ user.id }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="add-icon">
|
||||
<span>➕</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="isSearching" class="loading-state">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>Searching...</p>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else-if="searchQuery && !isSearching && searchResults.length === 0" class="empty-state">
|
||||
<div class="empty-icon">🔍</div>
|
||||
<p>No users found</p>
|
||||
<span>Try searching with a different ID</span>
|
||||
</div>
|
||||
|
||||
<!-- 搜索提示 -->
|
||||
<div v-else-if="!searchQuery" class="search-hint">
|
||||
<div class="hint-icon">👤</div>
|
||||
<p>Search for users</p>
|
||||
<span>Enter host ID to find users</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
import { setSelectedUser } from '../utils/coinSellerStore.js'
|
||||
|
||||
const router = useRouter()
|
||||
const searchQuery = ref('')
|
||||
const searchResults = ref([])
|
||||
const isSearching = ref(false)
|
||||
|
||||
// 模拟用户数据
|
||||
const mockUsers = [
|
||||
{
|
||||
id: '1234567890',
|
||||
name: 'User1'
|
||||
},
|
||||
{
|
||||
id: '0987654321',
|
||||
name: 'Alice Johnson'
|
||||
},
|
||||
{
|
||||
id: '1122334455',
|
||||
name: 'Bob Smith'
|
||||
}
|
||||
]
|
||||
|
||||
// 执行搜索
|
||||
const performSearch = () => {
|
||||
if (!searchQuery.value.trim()) {
|
||||
searchResults.value = []
|
||||
return
|
||||
}
|
||||
|
||||
isSearching.value = true
|
||||
|
||||
// 模拟搜索延迟
|
||||
setTimeout(() => {
|
||||
const query = searchQuery.value.toLowerCase()
|
||||
searchResults.value = mockUsers.filter(user =>
|
||||
user.name.toLowerCase().includes(query) ||
|
||||
user.id.includes(query)
|
||||
)
|
||||
isSearching.value = false
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// 清除搜索
|
||||
const clearSearch = () => {
|
||||
searchQuery.value = ''
|
||||
searchResults.value = []
|
||||
}
|
||||
|
||||
// 取消搜索
|
||||
const cancelSearch = () => {
|
||||
router.go(-1)
|
||||
}
|
||||
|
||||
// 确认用户
|
||||
const confirmUser = () => {
|
||||
if (searchResults.value.length > 0) {
|
||||
selectUser(searchResults.value[0])
|
||||
} else {
|
||||
performSearch()
|
||||
}
|
||||
}
|
||||
|
||||
// 选择用户
|
||||
const selectUser = (user) => {
|
||||
// 将选中的用户信息保存到全局状态
|
||||
setSelectedUser({
|
||||
id: user.id,
|
||||
name: user.name
|
||||
})
|
||||
|
||||
console.log('Selected user:', user)
|
||||
|
||||
// 返回上一页
|
||||
router.go(-1)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.coin-seller-search {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 搜索部分 */
|
||||
.search-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: white;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
margin-right: 8px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
background: none;
|
||||
outline: none;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 18px;
|
||||
color: #9ca3af;
|
||||
cursor: pointer;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #8B5CF6;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
padding: 4px 8px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.action-btn:active {
|
||||
color: #7C3AED;
|
||||
}
|
||||
|
||||
/* 搜索结果 */
|
||||
.results-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.user-item {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.user-item:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
background-color: #F59E0B;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.user-details h4 {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.user-details p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.add-icon {
|
||||
color: #10B981;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 3px solid #f3f3f3;
|
||||
border-top: 3px solid #F59E0B;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-state p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.empty-state span {
|
||||
font-size: 14px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
/* 搜索提示 */
|
||||
.search-hint {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hint-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-hint p {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.search-hint span {
|
||||
font-size: 14px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
</style>
|
||||
435
src/views/CoinSellerView.vue
Normal file
435
src/views/CoinSellerView.vue
Normal file
@ -0,0 +1,435 @@
|
||||
<template>
|
||||
<div class="coin-seller gradient-background-circles">
|
||||
<MobileHeader title="Coin Seller" />
|
||||
|
||||
<div class="content">
|
||||
<!-- 用户信息卡片 -->
|
||||
<div class="user-card">
|
||||
<div class="user-info">
|
||||
<div class="avatar">
|
||||
<span>{{ userInfo.name.charAt(0) }}</span>
|
||||
</div>
|
||||
<div class="user-details">
|
||||
<h3>{{ userInfo.name }}</h3>
|
||||
<p>ID: {{ userInfo.id }}</p>
|
||||
</div>
|
||||
<button class="settings-btn" @click="goToSettings">
|
||||
<span>⚙️</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 账户状态标签 -->
|
||||
<div class="account-tags">
|
||||
<span class="tag seller-tag">🪙 Seller</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 我的金币 -->
|
||||
<div class="coins-section">
|
||||
<h3>My coins:</h3>
|
||||
<div class="coins-display">
|
||||
<span class="coin-icon">🪙</span>
|
||||
<span class="coins-amount">{{ coinsAmount }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 用户搜索部分 -->
|
||||
<div class="user-section">
|
||||
<h3>User:</h3>
|
||||
<div v-if="selectedUser.id" class="selected-user">
|
||||
<div class="user-info">
|
||||
<div class="user-avatar">
|
||||
<span>{{ selectedUser.name.charAt(0) }}</span>
|
||||
</div>
|
||||
<div class="user-details">
|
||||
<div class="user-name">{{ selectedUser.name }}</div>
|
||||
<div class="user-id">ID: {{ selectedUser.id }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="change-btn" @click="searchUser">Change</button>
|
||||
</div>
|
||||
<div v-else class="search-placeholder" @click="searchUser">
|
||||
<span>Search</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 充值部分 -->
|
||||
<div class="recharge-section">
|
||||
<h3>Recharge:</h3>
|
||||
<div class="recharge-input">
|
||||
<input
|
||||
v-model="rechargeAmount"
|
||||
type="number"
|
||||
placeholder="Please Enter The Amount Of Gold Coins To Transfer"
|
||||
class="amount-input"
|
||||
/>
|
||||
<span class="currency-suffix">0/50</span>
|
||||
</div>
|
||||
<button
|
||||
class="recharge-btn"
|
||||
:disabled="!canRecharge"
|
||||
@click="rechargeNow"
|
||||
>
|
||||
Recharge now
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
import { getSelectedUser, clearSelectedUser } from '../utils/coinSellerStore.js'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
// 用户信息
|
||||
const userInfo = ref({
|
||||
name: 'User1',
|
||||
id: '1234567890'
|
||||
})
|
||||
|
||||
// 金币数量
|
||||
const coinsAmount = ref('123456478')
|
||||
|
||||
// 选中的用户
|
||||
const selectedUser = ref({
|
||||
id: '',
|
||||
name: ''
|
||||
})
|
||||
|
||||
// 初始化用户信息
|
||||
const initializeUser = () => {
|
||||
const savedUser = getSelectedUser()
|
||||
if (savedUser && savedUser.id) {
|
||||
selectedUser.value = { ...savedUser }
|
||||
}
|
||||
}
|
||||
|
||||
// 充值金额
|
||||
const rechargeAmount = ref('')
|
||||
|
||||
// 是否可以充值
|
||||
const canRecharge = computed(() => {
|
||||
return selectedUser.value.id && rechargeAmount.value && parseFloat(rechargeAmount.value) > 0
|
||||
})
|
||||
|
||||
// 跳转到设置
|
||||
const goToSettings = () => {
|
||||
router.push('/seller-records')
|
||||
}
|
||||
|
||||
// 搜索用户
|
||||
const searchUser = () => {
|
||||
router.push('/coin-seller-search')
|
||||
}
|
||||
|
||||
// 立即充值
|
||||
const rechargeNow = () => {
|
||||
if (!canRecharge.value) return
|
||||
|
||||
alert(`Recharge ${rechargeAmount.value} coins to ${selectedUser.value.name}`)
|
||||
|
||||
// 重置表单
|
||||
rechargeAmount.value = ''
|
||||
// 可以选择是否清除用户选择
|
||||
}
|
||||
|
||||
// 页面加载时初始化用户信息
|
||||
onMounted(() => {
|
||||
initializeUser()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.coin-seller {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 用户信息卡片 */
|
||||
.user-card {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
background-color: #F59E0B;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.user-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-details h3 {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.user-details p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.settings-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.account-tags {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding: 4px 12px;
|
||||
border-radius: 16px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.seller-tag {
|
||||
background-color: #FEF3C7;
|
||||
color: #D97706;
|
||||
}
|
||||
|
||||
/* 金币部分 */
|
||||
.coins-section {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.coins-section h3 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.coins-display {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.coin-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.coins-amount {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #F59E0B;
|
||||
}
|
||||
|
||||
/* 用户搜索部分 */
|
||||
.user-section {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.user-section h3 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.selected-user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.selected-user .user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 20px;
|
||||
background-color: #8B5CF6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.selected-user .user-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.user-id {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.change-btn {
|
||||
background-color: #C084FC;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.change-btn:active {
|
||||
background-color: #A855F7;
|
||||
}
|
||||
|
||||
.search-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
border: 2px dashed #ddd;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.search-placeholder:hover {
|
||||
border-color: #F59E0B;
|
||||
background-color: #fef5e7;
|
||||
}
|
||||
|
||||
.search-placeholder span {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 充值部分 */
|
||||
.recharge-section {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.recharge-section h3 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.recharge-input {
|
||||
position: relative;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.amount-input {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
background-color: #f9fafb;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.amount-input:focus {
|
||||
outline: none;
|
||||
border-color: #F59E0B;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.amount-input::placeholder {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.currency-suffix {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.recharge-btn {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
background-color: #8B5CF6;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.recharge-btn:not(:disabled):hover {
|
||||
background-color: #7C3AED;
|
||||
}
|
||||
|
||||
.recharge-btn:not(:disabled):active {
|
||||
background-color: #6D28D9;
|
||||
}
|
||||
|
||||
.recharge-btn:disabled {
|
||||
background-color: #D1D5DB;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
289
src/views/ExchangeGoldCoinsView.vue
Normal file
289
src/views/ExchangeGoldCoinsView.vue
Normal file
@ -0,0 +1,289 @@
|
||||
<template>
|
||||
<div class="exchange-gold-coins gradient-background-circles">
|
||||
<MobileHeader title="Exchange Gold Coins" />
|
||||
|
||||
<div class="content">
|
||||
<!-- 信息部分 -->
|
||||
<div class="info-section">
|
||||
<div class="info-header">
|
||||
<button class="details-btn" @click="showDetails">
|
||||
ℹ️ Details
|
||||
</button>
|
||||
</div>
|
||||
<div class="available-salary">
|
||||
<span>Available salary: </span>
|
||||
<span v-if="loading" class="loading-text">Loading...</span>
|
||||
<span v-else class="amount">{{ availableSalary }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 兑换部分 -->
|
||||
<div class="exchange-section">
|
||||
<h3>Exchange Gold Coins</h3>
|
||||
|
||||
<!-- 金币选择 -->
|
||||
<div class="coins-grid">
|
||||
<div
|
||||
v-for="coin in coinOptions"
|
||||
:key="coin.id"
|
||||
@click="selectCoin(coin)"
|
||||
:class="['coin-item', { selected: selectedCoin === coin.id }]"
|
||||
>
|
||||
<div class="coin-icon">🪙</div>
|
||||
<div class="coin-amount">{{ coin.amount }}</div>
|
||||
<div class="coin-price">${{ coin.price }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 兑换按钮 -->
|
||||
<button
|
||||
class="exchange-btn"
|
||||
@click="exchangeCoins"
|
||||
:disabled="!selectedCoin"
|
||||
>
|
||||
Exchange
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
import { getBankBalance } from '../api/wallet.js'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const availableSalary = ref('0')
|
||||
const selectedCoin = ref(null)
|
||||
const loading = ref(false)
|
||||
|
||||
// 金币选项
|
||||
const coinOptions = reactive([
|
||||
{ id: 1, amount: 10000, price: 1 },
|
||||
{ id: 2, amount: 10000, price: 1 },
|
||||
{ id: 3, amount: 10000, price: 1 },
|
||||
{ id: 4, amount: 10000, price: 1 },
|
||||
{ id: 5, amount: 10000, price: 1 },
|
||||
{ id: 6, amount: 10000, price: 1 }
|
||||
])
|
||||
|
||||
// 获取银行余额
|
||||
const fetchBankBalance = async () => {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
const response = await getBankBalance()
|
||||
|
||||
if (response.status && typeof response.body === 'number') {
|
||||
// 格式化余额显示,保留2位小数
|
||||
availableSalary.value = response.body.toFixed(2)
|
||||
} else {
|
||||
availableSalary.value = '0.00'
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch bank balance:', error)
|
||||
availableSalary.value = '0.00'
|
||||
// 可以显示错误提示
|
||||
alert('Failed to load balance. Please try again.')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 方法
|
||||
const showDetails = () => {
|
||||
router.push('/information-details')
|
||||
}
|
||||
|
||||
const selectCoin = (coin) => {
|
||||
// 单选逻辑:如果已经选中相同的金币,则取消选择;否则选中新的金币
|
||||
if (selectedCoin.value === coin.id) {
|
||||
selectedCoin.value = null
|
||||
} else {
|
||||
selectedCoin.value = coin.id
|
||||
}
|
||||
}
|
||||
|
||||
const exchangeCoins = () => {
|
||||
if (!selectedCoin.value) {
|
||||
alert('Please select a coin amount first')
|
||||
return
|
||||
}
|
||||
|
||||
const selectedCoinData = coinOptions.find(coin => coin.id === selectedCoin.value)
|
||||
|
||||
// 显示确认对话框
|
||||
const confirmExchange = confirm(`Do you want to exchange ${selectedCoinData.amount} coins for $${selectedCoinData.price}?`)
|
||||
|
||||
if (confirmExchange) {
|
||||
alert(`Successfully exchanged ${selectedCoinData.amount} coins for $${selectedCoinData.price}`)
|
||||
|
||||
// 重置选择
|
||||
selectedCoin.value = null
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时获取银行余额
|
||||
onMounted(() => {
|
||||
fetchBankBalance()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.exchange-gold-coins {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 信息部分 */
|
||||
.info-section {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.info-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.details-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #8B5CF6;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.details-btn:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
.details-btn:active {
|
||||
background-color: #e5e7eb;
|
||||
color: #7C3AED;
|
||||
}
|
||||
|
||||
.available-salary {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.available-salary .amount {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.available-salary .loading-text {
|
||||
font-weight: 500;
|
||||
color: #8B5CF6;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* 兑换部分 */
|
||||
.exchange-section {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.exchange-section h3 {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 金币网格 */
|
||||
.coins-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.coin-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16px 8px;
|
||||
border: 2px solid #e5e7eb;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.coin-item:hover {
|
||||
border-color: #8B5CF6;
|
||||
}
|
||||
|
||||
.coin-item.selected {
|
||||
border-color: #8B5CF6;
|
||||
background-color: #F3F4F6;
|
||||
}
|
||||
|
||||
.coin-item:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.coin-icon {
|
||||
font-size: 24px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.coin-amount {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.coin-price {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 兑换按钮 */
|
||||
.exchange-btn {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
background-color: #FCD34D;
|
||||
color: #92400E;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.exchange-btn:disabled {
|
||||
background-color: #ccc;
|
||||
color: #666;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.exchange-btn:not(:disabled):active {
|
||||
background-color: #F59E0B;
|
||||
}
|
||||
</style>
|
||||
362
src/views/HistorySalaryView.vue
Normal file
362
src/views/HistorySalaryView.vue
Normal file
@ -0,0 +1,362 @@
|
||||
<template>
|
||||
<div class="history-salary gradient-background-circles">
|
||||
<MobileHeader title="History Salary" />
|
||||
|
||||
<div class="content">
|
||||
<!-- Host Salary 卡片 -->
|
||||
<div class="host-salary-card">
|
||||
<div class="salary-header">
|
||||
<h3>Host Salary</h3>
|
||||
<button class="info-btn" @click="showSalaryInfo">
|
||||
<span>?</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="salary-amount">${{ totalSalary }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Work Report 部分 -->
|
||||
<div class="work-report-section">
|
||||
<h3>Work report:</h3>
|
||||
|
||||
<div class="report-list">
|
||||
<div
|
||||
v-for="report in workReports"
|
||||
:key="report.id"
|
||||
class="report-item"
|
||||
>
|
||||
<div class="report-header">
|
||||
<span class="report-date">{{ report.date }}</span>
|
||||
<span :class="['status-badge', report.status.toLowerCase().replace(' ', '-')]">
|
||||
{{ report.status }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="report-details">
|
||||
<div class="detail-row">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Target:</span>
|
||||
<span class="detail-value">{{ report.target }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Salary:</span>
|
||||
<span class="detail-value">{{ report.salary }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-row">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Time(Days):</span>
|
||||
<span class="detail-value">{{ report.timeDays }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Diamond:</span>
|
||||
<span class="detail-value">{{ report.diamond }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 薪资信息弹窗 -->
|
||||
<div v-if="showSalaryModal" class="modal-overlay" @click="showSalaryModal = false">
|
||||
<div class="modal" @click.stop>
|
||||
<h3>Salary Information</h3>
|
||||
<div class="salary-breakdown">
|
||||
<div class="breakdown-item">
|
||||
<span>Base Salary:</span>
|
||||
<span>$8,000</span>
|
||||
</div>
|
||||
<div class="breakdown-item">
|
||||
<span>Performance Bonus:</span>
|
||||
<span>$3,000</span>
|
||||
</div>
|
||||
<div class="breakdown-item">
|
||||
<span>Commission:</span>
|
||||
<span>$1,345</span>
|
||||
</div>
|
||||
<div class="breakdown-total">
|
||||
<span>Total:</span>
|
||||
<span>$12,345</span>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="showSalaryModal = false">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
|
||||
const totalSalary = ref('12345')
|
||||
const showSalaryModal = ref(false)
|
||||
|
||||
// 工作报告数据
|
||||
const workReports = reactive([
|
||||
{
|
||||
id: 1,
|
||||
date: '2025.07',
|
||||
status: 'In Progress',
|
||||
target: '-',
|
||||
salary: '-',
|
||||
timeDays: '-',
|
||||
diamond: '-'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
date: '2025.07',
|
||||
status: 'Pending',
|
||||
target: 'A',
|
||||
salary: '$10',
|
||||
timeDays: '20',
|
||||
diamond: '10000'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
date: '2025.07',
|
||||
status: 'Completed',
|
||||
target: 'B',
|
||||
salary: '$50',
|
||||
timeDays: '20',
|
||||
diamond: '50000'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
date: '2025.07',
|
||||
status: 'Out Of Account',
|
||||
target: '-',
|
||||
salary: '-',
|
||||
timeDays: '1',
|
||||
diamond: '1869'
|
||||
}
|
||||
])
|
||||
|
||||
// 显示薪资信息
|
||||
const showSalaryInfo = () => {
|
||||
showSalaryModal.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.history-salary {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* Host Salary 卡片 */
|
||||
.host-salary-card {
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 16px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.salary-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.salary-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.info-btn {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #ddd;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.salary-amount {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Work Report 部分 */
|
||||
.work-report-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.work-report-section h3 {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.report-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 报告项目 */
|
||||
.report-item {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.report-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.report-date {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 6px 16px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-badge.in-progress {
|
||||
background-color: #3B82F6;
|
||||
}
|
||||
|
||||
.status-badge.pending {
|
||||
background-color: #EF4444;
|
||||
}
|
||||
|
||||
.status-badge.completed {
|
||||
background-color: #10B981;
|
||||
}
|
||||
|
||||
.status-badge.out-of-account {
|
||||
background-color: #9CA3AF;
|
||||
}
|
||||
|
||||
/* 报告详情 */
|
||||
.report-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.modal {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 16px;
|
||||
width: 100%;
|
||||
max-width: 320px;
|
||||
}
|
||||
|
||||
.modal h3 {
|
||||
margin: 0 0 20px 0;
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.salary-breakdown {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.breakdown-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.breakdown-total {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 12px 0 8px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
border-top: 2px solid #e0e0e0;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.modal button {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background-color: #3B82F6;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal button:active {
|
||||
background-color: #2563EB;
|
||||
}
|
||||
</style>
|
||||
120
src/views/HomeView.vue
Normal file
120
src/views/HomeView.vue
Normal file
@ -0,0 +1,120 @@
|
||||
<script setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const goToApply = () => {
|
||||
router.push('/apply')
|
||||
}
|
||||
|
||||
const goToHostCenter = () => {
|
||||
router.push('/host-center')
|
||||
}
|
||||
|
||||
const goToHostSetting = () => {
|
||||
router.push('/host-setting')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="gradient-background-circles">
|
||||
<div class="home-content">
|
||||
<h1>Likei H5 App</h1>
|
||||
<p>Welcome to the demo pages</p>
|
||||
|
||||
<div class="nav-buttons">
|
||||
<button @click="goToApply" class="nav-btn apply-btn">
|
||||
<span>📝</span>
|
||||
<span>Apply to Join Team</span>
|
||||
</button>
|
||||
|
||||
<button @click="goToHostCenter" class="nav-btn host-btn">
|
||||
<span>👑</span>
|
||||
<span>Host Center</span>
|
||||
</button>
|
||||
|
||||
<button @click="goToHostSetting" class="nav-btn setting-btn">
|
||||
<span>⚙️</span>
|
||||
<span>Host Setting</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.home-content {
|
||||
padding: 20px 16px;
|
||||
text-align: center;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 0.8rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.nav-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
max-width: 280px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
padding: 16px 24px;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nav-btn:active {
|
||||
transform: translateY(1px);
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.apply-btn {
|
||||
background: linear-gradient(135deg, #8B5CF6, #A855F7);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.host-btn {
|
||||
background: linear-gradient(135deg, #3B82F6, #1D4ED8);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.setting-btn {
|
||||
background: linear-gradient(135deg, #10B981, #059669);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-btn span:first-child {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
</style>
|
||||
614
src/views/HostCenterView.vue
Normal file
614
src/views/HostCenterView.vue
Normal file
@ -0,0 +1,614 @@
|
||||
<template>
|
||||
<div class="host-center gradient-background-circles">
|
||||
<!-- 顶部导航 -->
|
||||
<MobileHeader title="Host Center" />
|
||||
|
||||
<!-- 主要内容 -->
|
||||
<div class="content">
|
||||
<!-- 用户信息卡片 -->
|
||||
<div class="user-card">
|
||||
<div class="user-info">
|
||||
<div class="avatar">
|
||||
<span>{{ userInfo.name.charAt(0) }}</span>
|
||||
</div>
|
||||
<div class="user-details">
|
||||
<h3>{{ userInfo.name }}</h3>
|
||||
<p>ID: {{ userInfo.id }}</p>
|
||||
</div>
|
||||
<button class="notification-btn" @click="goToNotification">
|
||||
<span>✏️</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 身份切换标签 -->
|
||||
<div class="identity-tabs">
|
||||
<button
|
||||
v-for="identity in identities"
|
||||
:key="identity.value"
|
||||
@click="handleIdentityChange(identity.value)"
|
||||
:class="['identity-tab', { active: currentIdentity === identity.value }]"
|
||||
>
|
||||
{{ identity.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 账户状态标签 -->
|
||||
<div class="account-tags">
|
||||
<span class="tag host-tag">👑 Host</span>
|
||||
<span class="tag salary-tag">💰 Salary</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- My salary 区域 -->
|
||||
<div class="salary-section">
|
||||
<div class="salary-header">
|
||||
<h3>My salary:</h3>
|
||||
<button class="personal-salary-btn" @click="showDatePicker">
|
||||
Personal Salary <span class="arrow">></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="salary-content">
|
||||
<div class="salary-item">
|
||||
<p class="salary-label">History Salary:</p>
|
||||
<p class="salary-amount">${{ salaryInfo.hostSalary }}</p>
|
||||
</div>
|
||||
<div class="salary-separator"></div>
|
||||
<div class="salary-item">
|
||||
<p class="salary-label">Current Salary:</p>
|
||||
<p class="salary-amount">${{ salaryInfo.currentSalary }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Today's Task -->
|
||||
<div class="task-section">
|
||||
<h3>Today's Task:</h3>
|
||||
|
||||
<!-- 任务表格 -->
|
||||
<div class="task-table">
|
||||
<div class="table-row table-header">
|
||||
<div class="table-cell">Anchor type</div>
|
||||
<div class="table-cell">Minutes</div>
|
||||
<div class="table-cell">Gift Task</div>
|
||||
</div>
|
||||
<div class="table-row table-data">
|
||||
<div class="table-cell">{{ taskInfo.anchorType }}</div>
|
||||
<div class="table-cell">
|
||||
<span class="minutes-completed">{{ taskInfo.minutes }}</span>
|
||||
</div>
|
||||
<div class="table-cell">{{ taskInfo.giftTask }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Platform Policy 链接 -->
|
||||
<!-- <div class="policy-link" @click="goToPlatformPolicy">
|
||||
<span>Platform Policy</span>
|
||||
<span class="arrow">></span>
|
||||
</div>-->
|
||||
</div>
|
||||
|
||||
<!-- 进度信息 -->
|
||||
<div class="progress-section">
|
||||
<div class="progress-header">
|
||||
<span class="progress-date">{{ progressInfo.date }}</span>
|
||||
<span class="progress-status">{{ progressInfo.status }}</span>
|
||||
</div>
|
||||
|
||||
<div class="progress-details">
|
||||
<div class="progress-item">
|
||||
<span>Target: {{ progressInfo.target }}</span>
|
||||
<span>Salary: ${{ progressInfo.salary }}</span>
|
||||
</div>
|
||||
<div class="progress-item">
|
||||
<span>Time(Days): {{ progressInfo.timeDays }}</span>
|
||||
<span>Diamond: {{ progressInfo.diamond }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="action-buttons">
|
||||
<button class="action-btn exchange-btn" @click="exchangeGoldCoins">
|
||||
Exchange<br>Gold Coins
|
||||
</button>
|
||||
<button class="action-btn transfer-btn" @click="transfer">
|
||||
Transfer
|
||||
</button>
|
||||
<!--
|
||||
<button class="action-btn withdraw-btn" @click="cashWithdraw">
|
||||
Cash<br>Withdraw
|
||||
</button>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
// 当前身份
|
||||
const currentIdentity = ref('host')
|
||||
|
||||
// 身份选项
|
||||
const identities = [
|
||||
{ label: 'Host', value: 'host' },
|
||||
{ label: 'Agency', value: 'agency' },
|
||||
{ label: 'Coin Seller', value: 'coin-seller' }
|
||||
]
|
||||
|
||||
// 用户信息
|
||||
const userInfo = reactive({
|
||||
name: 'User1',
|
||||
id: '1234567890'
|
||||
})
|
||||
|
||||
// 收入信息
|
||||
const salaryInfo = reactive({
|
||||
hostSalary: 12345,
|
||||
currentSalary: 111
|
||||
})
|
||||
|
||||
// 任务信息
|
||||
const taskInfo = reactive({
|
||||
anchorType: 'Chat',
|
||||
minutes: '0/120',
|
||||
giftTask: 0
|
||||
})
|
||||
|
||||
// 进度信息
|
||||
const progressInfo = reactive({
|
||||
date: '2025.08',
|
||||
status: 'Pending',
|
||||
target: 'B',
|
||||
salary: 50,
|
||||
timeDays: 20,
|
||||
diamond: 50000,
|
||||
hostSalary: '–',
|
||||
agentSalary: '–',
|
||||
total: '–'
|
||||
})
|
||||
|
||||
const showDatePicker = () => {
|
||||
router.push('/history-salary')
|
||||
}
|
||||
|
||||
const goToNotification = () => {
|
||||
router.push('/host-setting')
|
||||
}
|
||||
|
||||
// 监听身份变化
|
||||
const handleIdentityChange = (identity) => {
|
||||
switch (identity) {
|
||||
case 'agency':
|
||||
router.push('/agency-center')
|
||||
break
|
||||
case 'coin-seller':
|
||||
router.push('/coin-seller')
|
||||
break
|
||||
case 'host':
|
||||
default:
|
||||
// 保持在当前页面
|
||||
currentIdentity.value = identity
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const exchangeGoldCoins = () => {
|
||||
router.push('/exchange-gold-coins')
|
||||
}
|
||||
|
||||
const transfer = () => {
|
||||
router.push('/transfer')
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.host-center {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 用户信息卡片 */
|
||||
.user-card {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
background-color: #8B5CF6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.user-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-details h3 {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.user-details p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.notification-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 身份切换标签 */
|
||||
.identity-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.identity-tab {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 16px;
|
||||
background-color: white;
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.identity-tab.active {
|
||||
background-color: #8B5CF6;
|
||||
color: white;
|
||||
border-color: #8B5CF6;
|
||||
}
|
||||
|
||||
.identity-tab:not(.active):hover {
|
||||
border-color: #8B5CF6;
|
||||
color: #8B5CF6;
|
||||
}
|
||||
|
||||
.account-tags {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding: 4px 12px;
|
||||
border-radius: 16px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.host-tag {
|
||||
background-color: #E0E7FF;
|
||||
color: #5B21B6;
|
||||
}
|
||||
|
||||
.agency-tag {
|
||||
background-color: #DBEAFE;
|
||||
color: #1E40AF;
|
||||
}
|
||||
|
||||
.coin-tag {
|
||||
background-color: #FEF3C7;
|
||||
color: #D97706;
|
||||
}
|
||||
|
||||
.salary-tag {
|
||||
background-color: #FEF3C7;
|
||||
color: #D97706;
|
||||
}
|
||||
|
||||
/* My salary 区域 */
|
||||
.salary-section {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.salary-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.salary-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.personal-salary-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #8B5CF6;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.personal-salary-btn:hover {
|
||||
color: #7C3AED;
|
||||
}
|
||||
|
||||
.salary-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.salary-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.salary-label {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.salary-amount {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.salary-separator {
|
||||
width: 1px;
|
||||
height: 40px;
|
||||
background-color: #8B5CF6;
|
||||
}
|
||||
|
||||
/* Today's Task 区域 */
|
||||
.task-section {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.task-section h3 {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 任务表格 */
|
||||
.task-table {
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
background-color: #f8f9fa;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.table-data {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
padding: 12px 8px;
|
||||
text-align: center;
|
||||
border-right: 1px solid #e5e7eb;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.table-cell:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.table-header .table-cell {
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.table-data .table-cell {
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.minutes-progress {
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
.minutes-completed {
|
||||
color: #10B981;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Platform Policy 链接 */
|
||||
.policy-link {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
margin-top: 12px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.policy-link:hover {
|
||||
background-color: #e9ecef;
|
||||
border-color: #8B5CF6;
|
||||
}
|
||||
|
||||
.policy-link:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.policy-link span:first-child {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 进度信息 */
|
||||
.progress-section {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.progress-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.progress-date {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.progress-status {
|
||||
background-color: #3B82F6;
|
||||
color: white;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.progress-details {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.progress-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.progress-item span {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.action-buttons {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 14px 12px;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
transition: all 0.2s;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action-btn:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.exchange-btn {
|
||||
background-color: #FDE68A;
|
||||
color: #D97706;
|
||||
}
|
||||
|
||||
.transfer-btn {
|
||||
background-color: #8B5CF6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.withdraw-btn {
|
||||
background-color: #10B981;
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 通用箭头样式 */
|
||||
.arrow {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #8B5CF6;
|
||||
}
|
||||
</style>
|
||||
149
src/views/HostSettingView.vue
Normal file
149
src/views/HostSettingView.vue
Normal file
@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<div class="host-setting gradient-background-circles">
|
||||
<MobileHeader title="Host Setting" />
|
||||
|
||||
<div class="content">
|
||||
<!-- My Agent 部分 -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<h3>My Agent</h3>
|
||||
<span class="agency-tag">🏢 Agency</span>
|
||||
</div>
|
||||
|
||||
<div class="agent-card">
|
||||
<div class="agent-info">
|
||||
<div class="avatar">
|
||||
<span>{{ userInfo.name.charAt(0) }}</span>
|
||||
</div>
|
||||
<div class="agent-details">
|
||||
<h4>{{ userInfo.name }}</h4>
|
||||
<p>ID: {{ userInfo.id }}</p>
|
||||
</div>
|
||||
<button class="leave-btn" @click="leaveAgent">Leave</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
// 用户信息
|
||||
const userInfo = reactive({
|
||||
name: 'User1',
|
||||
id: '1234567890'
|
||||
})
|
||||
|
||||
// 方法
|
||||
const leaveAgent = () => {
|
||||
if (confirm('Are you sure you want to leave this agent?')) {
|
||||
alert('Leave agent request submitted')
|
||||
// 这里可以添加实际的离开代理商逻辑
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.host-setting {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.section-header h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.agency-tag {
|
||||
background-color: #DBEAFE;
|
||||
color: #1E40AF;
|
||||
padding: 4px 12px;
|
||||
border-radius: 16px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Agent Card */
|
||||
.agent-card {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.agent-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
background-color: #8B5CF6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.agent-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.agent-details h4 {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.agent-details p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.leave-btn {
|
||||
background-color: #EF4444;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.leave-btn:active {
|
||||
background-color: #DC2626;
|
||||
}
|
||||
</style>
|
||||
241
src/views/InformationDetailsView.vue
Normal file
241
src/views/InformationDetailsView.vue
Normal file
@ -0,0 +1,241 @@
|
||||
<template>
|
||||
<div class="information-details gradient-background-circles">
|
||||
<MobileHeader title="Information details" />
|
||||
|
||||
<div class="content">
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="loading-state">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
|
||||
<!-- 错误状态 -->
|
||||
<div v-else-if="error" class="error-state">
|
||||
<div class="error-icon">⚠️</div>
|
||||
<p>{{ error }}</p>
|
||||
<button @click="fetchData" class="retry-btn">Retry</button>
|
||||
</div>
|
||||
|
||||
<!-- 交易记录列表 -->
|
||||
<div v-else class="transaction-list">
|
||||
<div
|
||||
v-for="transaction in transactions"
|
||||
:key="transaction.id"
|
||||
class="transaction-item"
|
||||
>
|
||||
<div class="transaction-content">
|
||||
<div class="transaction-info">
|
||||
<h3>{{ transaction.eventDescribe }}</h3>
|
||||
<p class="transaction-time">{{ transaction.createTime }}</p>
|
||||
</div>
|
||||
<div class="transaction-amount" :class="transaction.amount > 0 ? 'positive' : 'negative'">
|
||||
{{ transaction.amount > 0 ? '+' : '' }}{{ Math.abs(transaction.amount) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-if="!loading && !error && transactions.length === 0" class="empty-state">
|
||||
<div class="empty-icon">📄</div>
|
||||
<p>No transaction records</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
import { getWalletDetails, formatDateTime, formatEventDescribe } from '../api/wallet.js'
|
||||
|
||||
const transactions = ref([])
|
||||
const loading = ref(false)
|
||||
const error = ref(null)
|
||||
|
||||
// 获取数据
|
||||
const fetchData = async () => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const response = await getWalletDetails()
|
||||
|
||||
// 处理API返回的数据
|
||||
if (response.body && Array.isArray(response.body)) {
|
||||
transactions.value = response.body.map(item => ({
|
||||
id: item.id,
|
||||
eventDescribe: formatEventDescribe(item.eventDescribe),
|
||||
createTime: formatDateTime(item.createTime),
|
||||
amount: item.type === 0 ? -Math.abs(item.amount) : item.amount, // type=0表示支出,显示为负数
|
||||
balance: item.balance,
|
||||
event: item.event
|
||||
}))
|
||||
} else {
|
||||
transactions.value = []
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch wallet details:', err)
|
||||
error.value = err.message || 'Failed to load data'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时获取数据
|
||||
onMounted(() => {
|
||||
fetchData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.information-details {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 交易记录列表 */
|
||||
.transaction-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.transaction-item {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.transaction-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.transaction-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.transaction-info h3 {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.transaction-time {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.transaction-amount {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
min-width: 40px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.transaction-amount.positive {
|
||||
color: #10B981;
|
||||
}
|
||||
|
||||
.transaction-amount.negative {
|
||||
color: #EF4444;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 3px solid #f3f3f3;
|
||||
border-top: 3px solid #8B5CF6;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-state p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 错误状态 */
|
||||
.error-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.error-state p {
|
||||
margin: 0 0 20px 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
padding: 10px 24px;
|
||||
background-color: #8B5CF6;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.retry-btn:active {
|
||||
background-color: #7C3AED;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
249
src/views/InviteMembersView.vue
Normal file
249
src/views/InviteMembersView.vue
Normal file
@ -0,0 +1,249 @@
|
||||
<template>
|
||||
<div class="invite-members gradient-background-circles">
|
||||
<MobileHeader title="Invite Members" />
|
||||
|
||||
<div class="content">
|
||||
<div class="instructions">
|
||||
<div class="instruction-item">
|
||||
<span class="step-number">1.</span>
|
||||
<span>Invite users to download Likei APP</span>
|
||||
</div>
|
||||
<div class="instruction-item">
|
||||
<span class="step-number">2.</span>
|
||||
<span>Invited users need to log in to the Likei APP!</span>
|
||||
</div>
|
||||
<div class="instruction-item">
|
||||
<span class="step-number">3.</span>
|
||||
<span>Me > Host Center >Apply to join the team.</span>
|
||||
</div>
|
||||
<div class="instruction-item">
|
||||
<span class="step-number">4.</span>
|
||||
<span>Enter the team ID,click send to join.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="team-info">
|
||||
<div class="team-id-section">
|
||||
<h3>Team ID:</h3>
|
||||
<div class="team-id-display">
|
||||
<span class="team-id">{{ teamId }}</span>
|
||||
<button class="copy-btn" @click="copyTeamId">
|
||||
📋
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="share-section">
|
||||
<h3>Share:</h3>
|
||||
<div class="share-buttons">
|
||||
<button class="share-btn" @click="shareToWeChat">
|
||||
<span class="share-icon">💬</span>
|
||||
WeChat
|
||||
</button>
|
||||
<button class="share-btn" @click="shareLink">
|
||||
<span class="share-icon">🔗</span>
|
||||
Link
|
||||
</button>
|
||||
<button class="share-btn" @click="shareQRCode">
|
||||
<span class="share-icon">📱</span>
|
||||
QR Code
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const teamId = ref('123456789')
|
||||
|
||||
// 复制团队ID
|
||||
const copyTeamId = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(teamId.value)
|
||||
alert('Team ID copied to clipboard!')
|
||||
} catch (error) {
|
||||
// 备选方案
|
||||
const textArea = document.createElement('textarea')
|
||||
textArea.value = teamId.value
|
||||
document.body.appendChild(textArea)
|
||||
textArea.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(textArea)
|
||||
alert('Team ID copied to clipboard!')
|
||||
}
|
||||
}
|
||||
|
||||
// 分享到微信
|
||||
const shareToWeChat = () => {
|
||||
alert('Share to WeChat functionality')
|
||||
}
|
||||
|
||||
// 分享链接
|
||||
const shareLink = () => {
|
||||
const shareText = `Join my team! Team ID: ${teamId.value}\nDownload Likei APP: https://likei.app`
|
||||
|
||||
if (navigator.share) {
|
||||
navigator.share({
|
||||
title: 'Join my team on Likei',
|
||||
text: shareText,
|
||||
url: 'https://likei.app'
|
||||
})
|
||||
} else {
|
||||
// 备选方案
|
||||
copyTeamId()
|
||||
alert('Link copied! Share with your friends.')
|
||||
}
|
||||
}
|
||||
|
||||
// 分享二维码
|
||||
const shareQRCode = () => {
|
||||
alert('QR Code sharing functionality')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.invite-members {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 说明步骤 */
|
||||
.instructions {
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.instruction-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.instruction-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
color: #8B5CF6;
|
||||
font-weight: 600;
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.instruction-item span:last-child {
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 团队信息 */
|
||||
.team-info {
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.team-id-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.team-id-section h3,
|
||||
.share-section h3 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.team-id-display {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: #f8f9fa;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.team-id {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
.copy-btn:active {
|
||||
background-color: #dee2e6;
|
||||
}
|
||||
|
||||
/* 分享按钮 */
|
||||
.share-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.share-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16px 12px;
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
flex: 1;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.share-btn:hover {
|
||||
background-color: #e9ecef;
|
||||
border-color: #8B5CF6;
|
||||
}
|
||||
|
||||
.share-btn:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.share-icon {
|
||||
font-size: 24px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.share-btn span:last-child {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
264
src/views/MessageView.vue
Normal file
264
src/views/MessageView.vue
Normal file
@ -0,0 +1,264 @@
|
||||
<template>
|
||||
<div class="message gradient-background-circles">
|
||||
<MobileHeader title="User's message" />
|
||||
|
||||
<div class="content">
|
||||
<div v-if="loading" class="loading-state">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>Loading messages...</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="message-list">
|
||||
<div
|
||||
v-for="message in messages"
|
||||
:key="message.id"
|
||||
class="message-item"
|
||||
>
|
||||
<div class="user-info">
|
||||
<div class="user-avatar">
|
||||
<img v-if="message.avatar" :src="message.avatar" :alt="message.name" />
|
||||
<span v-else>{{ message.name.charAt(0) }}</span>
|
||||
</div>
|
||||
<div class="user-details">
|
||||
<h4>{{ message.name }}</h4>
|
||||
<p>ID: {{ message.id }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button class="agree-btn" @click="handleAgree(message)">
|
||||
Agree
|
||||
</button>
|
||||
<button class="refuse-btn" @click="handleRefuse(message)">
|
||||
Refuse
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!loading && messages.length === 0" class="empty-state">
|
||||
<div class="empty-icon">📨</div>
|
||||
<p>No messages</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const messages = ref([])
|
||||
|
||||
// 模拟消息数据
|
||||
const mockMessages = [
|
||||
{
|
||||
id: '1234567890',
|
||||
name: 'User1',
|
||||
avatar: '',
|
||||
type: 'team_join_request'
|
||||
},
|
||||
{
|
||||
id: '1234567890',
|
||||
name: 'User1',
|
||||
avatar: '',
|
||||
type: 'team_join_request'
|
||||
},
|
||||
{
|
||||
id: '1234567890',
|
||||
name: 'User1',
|
||||
avatar: '',
|
||||
type: 'team_join_request'
|
||||
}
|
||||
]
|
||||
|
||||
// 获取消息列表
|
||||
const fetchMessages = async () => {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
// 模拟API调用
|
||||
setTimeout(() => {
|
||||
messages.value = mockMessages
|
||||
loading.value = false
|
||||
}, 800)
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch messages:', error)
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 同意申请
|
||||
const handleAgree = (message) => {
|
||||
alert(`Agreed to ${message.name}'s request`)
|
||||
// 从列表中移除
|
||||
const index = messages.value.findIndex(m => m === message)
|
||||
if (index > -1) {
|
||||
messages.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// 拒绝申请
|
||||
const handleRefuse = (message) => {
|
||||
alert(`Refused ${message.name}'s request`)
|
||||
// 从列表中移除
|
||||
const index = messages.value.findIndex(m => m === message)
|
||||
if (index > -1) {
|
||||
messages.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchMessages()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.message {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 3px solid #f3f3f3;
|
||||
border-top: 3px solid #8B5CF6;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 消息列表 */
|
||||
.message-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.message-item {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 20px;
|
||||
background-color: #8B5CF6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-right: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.user-avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.user-details h4 {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.user-details p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.agree-btn, .refuse-btn {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.agree-btn {
|
||||
background-color: #C084FC;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.agree-btn:active {
|
||||
background-color: #A855F7;
|
||||
}
|
||||
|
||||
.refuse-btn {
|
||||
background-color: #E5E7EB;
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
.refuse-btn:active {
|
||||
background-color: #D1D5DB;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
175
src/views/PlatformPolicyView.vue
Normal file
175
src/views/PlatformPolicyView.vue
Normal file
@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<div class="platform-policy gradient-background-circles">
|
||||
<MobileHeader title="Platform Policy" />
|
||||
|
||||
<div class="content">
|
||||
<div class="policy-list">
|
||||
<div
|
||||
v-for="policy in policies"
|
||||
:key="policy.level"
|
||||
class="policy-item"
|
||||
>
|
||||
<div class="policy-header">
|
||||
<h3>{{ policy.level }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="policy-details">
|
||||
<div class="policy-grid">
|
||||
<div class="policy-cell">
|
||||
<span class="cell-label">Time (hours)</span>
|
||||
<span class="cell-value">{{ policy.timeHours }}</span>
|
||||
</div>
|
||||
<div class="policy-cell">
|
||||
<span class="cell-label">Target</span>
|
||||
<span class="cell-value">{{ policy.target }}</span>
|
||||
</div>
|
||||
<div class="policy-cell">
|
||||
<span class="cell-label">Host Salary</span>
|
||||
<span class="cell-value">${{ policy.hostSalary }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="policy-grid bottom-row">
|
||||
<div class="policy-cell">
|
||||
<span class="cell-label">Agent Salary</span>
|
||||
<span class="cell-value">${{ policy.agentSalary }}</span>
|
||||
</div>
|
||||
<div class="policy-cell">
|
||||
<span class="cell-label">Total Salary</span>
|
||||
<span class="cell-value">${{ policy.totalSalary }}</span>
|
||||
</div>
|
||||
<div class="policy-cell empty"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
// 政策数据
|
||||
const policies = ref([
|
||||
{
|
||||
level: 'Lv.1',
|
||||
timeHours: 20,
|
||||
target: 32500,
|
||||
hostSalary: 3,
|
||||
agentSalary: 1,
|
||||
totalSalary: 4
|
||||
},
|
||||
{
|
||||
level: 'Lv.2',
|
||||
timeHours: 20,
|
||||
target: 65000,
|
||||
hostSalary: 6,
|
||||
agentSalary: 2,
|
||||
totalSalary: 8
|
||||
},
|
||||
{
|
||||
level: 'Lv.3',
|
||||
timeHours: 15,
|
||||
target: 108000,
|
||||
hostSalary: 10,
|
||||
agentSalary: 3,
|
||||
totalSalary: 13
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.platform-policy {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.policy-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.policy-item {
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.policy-header {
|
||||
padding: 16px 16px 8px 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.policy-header h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.policy-details {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.policy-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.policy-grid.bottom-row {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.policy-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.policy-cell.empty {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.cell-label {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.cell-value {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 480px) {
|
||||
.policy-grid {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.cell-label {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.cell-value {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
606
src/views/SearchPayeeView.vue
Normal file
606
src/views/SearchPayeeView.vue
Normal file
@ -0,0 +1,606 @@
|
||||
<template>
|
||||
<div class="search-payee gradient-background-circles">
|
||||
<MobileHeader title="Search Payee" />
|
||||
|
||||
<div class="content">
|
||||
<!-- 搜索框 -->
|
||||
<div class="search-section">
|
||||
<!-- 用户类型选择 -->
|
||||
<div v-if="!isLoadingRole && availableUserTypes.length > 1" class="user-type-selector">
|
||||
<label>搜索类型:</label>
|
||||
<div class="type-buttons">
|
||||
<button
|
||||
v-for="type in availableUserTypes"
|
||||
:key="type.value"
|
||||
@click="selectedUserType = type.value"
|
||||
:class="['type-btn', { active: selectedUserType === type.value }]"
|
||||
>
|
||||
{{ type.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="search-box">
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
placeholder="Search by ID or name"
|
||||
class="search-input"
|
||||
@keyup.enter="performSearch"
|
||||
/>
|
||||
<button
|
||||
v-if="searchQuery"
|
||||
@click="clearSearch"
|
||||
class="clear-btn"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<button class="search-btn" @click="performSearch" :disabled="!selectedUserType">
|
||||
Search
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 角色检查加载状态 -->
|
||||
<div v-if="isLoadingRole" class="loading-state">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>Checking permissions...</p>
|
||||
</div>
|
||||
|
||||
<!-- 搜索结果 -->
|
||||
<div v-else-if="searchResults.length > 0" class="results-section">
|
||||
<div
|
||||
v-for="user in searchResults"
|
||||
:key="user.id"
|
||||
class="user-item"
|
||||
>
|
||||
<div class="user-info">
|
||||
<div class="user-avatar">
|
||||
<img v-if="user.avatar" :src="user.avatar" :alt="user.name" />
|
||||
<span v-else>{{ user.name.charAt(0) }}</span>
|
||||
</div>
|
||||
<div class="user-details">
|
||||
<h4>{{ user.name }}</h4>
|
||||
<p>ID: {{ user.account || user.id }}</p>
|
||||
<div class="user-tags">
|
||||
<span v-if="user.isHost" class="tag host-tag">👑 Host</span>
|
||||
<span v-if="user.isAgency" class="tag agency-tag">🏢 Agency</span>
|
||||
<span v-if="user.hasSalary" class="tag salary-tag">💰 Salary</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="add-btn" @click="selectUser(user)">
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else-if="searchQuery && !isSearching && !isLoadingRole" class="empty-state">
|
||||
<div class="empty-icon">🔍</div>
|
||||
<p>No users found</p>
|
||||
<span>Try searching with a different keyword</span>
|
||||
</div>
|
||||
|
||||
<!-- 搜索提示 -->
|
||||
<div v-else-if="!searchQuery && !isLoadingRole" class="search-hint">
|
||||
<div class="hint-icon">👥</div>
|
||||
<p>Search for users</p>
|
||||
<span v-if="userRole">You can search for: {{ getRoleDescription(userRole) }}</span>
|
||||
<span v-else>Enter user ID or name to find payees</span>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="isSearching" class="loading-state">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>Searching...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
import { userBankCheckTransfer, userBankSearchUserProfile } from '../api/wallet.js'
|
||||
import { setSelectedPayee } from '../utils/payeeStore.js'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const searchQuery = ref('')
|
||||
const searchResults = ref([])
|
||||
const isSearching = ref(false)
|
||||
const userRole = ref('')
|
||||
const isLoadingRole = ref(false)
|
||||
const selectedUserType = ref('')
|
||||
const showTypeSelector = ref(false)
|
||||
|
||||
// 模拟用户数据 - 根据角色显示不同的用户
|
||||
const allUsers = {
|
||||
'TEAM_OWN': [
|
||||
{
|
||||
id: '1234567890',
|
||||
name: 'Agent User',
|
||||
type: 'AGENT',
|
||||
isHost: false,
|
||||
isAgency: true,
|
||||
hasSalary: false
|
||||
},
|
||||
{
|
||||
id: '0987654321',
|
||||
name: 'BD Manager',
|
||||
type: 'BD',
|
||||
isHost: false,
|
||||
isAgency: true,
|
||||
hasSalary: true
|
||||
},
|
||||
{
|
||||
id: '1122334455',
|
||||
name: 'Host Anchor',
|
||||
type: 'ANCHOR',
|
||||
isHost: true,
|
||||
isAgency: false,
|
||||
hasSalary: true
|
||||
}
|
||||
],
|
||||
'TEAM_MEMBER': [
|
||||
{
|
||||
id: '1234567890',
|
||||
name: 'Team Agent',
|
||||
type: 'AGENT',
|
||||
isHost: false,
|
||||
isAgency: true,
|
||||
hasSalary: false
|
||||
},
|
||||
{
|
||||
id: '0987654321',
|
||||
name: 'BD Manager',
|
||||
type: 'BD',
|
||||
isHost: false,
|
||||
isAgency: true,
|
||||
hasSalary: true
|
||||
}
|
||||
],
|
||||
'BD': [
|
||||
{
|
||||
id: '1234567890',
|
||||
name: 'Agent User',
|
||||
type: 'AGENT',
|
||||
isHost: false,
|
||||
isAgency: true,
|
||||
hasSalary: false
|
||||
}
|
||||
],
|
||||
'default': [
|
||||
{
|
||||
id: '1234567890',
|
||||
name: 'Default User',
|
||||
type: 'USER',
|
||||
isHost: false,
|
||||
isAgency: false,
|
||||
hasSalary: false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 根据角色获取可搜索的用户列表
|
||||
const availableUsers = computed(() => {
|
||||
return allUsers[userRole.value] || allUsers['default']
|
||||
})
|
||||
|
||||
// 根据用户角色获取可选择的用户类型
|
||||
const availableUserTypes = computed(() => {
|
||||
switch (userRole.value) {
|
||||
case 'TEAM_OWN':
|
||||
return [
|
||||
{ label: 'Agent', value: 'AGENT' },
|
||||
{ label: 'BD Manager', value: 'BD' },
|
||||
{ label: 'Anchor', value: 'ANCHOR' }
|
||||
]
|
||||
case 'TEAM_MEMBER':
|
||||
return [
|
||||
{ label: 'Agent', value: 'AGENT' },
|
||||
{ label: 'BD Manager', value: 'BD' }
|
||||
]
|
||||
case 'BD':
|
||||
return [
|
||||
{ label: 'Agent', value: 'AGENT' }
|
||||
]
|
||||
default:
|
||||
return [
|
||||
{ label: 'User', value: 'USER' }
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
// 检查用户转账角色
|
||||
const checkUserRole = async () => {
|
||||
isLoadingRole.value = true
|
||||
|
||||
try {
|
||||
const response = await userBankCheckTransfer()
|
||||
|
||||
if (response.status && response.body) {
|
||||
// 根据接口返回的role设置用户角色
|
||||
userRole.value = response.body.role || ''
|
||||
console.log('用户角色:', userRole.value)
|
||||
|
||||
// 设置默认的用户类型
|
||||
if (userRole.value === 'TEAM_OWN') {
|
||||
selectedUserType.value = 'ANCHOR' // TEAM_OWN 默认选择 ANCHOR
|
||||
} else {
|
||||
selectedUserType.value = 'AGENT' // 其他角色默认选择 AGENT
|
||||
}
|
||||
} else {
|
||||
userRole.value = ''
|
||||
selectedUserType.value = 'USER'
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to check user role:', error)
|
||||
userRole.value = ''
|
||||
selectedUserType.value = 'USER'
|
||||
} finally {
|
||||
isLoadingRole.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 执行搜索
|
||||
const performSearch = async () => {
|
||||
if (!searchQuery.value.trim()) {
|
||||
searchResults.value = []
|
||||
return
|
||||
}
|
||||
|
||||
if (!selectedUserType.value) {
|
||||
alert('Please select user type first')
|
||||
return
|
||||
}
|
||||
|
||||
isSearching.value = true
|
||||
|
||||
try {
|
||||
const response = await userBankSearchUserProfile(selectedUserType.value, searchQuery.value.trim())
|
||||
|
||||
if (response.status && response.body) {
|
||||
// 根据新的API返回数据结构进行映射
|
||||
const user = {
|
||||
id: response.body.id,
|
||||
account: response.body.account,
|
||||
name: response.body.userNickname,
|
||||
avatar: response.body.userAvatar,
|
||||
type: selectedUserType.value,
|
||||
sex: response.body.userSex,
|
||||
age: response.body.age,
|
||||
countryName: response.body.countryName,
|
||||
countryCode: response.body.countryCode,
|
||||
accountStatus: response.body.accountStatus,
|
||||
// 根据类型设置标签
|
||||
isHost: selectedUserType.value === 'ANCHOR',
|
||||
isAgency: ['AGENT', 'BD'].includes(selectedUserType.value),
|
||||
hasSalary: ['ANCHOR', 'BD'].includes(selectedUserType.value)
|
||||
}
|
||||
searchResults.value = [user]
|
||||
} else {
|
||||
searchResults.value = []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Search failed:', error)
|
||||
searchResults.value = []
|
||||
alert('User not found or search failed')
|
||||
} finally {
|
||||
isSearching.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 清除搜索
|
||||
const clearSearch = () => {
|
||||
searchQuery.value = ''
|
||||
searchResults.value = []
|
||||
}
|
||||
|
||||
// 选择用户
|
||||
const selectUser = (user) => {
|
||||
// 将选中的用户信息保存到全局状态
|
||||
setSelectedPayee({
|
||||
id: user.id,
|
||||
account: user.account,
|
||||
name: user.name,
|
||||
avatar: user.avatar,
|
||||
type: user.type,
|
||||
isHost: user.isHost,
|
||||
isAgency: user.isAgency,
|
||||
hasSalary: user.hasSalary
|
||||
})
|
||||
|
||||
console.log('Selected payee:', user)
|
||||
|
||||
// 返回到 Transfer 页面
|
||||
router.push('/transfer')
|
||||
}
|
||||
|
||||
// 获取角色描述
|
||||
const getRoleDescription = (role) => {
|
||||
switch (role) {
|
||||
case 'TEAM_OWN':
|
||||
return 'Agents, BD Managers, Anchors'
|
||||
case 'TEAM_MEMBER':
|
||||
return 'Agents, BD Managers'
|
||||
case 'BD':
|
||||
return 'Agents'
|
||||
default:
|
||||
return 'Users'
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时检查用户角色
|
||||
onMounted(() => {
|
||||
checkUserRole()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search-payee {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 搜索部分 */
|
||||
.search-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 用户类型选择器 */
|
||||
.user-type-selector {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.user-type-selector label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.type-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.type-btn {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 16px;
|
||||
background-color: white;
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.type-btn.active {
|
||||
background-color: #8B5CF6;
|
||||
color: white;
|
||||
border-color: #8B5CF6;
|
||||
}
|
||||
|
||||
.type-btn:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
padding: 4px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
padding: 12px 16px;
|
||||
font-size: 16px;
|
||||
background: transparent;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 8px;
|
||||
color: #999;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
background-color: #8B5CF6;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search-btn:active {
|
||||
background-color: #7C3AED;
|
||||
}
|
||||
|
||||
.search-btn:disabled {
|
||||
background-color: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 搜索结果 */
|
||||
.results-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.user-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.user-item:active {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
background-color: #8B5CF6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-right: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.user-avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.user-details h4 {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.user-details p {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.user-tags {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.host-tag {
|
||||
background-color: #E0E7FF;
|
||||
color: #5B21B6;
|
||||
}
|
||||
|
||||
.agency-tag {
|
||||
background-color: #DBEAFE;
|
||||
color: #1E40AF;
|
||||
}
|
||||
|
||||
.salary-tag {
|
||||
background-color: #FEF3C7;
|
||||
color: #D97706;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
background-color: #10B981;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.add-btn:active {
|
||||
background-color: #059669;
|
||||
}
|
||||
|
||||
/* 空状态和提示 */
|
||||
.empty-state, .search-hint {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.empty-icon, .hint-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-state p, .search-hint p {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 8px 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.empty-state span, .search-hint span {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-state {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 3px solid #f3f3f3;
|
||||
border-top: 3px solid #8B5CF6;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 16px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-state p {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
235
src/views/SellerRecordsView.vue
Normal file
235
src/views/SellerRecordsView.vue
Normal file
@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<div class="seller-records gradient-background-circles">
|
||||
<MobileHeader title="Seller records" />
|
||||
|
||||
<div class="content">
|
||||
<div v-if="loading" class="loading-state">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>Loading records...</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="records-list">
|
||||
<div
|
||||
v-for="record in records"
|
||||
:key="record.id"
|
||||
class="record-item"
|
||||
>
|
||||
<div class="record-info">
|
||||
<div class="user-avatar">
|
||||
<span>{{ record.userName.charAt(0) }}</span>
|
||||
</div>
|
||||
<div class="record-details">
|
||||
<h4>{{ record.userName }}</h4>
|
||||
<p>ID: {{ record.userId }}</p>
|
||||
<div class="record-time">{{ record.time }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="record-amount">
|
||||
<span class="coins-amount">{{ record.amount }} coins</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!loading && records.length === 0" class="empty-state">
|
||||
<div class="empty-icon">📋</div>
|
||||
<p>No records found</p>
|
||||
<span>No selling records yet</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const records = ref([])
|
||||
|
||||
// 模拟记录数据
|
||||
const mockRecords = [
|
||||
{
|
||||
id: 1,
|
||||
userName: 'User1033...',
|
||||
userId: '452319866',
|
||||
amount: '-10000',
|
||||
time: '2025.08.01 10:59:59'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
userName: 'User1033...',
|
||||
userId: '452319866',
|
||||
amount: '-10000',
|
||||
time: '2025.08.01 10:59:59'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
userName: 'User1033...',
|
||||
userId: '452319866',
|
||||
amount: '-10000',
|
||||
time: '2025.08.01 10:59:59'
|
||||
}
|
||||
]
|
||||
|
||||
// 获取记录
|
||||
const fetchRecords = async () => {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
// 模拟API调用
|
||||
setTimeout(() => {
|
||||
records.value = mockRecords
|
||||
loading.value = false
|
||||
}, 800)
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch records:', error)
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchRecords()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.seller-records {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 3px solid #f3f3f3;
|
||||
border-top: 3px solid #F59E0B;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-state p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 记录列表 */
|
||||
.records-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.record-item {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.record-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
background-color: #9CA3AF;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.record-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.record-details h4 {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.record-details p {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.record-time {
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.record-amount {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.coins-amount {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #EF4444;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.empty-state span {
|
||||
font-size: 14px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
</style>
|
||||
750
src/views/TeamBillView.vue
Normal file
750
src/views/TeamBillView.vue
Normal file
@ -0,0 +1,750 @@
|
||||
<template>
|
||||
<div class="team-bill gradient-background-circles">
|
||||
<MobileHeader title="Team Bill" />
|
||||
|
||||
<div class="content">
|
||||
<!-- 历史团队收入 -->
|
||||
<div class="history-income-card">
|
||||
<div class="income-header">
|
||||
<span class="income-label">History Team Income:</span>
|
||||
<button class="help-btn" @click="showHelpInfo">?</button>
|
||||
</div>
|
||||
<div class="income-amount">${{ historyIncome }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 工作报告标题 -->
|
||||
<div class="work-report-title">
|
||||
<span>Work report:</span>
|
||||
</div>
|
||||
|
||||
<!-- 账单列表 -->
|
||||
<div class="bill-list">
|
||||
<div
|
||||
v-for="bill in bills"
|
||||
:key="bill.id"
|
||||
class="bill-item"
|
||||
@click="showBillDetail(bill)"
|
||||
>
|
||||
<div class="bill-header">
|
||||
<span class="bill-period">{{ bill.period }}</span>
|
||||
<span :class="['bill-status', bill.status.toLowerCase()]">
|
||||
{{ bill.statusText }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="bill-details">
|
||||
<div class="salary-row">
|
||||
<div class="salary-item">
|
||||
<span class="salary-label">Host Salary:</span>
|
||||
<span class="salary-value">{{ bill.hostSalary }}</span>
|
||||
</div>
|
||||
<div class="salary-item">
|
||||
<span class="salary-label">Agent Salary:</span>
|
||||
<span class="salary-value">{{ bill.agentSalary }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="total-row">
|
||||
<span class="total-label">Total:</span>
|
||||
<span class="total-value">{{ bill.total }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="more-btn">
|
||||
<span>More</span>
|
||||
<span class="arrow">></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 帮助模态框 -->
|
||||
<div v-if="showHelpModal" class="modal-overlay" @click="closeHelpModal">
|
||||
<div class="help-modal-content" @click.stop>
|
||||
<div class="help-modal-header">
|
||||
<h3>Help Information</h3>
|
||||
<button class="close-btn" @click="closeHelpModal">×</button>
|
||||
</div>
|
||||
|
||||
<div class="help-modal-body">
|
||||
<div class="help-section">
|
||||
<p class="help-description">
|
||||
Shown below is the work report of the members "More" can view more work detailed.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="help-section">
|
||||
<h4>Status Description:</h4>
|
||||
<div class="status-list">
|
||||
<div class="status-item">
|
||||
<span class="status-number">(1)</span>
|
||||
<span class="status-name">Completed:</span>
|
||||
<span class="status-desc">The system has completed the settlement report.</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-number">(2)</span>
|
||||
<span class="status-name">Pending:</span>
|
||||
<span class="status-desc">There is currently a dispute, and the system cannot perform a settlement.</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-number">(3)</span>
|
||||
<span class="status-name">Out of account:</span>
|
||||
<span class="status-desc">waiting for system verification</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-number">(4)</span>
|
||||
<span class="status-name">In progress:</span>
|
||||
<span class="status-desc">work now in progress.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 成员收入弹窗 -->
|
||||
<div v-if="showMemberModal" class="modal-overlay" @click="closeMemberModal">
|
||||
<div class="modal-content" @click.stop>
|
||||
<div class="modal-header">
|
||||
<h3>Member Income</h3>
|
||||
<button class="close-btn" @click="closeMemberModal">×</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="modal-period">
|
||||
<span>{{ selectedBill?.period }}</span>
|
||||
<span :class="['modal-status', selectedBill?.status.toLowerCase()]">
|
||||
{{ selectedBill?.statusText }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="search-section">
|
||||
<div class="search-box">
|
||||
<span class="search-icon">🔍</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Please enter the host ID"
|
||||
v-model="searchQuery"
|
||||
class="search-input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="member-list">
|
||||
<div
|
||||
v-for="member in filteredMembers"
|
||||
:key="member.id"
|
||||
class="member-item"
|
||||
>
|
||||
<div class="member-info">
|
||||
<div class="member-avatar">
|
||||
<span>{{ member.name.charAt(0) }}</span>
|
||||
</div>
|
||||
<div class="member-details">
|
||||
<div class="member-name">{{ member.name }}</div>
|
||||
<div class="member-id">ID: {{ member.id }}</div>
|
||||
<div class="member-target">Target: {{ member.target }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="member-stats">
|
||||
<div class="member-stat">
|
||||
<span class="stat-icon">💰</span>
|
||||
<span>{{ member.id }}</span>
|
||||
</div>
|
||||
<div class="member-time">Time (Days): {{ member.days }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const historyIncome = ref('12345')
|
||||
const showMemberModal = ref(false)
|
||||
const showHelpModal = ref(false)
|
||||
const selectedBill = ref(null)
|
||||
const searchQuery = ref('')
|
||||
|
||||
// 账单数据
|
||||
const bills = ref([
|
||||
{
|
||||
id: 1,
|
||||
period: '2025.08',
|
||||
status: 'pending',
|
||||
statusText: '进行中 (Pending)',
|
||||
hostSalary: '–',
|
||||
agentSalary: '–',
|
||||
total: '–'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
period: '2025.07',
|
||||
status: 'pending',
|
||||
statusText: '待处理 (Pending)',
|
||||
hostSalary: '$100',
|
||||
agentSalary: '$10',
|
||||
total: '110'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
period: '2025.06',
|
||||
status: 'completed',
|
||||
statusText: '已完成 (Completed)',
|
||||
hostSalary: '$200',
|
||||
agentSalary: '$30',
|
||||
total: '$230'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
period: '2025.05',
|
||||
status: 'out',
|
||||
statusText: '挂起 (Out of account)',
|
||||
hostSalary: '$500',
|
||||
agentSalary: '$100',
|
||||
total: '$600'
|
||||
}
|
||||
])
|
||||
|
||||
// 成员数据
|
||||
const members = ref([
|
||||
{
|
||||
id: '1234567890',
|
||||
name: 'User name 1',
|
||||
target: 'A',
|
||||
days: 14
|
||||
},
|
||||
{
|
||||
id: '1234567890',
|
||||
name: 'User name 1',
|
||||
target: 'A',
|
||||
days: 14
|
||||
},
|
||||
{
|
||||
id: '1234567890',
|
||||
name: 'User name 1',
|
||||
target: 'A',
|
||||
days: 14
|
||||
},
|
||||
{
|
||||
id: '1234567890',
|
||||
name: 'User name 1',
|
||||
target: 'A',
|
||||
days: 14
|
||||
},
|
||||
{
|
||||
id: '1234567890',
|
||||
name: 'User name 1',
|
||||
target: 'A',
|
||||
days: 14
|
||||
},
|
||||
{
|
||||
id: '1234567890',
|
||||
name: 'User name 1',
|
||||
target: 'A',
|
||||
days: 14
|
||||
}
|
||||
])
|
||||
|
||||
// 过滤后的成员列表
|
||||
const filteredMembers = computed(() => {
|
||||
if (!searchQuery.value) return members.value
|
||||
return members.value.filter(member =>
|
||||
member.id.includes(searchQuery.value) ||
|
||||
member.name.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||
)
|
||||
})
|
||||
|
||||
// 显示账单详情
|
||||
const showBillDetail = (bill) => {
|
||||
selectedBill.value = bill
|
||||
showMemberModal.value = true
|
||||
}
|
||||
|
||||
// 关闭成员模态框
|
||||
const closeMemberModal = () => {
|
||||
showMemberModal.value = false
|
||||
selectedBill.value = null
|
||||
searchQuery.value = ''
|
||||
}
|
||||
|
||||
// 显示帮助模态框
|
||||
const showHelpInfo = () => {
|
||||
showHelpModal.value = true
|
||||
}
|
||||
|
||||
// 关闭帮助模态框
|
||||
const closeHelpModal = () => {
|
||||
showHelpModal.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.team-bill {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 历史收入卡片 */
|
||||
.history-income-card {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.income-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.income-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.help-btn {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 10px;
|
||||
background-color: #666;
|
||||
color: white;
|
||||
border: none;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.help-btn:hover {
|
||||
background-color: #8B5CF6;
|
||||
}
|
||||
|
||||
.help-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.income-amount {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 工作报告标题 */
|
||||
.work-report-title {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
/* 账单列表 */
|
||||
.bill-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.bill-item {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.bill-item:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.bill-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.bill-period {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.bill-status {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.bill-status.pending {
|
||||
background-color: #FEF3C7;
|
||||
color: #D97706;
|
||||
}
|
||||
|
||||
.bill-status.completed {
|
||||
background-color: #D1FAE5;
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
.bill-status.out {
|
||||
background-color: #FEE2E2;
|
||||
color: #DC2626;
|
||||
}
|
||||
|
||||
.bill-details {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.salary-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.salary-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.salary-label {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.salary-value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.total-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.total-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.total-value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.more-btn {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: #C084FC;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 模态框 */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.modal-header h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
color: #666;
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 16px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-period {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.modal-status {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.modal-status.pending {
|
||||
background-color: #FEF3C7;
|
||||
color: #D97706;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #f9fafb;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
margin-right: 8px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
background: none;
|
||||
outline: none;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.member-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.member-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
background-color: #f9fafb;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.member-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.member-avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 16px;
|
||||
background-color: #8B5CF6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.member-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.member-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.member-id {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.member-target {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.member-stats {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.member-stat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.member-time {
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 帮助模态框 */
|
||||
.help-modal-content {
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.help-modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.help-modal-header h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.help-modal-body {
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.help-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.help-section:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.help-description {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: #555;
|
||||
margin: 0;
|
||||
padding: 12px;
|
||||
background-color: #f0f9ff;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #3b82f6;
|
||||
}
|
||||
|
||||
.help-section h4 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.status-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
background-color: #f9fafb;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.status-number {
|
||||
font-weight: 600;
|
||||
color: #8B5CF6;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.status-name {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
flex-shrink: 0;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.status-desc {
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
426
src/views/TeamMemberView.vue
Normal file
426
src/views/TeamMemberView.vue
Normal file
@ -0,0 +1,426 @@
|
||||
<template>
|
||||
<div class="team-member gradient-background-circles">
|
||||
<MobileHeader title="Team Member (87)" />
|
||||
|
||||
<div class="content">
|
||||
<!-- 搜索框 -->
|
||||
<div class="search-section">
|
||||
<div class="search-box">
|
||||
<span class="search-icon">🔍</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search by ID or name"
|
||||
v-model="searchQuery"
|
||||
class="search-input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 筛选标签 -->
|
||||
<div class="filter-tabs">
|
||||
<button
|
||||
v-for="tab in filterTabs"
|
||||
:key="tab.value"
|
||||
@click="activeFilter = tab.value"
|
||||
:class="['filter-tab', { active: activeFilter === tab.value }]"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 成员列表 -->
|
||||
<div class="member-list">
|
||||
<div
|
||||
v-for="member in filteredMembers"
|
||||
:key="member.id"
|
||||
class="member-item"
|
||||
@click="viewMemberDetail(member)"
|
||||
>
|
||||
<div class="member-info">
|
||||
<div class="member-avatar">
|
||||
<img v-if="member.avatar" :src="member.avatar" :alt="member.name" />
|
||||
<span v-else>{{ member.name.charAt(0) }}</span>
|
||||
</div>
|
||||
<div class="member-details">
|
||||
<h4>{{ member.name }}</h4>
|
||||
<p>ID: {{ member.id }}</p>
|
||||
<div class="member-tags">
|
||||
<span v-if="member.isHost" class="tag host-tag">👑 Host</span>
|
||||
<span v-if="member.isAgent" class="tag agent-tag">👤 Agent</span>
|
||||
<span v-if="member.isActive" class="tag active-tag">🟢 Active</span>
|
||||
<span v-else class="tag inactive-tag">🔴 Inactive</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="member-stats">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Target:</span>
|
||||
<span class="stat-value">{{ member.target }}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Days:</span>
|
||||
<span class="stat-value">{{ member.activeDays }}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Salary:</span>
|
||||
<span class="stat-value">${{ member.salary }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-if="filteredMembers.length === 0" class="empty-state">
|
||||
<div class="empty-icon">👥</div>
|
||||
<p>No members found</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const searchQuery = ref('')
|
||||
const activeFilter = ref('all')
|
||||
|
||||
// 筛选标签
|
||||
const filterTabs = [
|
||||
{ label: 'All', value: 'all' },
|
||||
{ label: 'Host', value: 'host' },
|
||||
{ label: 'Agent', value: 'agent' },
|
||||
{ label: 'Active', value: 'active' },
|
||||
{ label: 'Inactive', value: 'inactive' }
|
||||
]
|
||||
|
||||
// 成员数据
|
||||
const members = ref([
|
||||
{
|
||||
id: '1234567890',
|
||||
name: 'Alice Johnson',
|
||||
avatar: '',
|
||||
isHost: true,
|
||||
isAgent: false,
|
||||
isActive: true,
|
||||
target: 'A',
|
||||
activeDays: 25,
|
||||
salary: 450
|
||||
},
|
||||
{
|
||||
id: '0987654321',
|
||||
name: 'Bob Smith',
|
||||
avatar: '',
|
||||
isHost: false,
|
||||
isAgent: true,
|
||||
isActive: true,
|
||||
target: 'B',
|
||||
activeDays: 18,
|
||||
salary: 320
|
||||
},
|
||||
{
|
||||
id: '1122334455',
|
||||
name: 'Carol Davis',
|
||||
avatar: '',
|
||||
isHost: true,
|
||||
isAgent: false,
|
||||
isActive: false,
|
||||
target: 'A',
|
||||
activeDays: 8,
|
||||
salary: 120
|
||||
},
|
||||
{
|
||||
id: '5566778899',
|
||||
name: 'David Wilson',
|
||||
avatar: '',
|
||||
isHost: false,
|
||||
isAgent: true,
|
||||
isActive: true,
|
||||
target: 'C',
|
||||
activeDays: 30,
|
||||
salary: 580
|
||||
},
|
||||
{
|
||||
id: '9988776655',
|
||||
name: 'Eva Brown',
|
||||
avatar: '',
|
||||
isHost: false,
|
||||
isAgent: true,
|
||||
isActive: false,
|
||||
target: 'B',
|
||||
activeDays: 5,
|
||||
salary: 80
|
||||
},
|
||||
{
|
||||
id: '4433221100',
|
||||
name: 'Frank Miller',
|
||||
avatar: '',
|
||||
isHost: true,
|
||||
isAgent: false,
|
||||
isActive: true,
|
||||
target: 'A',
|
||||
activeDays: 22,
|
||||
salary: 390
|
||||
}
|
||||
])
|
||||
|
||||
// 过滤后的成员列表
|
||||
const filteredMembers = computed(() => {
|
||||
let filtered = members.value
|
||||
|
||||
// 搜索过滤
|
||||
if (searchQuery.value) {
|
||||
const query = searchQuery.value.toLowerCase()
|
||||
filtered = filtered.filter(member =>
|
||||
member.name.toLowerCase().includes(query) ||
|
||||
member.id.includes(query)
|
||||
)
|
||||
}
|
||||
|
||||
// 标签过滤
|
||||
switch (activeFilter.value) {
|
||||
case 'host':
|
||||
filtered = filtered.filter(member => member.isHost)
|
||||
break
|
||||
case 'agent':
|
||||
filtered = filtered.filter(member => member.isAgent)
|
||||
break
|
||||
case 'active':
|
||||
filtered = filtered.filter(member => member.isActive)
|
||||
break
|
||||
case 'inactive':
|
||||
filtered = filtered.filter(member => !member.isActive)
|
||||
break
|
||||
}
|
||||
|
||||
return filtered
|
||||
})
|
||||
|
||||
// 查看成员详情
|
||||
const viewMemberDetail = (member) => {
|
||||
alert(`View details for ${member.name}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.team-member {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 搜索部分 */
|
||||
.search-section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: white;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
margin-right: 8px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
background: none;
|
||||
outline: none;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
/* 筛选标签 */
|
||||
.filter-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.filter-tab {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 16px;
|
||||
background-color: white;
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.filter-tab.active {
|
||||
background-color: #8B5CF6;
|
||||
color: white;
|
||||
border-color: #8B5CF6;
|
||||
}
|
||||
|
||||
.filter-tab:not(.active):hover {
|
||||
border-color: #8B5CF6;
|
||||
color: #8B5CF6;
|
||||
}
|
||||
|
||||
/* 成员列表 */
|
||||
.member-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.member-item {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.member-item:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.member-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.member-avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
background-color: #8B5CF6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-right: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.member-avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.member-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.member-details h4 {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.member-details p {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.member-tags {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding: 2px 6px;
|
||||
border-radius: 8px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.host-tag {
|
||||
background-color: #E0E7FF;
|
||||
color: #5B21B6;
|
||||
}
|
||||
|
||||
.agent-tag {
|
||||
background-color: #DBEAFE;
|
||||
color: #1E40AF;
|
||||
}
|
||||
|
||||
.active-tag {
|
||||
background-color: #D1FAE5;
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
.inactive-tag {
|
||||
background-color: #FEE2E2;
|
||||
color: #DC2626;
|
||||
}
|
||||
|
||||
.member-stats {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
487
src/views/TransferView.vue
Normal file
487
src/views/TransferView.vue
Normal file
@ -0,0 +1,487 @@
|
||||
<template>
|
||||
<div class="transfer gradient-background-circles">
|
||||
<MobileHeader title="Transfer" />
|
||||
|
||||
<div class="content">
|
||||
<!-- 信息部分 -->
|
||||
<div class="info-section">
|
||||
<div class="info-header">
|
||||
<h3>Information</h3>
|
||||
<button class="details-btn" @click="showDetails">
|
||||
Details <span>></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="available-salary">
|
||||
<span>Available salary: </span>
|
||||
<span v-if="loading" class="loading-text">Loading...</span>
|
||||
<span v-else class="amount">{{ availableSalary }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 转账部分 -->
|
||||
<div class="transfer-section">
|
||||
<h3>Transfer to others</h3>
|
||||
|
||||
<!-- 收款人信息 -->
|
||||
<div v-if="selectedPayee.id" class="payee-info" @click="searchPayee">
|
||||
<div class="payee-details">
|
||||
<div class="payee-avatar">
|
||||
<img v-if="selectedPayee.avatar" :src="selectedPayee.avatar" :alt="selectedPayee.name" />
|
||||
<span v-else>{{ selectedPayee.name.charAt(0) }}</span>
|
||||
</div>
|
||||
<div class="payee-text">
|
||||
<h4>{{ selectedPayee.name }}</h4>
|
||||
<p>ID: {{ selectedPayee.account || selectedPayee.id }}</p>
|
||||
<div class="payee-tags">
|
||||
<span v-if="selectedPayee.isHost" class="tag host-tag">👑 Host</span>
|
||||
<span v-if="selectedPayee.isAgency" class="tag agency-tag">🏢 Agency</span>
|
||||
<span v-if="selectedPayee.hasSalary" class="tag salary-tag">💰 Salary</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="change-btn" @click.stop="clearPayee">Change</button>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 - 默认显示 -->
|
||||
<div v-else class="empty-payee-state" @click="searchPayee">
|
||||
<div class="search-placeholder">
|
||||
<div class="search-icon">🔍</div>
|
||||
<span>Search</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 转账金额选择 -->
|
||||
<div class="coins-grid">
|
||||
<div
|
||||
v-for="coin in coinOptions"
|
||||
:key="coin.id"
|
||||
@click="selectCoin(coin)"
|
||||
:class="['coin-item', { selected: selectedCoin === coin.id }]"
|
||||
>
|
||||
<div class="coin-icon">🪙</div>
|
||||
<div class="coin-amount">{{ coin.amount }}</div>
|
||||
<div class="coin-price">${{ coin.price }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 转账按钮 -->
|
||||
<button
|
||||
class="transfer-btn"
|
||||
@click="transfer"
|
||||
:disabled="!selectedCoin || !selectedPayee.id"
|
||||
>
|
||||
Transfer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MobileHeader from '../components/MobileHeader.vue'
|
||||
import { getBankBalance } from '../api/wallet.js'
|
||||
import { getSelectedPayee, clearSelectedPayee } from '../utils/payeeStore.js'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const availableSalary = ref('0')
|
||||
const selectedCoin = ref(null)
|
||||
const loading = ref(false)
|
||||
|
||||
// 选中的收款人
|
||||
const selectedPayee = reactive({
|
||||
id: null,
|
||||
account: '',
|
||||
name: '',
|
||||
avatar: '',
|
||||
type: '',
|
||||
isHost: false,
|
||||
isAgency: false,
|
||||
hasSalary: false
|
||||
})
|
||||
|
||||
// 初始化收款人信息
|
||||
const initializePayee = () => {
|
||||
const savedPayee = getSelectedPayee()
|
||||
if (savedPayee) {
|
||||
Object.assign(selectedPayee, savedPayee)
|
||||
}
|
||||
}
|
||||
|
||||
// 金币选项
|
||||
const coinOptions = reactive([
|
||||
{ id: 1, amount: 10000, price: 1 },
|
||||
{ id: 2, amount: 50000, price: 5 },
|
||||
{ id: 3, amount: 100000, price: 10 },
|
||||
{ id: 4, amount: 500000, price: 50 },
|
||||
{ id: 5, amount: 1000000, price: 100 },
|
||||
{ id: 6, amount: 2000000, price: 200 }
|
||||
])
|
||||
|
||||
// 获取银行余额
|
||||
const fetchBankBalance = async () => {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
const response = await getBankBalance()
|
||||
|
||||
if (response.status && typeof response.body === 'number') {
|
||||
// 格式化余额显示,保疙2位小数
|
||||
availableSalary.value = response.body.toFixed(2)
|
||||
} else {
|
||||
availableSalary.value = '0.00'
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch bank balance:', error)
|
||||
availableSalary.value = '0.00'
|
||||
// 可以显示错误提示
|
||||
alert('Failed to load balance. Please try again.')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 方法
|
||||
const showDetails = () => {
|
||||
router.push('/information-details')
|
||||
}
|
||||
|
||||
const searchPayee = () => {
|
||||
router.push('/search-payee')
|
||||
}
|
||||
|
||||
// 清除当前选中的收款人
|
||||
const clearPayee = () => {
|
||||
Object.assign(selectedPayee, {
|
||||
id: null,
|
||||
account: '',
|
||||
name: '',
|
||||
avatar: '',
|
||||
type: '',
|
||||
isHost: false,
|
||||
isAgency: false,
|
||||
hasSalary: false
|
||||
})
|
||||
clearSelectedPayee()
|
||||
}
|
||||
|
||||
const selectCoin = (coin) => {
|
||||
// 单选逻辑:如果已经选中相同的金币,则取消选择;否则选中新的金币
|
||||
if (selectedCoin.value === coin.id) {
|
||||
selectedCoin.value = null
|
||||
} else {
|
||||
selectedCoin.value = coin.id
|
||||
}
|
||||
}
|
||||
|
||||
const transfer = () => {
|
||||
if (!selectedCoin.value) {
|
||||
alert('Please select an amount first')
|
||||
return
|
||||
}
|
||||
|
||||
if (!selectedPayee.id) {
|
||||
alert('Please select a payee first')
|
||||
return
|
||||
}
|
||||
|
||||
const selectedCoinData = coinOptions.find(coin => coin.id === selectedCoin.value)
|
||||
alert(`Transfer ${selectedCoinData.amount} (${selectedCoinData.price} USD) to ${selectedPayee.name}`)
|
||||
|
||||
// 重置选择
|
||||
selectedCoin.value = null
|
||||
}
|
||||
|
||||
// 页面加载时获取银行余额和初始化收款人
|
||||
onMounted(() => {
|
||||
fetchBankBalance()
|
||||
initializePayee()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.transfer {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 信息部分 */
|
||||
.info-section {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.info-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.info-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.details-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.available-salary {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.available-salary .amount {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.available-salary .loading-text {
|
||||
font-weight: 500;
|
||||
color: #8B5CF6;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* 转账部分 */
|
||||
.transfer-section {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.transfer-section h3 {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 收款人信息 */
|
||||
.payee-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 20px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.payee-info:active {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.payee-details {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.payee-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 20px;
|
||||
background-color: #8B5CF6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-right: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.payee-avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.payee-text h4 {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.payee-text p {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.payee-tags {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.host-tag {
|
||||
background-color: #E0E7FF;
|
||||
color: #5B21B6;
|
||||
}
|
||||
|
||||
.agency-tag {
|
||||
background-color: #DBEAFE;
|
||||
color: #1E40AF;
|
||||
}
|
||||
|
||||
.salary-tag {
|
||||
background-color: #FEF3C7;
|
||||
color: #D97706;
|
||||
}
|
||||
|
||||
.change-btn {
|
||||
background-color: #8B5CF6;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.change-btn:active {
|
||||
background-color: #7C3AED;
|
||||
}
|
||||
|
||||
/* 空状态样式 */
|
||||
.empty-payee-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
border: 2px dashed #ddd;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.empty-payee-state:hover {
|
||||
border-color: #8B5CF6;
|
||||
background-color: #f8f5ff;
|
||||
}
|
||||
|
||||
.empty-payee-state:active {
|
||||
background-color: #f0e6ff;
|
||||
}
|
||||
|
||||
.search-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.search-placeholder span {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 金币网格 */
|
||||
.coins-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.coin-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16px 8px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.coin-item.selected {
|
||||
border-color: #8B5CF6;
|
||||
background-color: #F3F4F6;
|
||||
}
|
||||
|
||||
.coin-item:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.coin-icon {
|
||||
font-size: 24px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.coin-amount {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.coin-price {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 转账按钮 */
|
||||
.transfer-btn {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
background-color: #8B5CF6;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.transfer-btn:disabled {
|
||||
background-color: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.transfer-btn:not(:disabled):active {
|
||||
background-color: #7C3AED;
|
||||
}
|
||||
</style>
|
||||
18
vite.config.js
Normal file
18
vite.config.js
Normal file
@ -0,0 +1,18 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
vueDevTools(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
},
|
||||
},
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user