31 changed files with 5577 additions and 4003 deletions
File diff suppressed because it is too large
@ -0,0 +1,119 @@ |
|||||
|
version: '3.8' |
||||
|
|
||||
|
services: |
||||
|
# PHP 服务 |
||||
|
php: |
||||
|
image: php:8.2-fpm |
||||
|
container_name: niucloud_php |
||||
|
volumes: |
||||
|
- ./niucloud:/var/www/html |
||||
|
- ./docker/php/php.ini:/usr/local/etc/php/php.ini |
||||
|
- ./docker/php/www.conf:/usr/local/etc/php-fpm.d/www.conf |
||||
|
working_dir: /var/www/html |
||||
|
depends_on: |
||||
|
- mysql |
||||
|
- redis |
||||
|
environment: |
||||
|
- PHP_IDE_CONFIG=serverName=niucloud |
||||
|
command: > |
||||
|
bash -c " |
||||
|
apt-get update && |
||||
|
apt-get install -y libzip-dev zip unzip git libpng-dev libjpeg-dev libfreetype6-dev && |
||||
|
docker-php-ext-configure gd --with-freetype --with-jpeg && |
||||
|
docker-php-ext-install pdo pdo_mysql mysqli zip gd && |
||||
|
pecl install redis && |
||||
|
docker-php-ext-enable redis && |
||||
|
php-fpm |
||||
|
" |
||||
|
networks: |
||||
|
- niucloud_network |
||||
|
|
||||
|
# Nginx 服务 |
||||
|
nginx: |
||||
|
image: nginx:alpine |
||||
|
container_name: niucloud_nginx |
||||
|
ports: |
||||
|
- "20080:80" # 原本是 80 映射到 20080 |
||||
|
- "20081:8080" # 原本是 8080 映射到 20081 |
||||
|
volumes: |
||||
|
- ./niucloud:/var/www/html |
||||
|
- ./admin/dist:/var/www/admin |
||||
|
- ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf |
||||
|
- ./docker/nginx/conf.d:/etc/nginx/conf.d |
||||
|
- ./docker/logs/nginx:/var/log/nginx |
||||
|
depends_on: |
||||
|
- php |
||||
|
- node |
||||
|
networks: |
||||
|
- niucloud_network |
||||
|
|
||||
|
# MySQL 数据库 |
||||
|
mysql: |
||||
|
image: mysql:8.0 |
||||
|
container_name: niucloud_mysql |
||||
|
ports: |
||||
|
- "23306:3306" # 原本是 3306 映射到 23306 |
||||
|
environment: |
||||
|
MYSQL_ROOT_PASSWORD: root123456 |
||||
|
MYSQL_DATABASE: niucloud |
||||
|
MYSQL_USER: niucloud |
||||
|
MYSQL_PASSWORD: niucloud123 |
||||
|
volumes: |
||||
|
- ./docker/data/mysql:/var/lib/mysql |
||||
|
- ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf |
||||
|
- ./docker/logs/mysql:/var/log/mysql |
||||
|
command: --default-authentication-plugin=mysql_native_password |
||||
|
networks: |
||||
|
- niucloud_network |
||||
|
|
||||
|
# Redis 缓存 |
||||
|
redis: |
||||
|
image: redis:alpine |
||||
|
container_name: niucloud_redis |
||||
|
ports: |
||||
|
- "26379:6379" # 原本是 6379 映射到 26379 |
||||
|
volumes: |
||||
|
- ./docker/data/redis:/data |
||||
|
- ./docker/redis/redis.conf:/usr/local/etc/redis/redis.conf |
||||
|
command: redis-server /usr/local/etc/redis/redis.conf |
||||
|
networks: |
||||
|
- niucloud_network |
||||
|
|
||||
|
# Node.js 服务 (用于构建前端) |
||||
|
node: |
||||
|
image: node:18-alpine |
||||
|
container_name: niucloud_node |
||||
|
working_dir: /app |
||||
|
volumes: |
||||
|
- ./admin:/app |
||||
|
- ./docker/data/node_modules:/app/node_modules |
||||
|
ports: |
||||
|
- "23000:3000" # 原本是 3000 映射到 23000 |
||||
|
command: > |
||||
|
sh -c " |
||||
|
npm config set registry https://registry.npmmirror.com && |
||||
|
npm install && |
||||
|
npm run dev |
||||
|
" |
||||
|
networks: |
||||
|
- niucloud_network |
||||
|
|
||||
|
# Composer 服务 (用于 PHP 依赖管理) |
||||
|
composer: |
||||
|
image: composer:latest |
||||
|
container_name: niucloud_composer |
||||
|
volumes: |
||||
|
- ./niucloud:/app |
||||
|
working_dir: /app |
||||
|
command: install --ignore-platform-reqs |
||||
|
networks: |
||||
|
- niucloud_network |
||||
|
|
||||
|
networks: |
||||
|
niucloud_network: |
||||
|
driver: bridge |
||||
|
|
||||
|
volumes: |
||||
|
mysql_data: |
||||
|
redis_data: |
||||
|
node_modules: |
||||
@ -0,0 +1,268 @@ |
|||||
|
#!/bin/bash |
||||
|
|
||||
|
# 颜色输出函数 |
||||
|
print_message() { |
||||
|
echo -e "\033[1;32m[INFO] $1\033[0m" |
||||
|
} |
||||
|
|
||||
|
print_warning() { |
||||
|
echo -e "\033[1;33m[WARNING] $1\033[0m" |
||||
|
} |
||||
|
|
||||
|
print_error() { |
||||
|
echo -e "\033[1;31m[ERROR] $1\033[0m" |
||||
|
} |
||||
|
|
||||
|
# 设置项目名称 |
||||
|
PROJECT_NAME="MyApp" |
||||
|
|
||||
|
# 检查Docker和Docker Compose是否安装 |
||||
|
check_docker() { |
||||
|
if ! command -v docker &> /dev/null; then |
||||
|
print_error "Docker未安装,请先安装Docker" |
||||
|
exit 1 |
||||
|
fi |
||||
|
if ! command -v docker-compose &> /dev/null; then |
||||
|
print_error "Docker Compose未安装,请先安装Docker Compose" |
||||
|
exit 1 |
||||
|
fi |
||||
|
} |
||||
|
|
||||
|
# 创建必要的目录结构 |
||||
|
create_directories() { |
||||
|
print_message "创建必要的目录结构..." |
||||
|
mkdir -p docker/{logs/{nginx,mysql},data/{mysql,redis,node_modules},nginx/conf.d,php,mysql,redis} |
||||
|
} |
||||
|
|
||||
|
# 检查并生成配置文件 |
||||
|
check_config_files() { |
||||
|
print_message "检查必要的配置文件..." |
||||
|
|
||||
|
# Nginx 主配置 |
||||
|
if [ ! -f "docker/nginx/nginx.conf" ]; then |
||||
|
print_warning "Nginx主配置文件不存在,创建默认配置..." |
||||
|
cat > docker/nginx/nginx.conf << 'EOF' |
||||
|
user nginx; |
||||
|
worker_processes auto; |
||||
|
error_log /var/log/nginx/error.log warn; |
||||
|
pid /var/run/nginx.pid; |
||||
|
|
||||
|
events { |
||||
|
worker_connections 1024; |
||||
|
} |
||||
|
|
||||
|
http { |
||||
|
include /etc/nginx/mime.types; |
||||
|
default_type application/octet-stream; |
||||
|
|
||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" ' |
||||
|
'$status $body_bytes_sent "$http_referer" ' |
||||
|
'"$http_user_agent" "$http_x_forwarded_for"'; |
||||
|
|
||||
|
access_log /var/log/nginx/access.log main; |
||||
|
|
||||
|
sendfile on; |
||||
|
keepalive_timeout 65; |
||||
|
client_max_body_size 20M; |
||||
|
|
||||
|
include /etc/nginx/conf.d/*.conf; |
||||
|
} |
||||
|
EOF |
||||
|
fi |
||||
|
|
||||
|
# Nginx 站点配置 |
||||
|
if [ ! -f "docker/nginx/conf.d/default.conf" ]; then |
||||
|
print_warning "Nginx站点配置文件不存在,创建默认配置..." |
||||
|
cat > docker/nginx/conf.d/default.conf << 'EOF' |
||||
|
server { |
||||
|
listen 80; |
||||
|
server_name localhost; |
||||
|
root /var/www/html/public; |
||||
|
index index.php index.html; |
||||
|
|
||||
|
location / { |
||||
|
try_files $uri $uri/ /index.php?$query_string; |
||||
|
} |
||||
|
|
||||
|
location ~ \.php$ { |
||||
|
fastcgi_pass php:9000; |
||||
|
fastcgi_index index.php; |
||||
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; |
||||
|
include fastcgi_params; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
server { |
||||
|
listen 8080; |
||||
|
server_name localhost; |
||||
|
root /var/www/admin; |
||||
|
index index.html; |
||||
|
|
||||
|
location / { |
||||
|
try_files $uri $uri/ /index.html; |
||||
|
} |
||||
|
} |
||||
|
EOF |
||||
|
fi |
||||
|
|
||||
|
# PHP 配置 |
||||
|
if [ ! -f "docker/php/php.ini" ]; then |
||||
|
print_warning "PHP配置文件不存在,创建默认配置..." |
||||
|
cat > docker/php/php.ini << 'EOF' |
||||
|
[PHP] |
||||
|
memory_limit = 256M |
||||
|
upload_max_filesize = 20M |
||||
|
post_max_size = 20M |
||||
|
max_execution_time = 300 |
||||
|
date.timezone = Asia/Shanghai |
||||
|
|
||||
|
[opcache] |
||||
|
opcache.enable=1 |
||||
|
opcache.memory_consumption=128 |
||||
|
opcache.interned_strings_buffer=8 |
||||
|
opcache.max_accelerated_files=4000 |
||||
|
EOF |
||||
|
fi |
||||
|
|
||||
|
# PHP-FPM 配置 |
||||
|
if [ ! -f "docker/php/www.conf" ]; then |
||||
|
print_warning "PHP-FPM配置文件不存在,创建默认配置..." |
||||
|
cat > docker/php/www.conf << 'EOF' |
||||
|
[www] |
||||
|
user = www-data |
||||
|
group = www-data |
||||
|
listen = 9000 |
||||
|
pm = dynamic |
||||
|
pm.max_children = 5 |
||||
|
pm.start_servers = 2 |
||||
|
pm.min_spare_servers = 1 |
||||
|
pm.max_spare_servers = 3 |
||||
|
EOF |
||||
|
fi |
||||
|
|
||||
|
# MySQL 配置 |
||||
|
if [ ! -f "docker/mysql/my.cnf" ]; then |
||||
|
print_warning "MySQL配置文件不存在,创建默认配置..." |
||||
|
cat > docker/mysql/my.cnf << 'EOF' |
||||
|
[mysqld] |
||||
|
character-set-server = utf8mb4 |
||||
|
collation-server = utf8mb4_unicode_ci |
||||
|
default-authentication-plugin = mysql_native_password |
||||
|
max_allowed_packet = 64M |
||||
|
sql_mode = '' |
||||
|
|
||||
|
[client] |
||||
|
default-character-set = utf8mb4 |
||||
|
EOF |
||||
|
fi |
||||
|
|
||||
|
# Redis 配置 |
||||
|
if [ ! -f "docker/redis/redis.conf" ]; then |
||||
|
print_warning "Redis配置文件不存在,创建默认配置..." |
||||
|
cat > docker/redis/redis.conf << 'EOF' |
||||
|
requirepass niucloud123456 |
||||
|
appendonly yes |
||||
|
EOF |
||||
|
fi |
||||
|
} |
||||
|
|
||||
|
# 启动服务 |
||||
|
start_services() { |
||||
|
print_message "启动 ${PROJECT_NAME} 服务..." |
||||
|
docker-compose up -d |
||||
|
} |
||||
|
|
||||
|
# 停止服务 |
||||
|
stop_services() { |
||||
|
print_message "停止 ${PROJECT_NAME} 服务..." |
||||
|
docker-compose down |
||||
|
} |
||||
|
|
||||
|
# 重启服务 |
||||
|
restart_services() { |
||||
|
print_message "重启 ${PROJECT_NAME} 服务..." |
||||
|
docker-compose restart |
||||
|
} |
||||
|
|
||||
|
# 查看状态 |
||||
|
check_status() { |
||||
|
print_message "${PROJECT_NAME} 服务状态:" |
||||
|
docker-compose ps |
||||
|
} |
||||
|
|
||||
|
# 查看日志 |
||||
|
view_logs() { |
||||
|
service=$1 |
||||
|
if [ -z "$service" ]; then |
||||
|
print_message "查看所有服务日志..." |
||||
|
docker-compose logs |
||||
|
else |
||||
|
print_message "查看 $service 服务日志..." |
||||
|
docker-compose logs $service |
||||
|
fi |
||||
|
} |
||||
|
|
||||
|
# 初始化项目 |
||||
|
init_project() { |
||||
|
print_message "初始化 ${PROJECT_NAME} 项目..." |
||||
|
|
||||
|
# 检查是否存在 docker-compose.yml |
||||
|
if [ ! -f "docker-compose.yml" ]; then |
||||
|
print_error "缺少 docker-compose.yml 文件,请先创建!" |
||||
|
exit 1 |
||||
|
fi |
||||
|
|
||||
|
create_directories |
||||
|
check_config_files |
||||
|
|
||||
|
print_message "拉取Docker镜像..." |
||||
|
docker-compose pull |
||||
|
|
||||
|
print_message "项目初始化完成,现在可以启动服务了" |
||||
|
} |
||||
|
|
||||
|
# 显示帮助信息 |
||||
|
show_help() { |
||||
|
echo "用法: $0 [选项]" |
||||
|
echo "" |
||||
|
echo "选项:" |
||||
|
echo " start 启动所有服务" |
||||
|
echo " stop 停止所有服务" |
||||
|
echo " restart 重启所有服务" |
||||
|
echo " status 查看服务状态" |
||||
|
echo " logs 查看所有服务日志" |
||||
|
echo " logs <服务名> 查看指定服务日志" |
||||
|
echo " init 初始化项目" |
||||
|
echo " help 显示此帮助信息" |
||||
|
} |
||||
|
|
||||
|
# 主程序入口 |
||||
|
main() { |
||||
|
check_docker |
||||
|
|
||||
|
case "$1" in |
||||
|
start) |
||||
|
start_services |
||||
|
;; |
||||
|
stop) |
||||
|
stop_services |
||||
|
;; |
||||
|
restart) |
||||
|
restart_services |
||||
|
;; |
||||
|
status) |
||||
|
check_status |
||||
|
;; |
||||
|
logs) |
||||
|
view_logs "$2" |
||||
|
;; |
||||
|
init) |
||||
|
init_project |
||||
|
;; |
||||
|
help|*) |
||||
|
show_help |
||||
|
;; |
||||
|
esac |
||||
|
} |
||||
|
|
||||
|
main "$@" |
||||
@ -0,0 +1,70 @@ |
|||||
|
<template> |
||||
|
<fui-modal :buttons="[]" :width="width" :show="show" :maskClose="maskClose"> |
||||
|
<!-- 默认内容插槽 --> |
||||
|
<slot> |
||||
|
<!-- 默认内容 --> |
||||
|
</slot> |
||||
|
|
||||
|
<!-- 按钮插槽 --> |
||||
|
<view v-if="$slots.buttons" class="button-section"> |
||||
|
<slot name="buttons"></slot> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 关闭按钮 --> |
||||
|
<view v-if="showClose" class="fui-icon__close" @tap="handleClose"> |
||||
|
<fui-icon name="close" color="#B2B2B2" :size="48"></fui-icon> |
||||
|
</view> |
||||
|
</fui-modal> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
name: 'CustomModal', |
||||
|
props: { |
||||
|
// 是否显示弹窗 |
||||
|
show: { |
||||
|
type: Boolean, |
||||
|
default: false |
||||
|
}, |
||||
|
// 弹窗宽度 |
||||
|
width: { |
||||
|
type: [String, Number], |
||||
|
default: 600 |
||||
|
}, |
||||
|
// 是否显示关闭按钮 |
||||
|
showClose: { |
||||
|
type: Boolean, |
||||
|
default: true |
||||
|
}, |
||||
|
// 是否允许点击遮罩关闭 |
||||
|
maskClose: { |
||||
|
type: Boolean, |
||||
|
default: true |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
// 处理关闭事件 |
||||
|
handleClose() { |
||||
|
this.$emit('cancel', 'close') |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="less" scoped> |
||||
|
.button-section { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
flex-direction: column; |
||||
|
gap: 20rpx; |
||||
|
margin-top: 40rpx; |
||||
|
} |
||||
|
|
||||
|
.fui-icon__close { |
||||
|
position: absolute; |
||||
|
right: 24rpx; |
||||
|
top: 20rpx; |
||||
|
z-index: 1000; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,234 @@ |
|||||
|
<template> |
||||
|
<view class="container"> |
||||
|
<fui-button @click="showSuccessModal = true" text="显示成功弹窗"></fui-button> |
||||
|
<fui-button @click="showConfirmModal = true" text="显示确认弹窗"></fui-button> |
||||
|
<fui-button @click="showCustomModal = true" text="显示自定义弹窗"></fui-button> |
||||
|
|
||||
|
<!-- 成功弹窗示例 --> |
||||
|
<custom-modal |
||||
|
:show="showSuccessModal" |
||||
|
width="600" |
||||
|
@cancel="handleModalCancel" |
||||
|
> |
||||
|
<fui-icon name="checkbox-fill" :size="108" color="#09BE4F"></fui-icon> |
||||
|
<text class="fui-title">购买成功</text> |
||||
|
<text class="fui-descr">成功购买一张月卡,可免费阅读30天</text> |
||||
|
|
||||
|
<template #buttons> |
||||
|
<fui-button |
||||
|
text="我知道了" |
||||
|
width="240rpx" |
||||
|
height="72rpx" |
||||
|
:size="28" |
||||
|
radius="36rpx" |
||||
|
background="#FFB703" |
||||
|
borderWidth="0" |
||||
|
:margin="['0','0','24rpx']" |
||||
|
@click="handleButtonClick('success_confirm')" |
||||
|
/> |
||||
|
</template> |
||||
|
</custom-modal> |
||||
|
|
||||
|
<!-- 确认弹窗示例 --> |
||||
|
<custom-modal |
||||
|
:show="showConfirmModal" |
||||
|
width="500" |
||||
|
@cancel="handleModalCancel" |
||||
|
> |
||||
|
<fui-icon name="warning-fill" :size="108" color="#FF6B35"></fui-icon> |
||||
|
<text class="fui-title">确认删除</text> |
||||
|
<text class="fui-descr">删除后无法恢复,确定要继续吗?</text> |
||||
|
|
||||
|
<template #buttons> |
||||
|
<fui-button |
||||
|
text="取消" |
||||
|
width="200rpx" |
||||
|
height="72rpx" |
||||
|
:size="28" |
||||
|
radius="36rpx" |
||||
|
background="#F5F5F5" |
||||
|
color="#333" |
||||
|
borderWidth="0" |
||||
|
:margin="['0','0','12rpx']" |
||||
|
@click="handleButtonClick('confirm_cancel')" |
||||
|
/> |
||||
|
<fui-button |
||||
|
text="确定删除" |
||||
|
width="200rpx" |
||||
|
height="72rpx" |
||||
|
:size="28" |
||||
|
radius="36rpx" |
||||
|
background="#FF6B35" |
||||
|
borderWidth="0" |
||||
|
@click="handleButtonClick('confirm_delete')" |
||||
|
/> |
||||
|
</template> |
||||
|
</custom-modal> |
||||
|
|
||||
|
<!-- 自定义内容弹窗示例 --> |
||||
|
<custom-modal |
||||
|
:show="showCustomModal" |
||||
|
width="700" |
||||
|
:showClose="false" |
||||
|
@cancel="handleModalCancel" |
||||
|
> |
||||
|
<view class="custom-content"> |
||||
|
<text class="custom-title">支付二维码</text> |
||||
|
<view class="custom-form"> |
||||
|
<fui-qrcode :value="qrcode"></fui-qrcode> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<template #buttons> |
||||
|
<view class="button-row"> |
||||
|
<fui-button |
||||
|
text="发送二维码给用户" |
||||
|
width="200rpx" |
||||
|
height="72rpx" |
||||
|
:size="28" |
||||
|
radius="36rpx" |
||||
|
background="#007AFF" |
||||
|
borderWidth="0" |
||||
|
@click="handleButtonClick('custom_submit')" |
||||
|
/> |
||||
|
</view> |
||||
|
</template> |
||||
|
</custom-modal> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import CustomModal from './custom-modal.vue' |
||||
|
|
||||
|
export default { |
||||
|
components: { |
||||
|
CustomModal |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
showSuccessModal: false, |
||||
|
showConfirmModal: false, |
||||
|
showCustomModal: false, |
||||
|
qrcode: 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET', |
||||
|
formData: { |
||||
|
name: '', |
||||
|
phone: '' |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
// 处理弹窗取消事件 |
||||
|
handleModalCancel(type) { |
||||
|
console.log('弹窗取消:', type) |
||||
|
this.showSuccessModal = false |
||||
|
this.showConfirmModal = false |
||||
|
this.showCustomModal = false |
||||
|
}, |
||||
|
|
||||
|
// 处理按钮点击事件 |
||||
|
handleButtonClick(action) { |
||||
|
console.log('按钮点击:', action) |
||||
|
|
||||
|
switch(action) { |
||||
|
case 'success_confirm': |
||||
|
this.showSuccessModal = false |
||||
|
uni.showToast({ |
||||
|
title: '确认成功', |
||||
|
icon: 'success' |
||||
|
}) |
||||
|
break |
||||
|
|
||||
|
case 'confirm_cancel': |
||||
|
this.showConfirmModal = false |
||||
|
break |
||||
|
|
||||
|
case 'confirm_delete': |
||||
|
this.showConfirmModal = false |
||||
|
uni.showToast({ |
||||
|
title: '删除成功', |
||||
|
icon: 'success' |
||||
|
}) |
||||
|
break |
||||
|
|
||||
|
case 'custom_cancel': |
||||
|
this.showCustomModal = false |
||||
|
break |
||||
|
|
||||
|
case 'custom_submit': |
||||
|
if (!this.formData.name || !this.formData.phone) { |
||||
|
uni.showToast({ |
||||
|
title: '请填写完整信息', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
this.showCustomModal = false |
||||
|
uni.showToast({ |
||||
|
title: '提交成功', |
||||
|
icon: 'success' |
||||
|
}) |
||||
|
|
||||
|
// 重置表单 |
||||
|
this.formData = { |
||||
|
name: '', |
||||
|
phone: '' |
||||
|
} |
||||
|
break |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="less" scoped> |
||||
|
.container { |
||||
|
padding: 40rpx; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.fui-title { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
text-align: center; |
||||
|
margin: 20rpx 0; |
||||
|
} |
||||
|
|
||||
|
.fui-descr { |
||||
|
font-size: 28rpx; |
||||
|
color: #666; |
||||
|
text-align: center; |
||||
|
margin: 0 0 30rpx 0; |
||||
|
line-height: 1.5; |
||||
|
} |
||||
|
|
||||
|
.custom-content { |
||||
|
width: 100%; |
||||
|
padding: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.custom-title { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
text-align: center; |
||||
|
display: block; |
||||
|
margin-bottom: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.custom-form { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.button-row { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
gap: 40rpx; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,164 @@ |
|||||
|
<template> |
||||
|
<view class="dark-table-container"> |
||||
|
<view v-for="(row, rIdx) in data" :key="rIdx" class="card"> |
||||
|
<view class="card-title" @click="toggleExpand(rIdx)"> |
||||
|
{{ row.channel }} |
||||
|
<text class="expand-icon">{{ expanded[rIdx] ? '▲' : '▼' }}</text> |
||||
|
</view> |
||||
|
<view class="card-content"> |
||||
|
<view |
||||
|
class="card-row" |
||||
|
v-for="(col, cIdx) in getVisibleColumns(rIdx)" |
||||
|
:key="cIdx" |
||||
|
> |
||||
|
<view class="card-label"> |
||||
|
{{ col.label }} |
||||
|
<text v-if="col.tip" class="tip" @click.stop="showTip(col.tip)">?</text> |
||||
|
</view> |
||||
|
<view class="card-value">{{ row[col.key] || '-' }}</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view v-if="columns.length > 6" class="expand-btn" @click="toggleExpand(rIdx)"> |
||||
|
{{ expanded[rIdx] ? '收起' : '展开全部' }} |
||||
|
</view> |
||||
|
</view> |
||||
|
<!-- 说明弹窗 --> |
||||
|
<uni-popup ref="popup" type="center"> |
||||
|
<view class="popup-content">{{ tipContent }}</view> |
||||
|
</uni-popup> |
||||
|
<AQTabber></AQTabber> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import AQTabber from "@/components/AQ/AQTabber.vue" |
||||
|
|
||||
|
export default { |
||||
|
components: { |
||||
|
AQTabber, |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
columns: [ |
||||
|
{ label: '渠道', key: 'channel' }, |
||||
|
{ label: '资源数', key: 'resource', tip: '本月的新资源' }, |
||||
|
{ label: '到访数(一访)', key: 'visit1', tip: '本月资源的一访到访数' }, |
||||
|
{ label: '到访率(一访)', key: 'visitRate1', tip: '一访到访数/本月新资源数' }, |
||||
|
{ label: '到访数(二访)', key: 'visit2', tip: '二访到访数/一访到访数' }, |
||||
|
{ label: '到访率(二访)', key: 'visitRate2', tip: '二访到访数/一访到访数' }, |
||||
|
{ label: '关单数(一访)', key: 'close1', tip: '本月一访到访的关单数' }, |
||||
|
{ label: '关单率(一访)', key: 'closeRate1', tip: '一访关单数/一访到访数' }, |
||||
|
{ label: '关单数(二访)', key: 'close2', tip: '二访到访的关单数' }, |
||||
|
{ label: '关单率(二访)', key: 'closeRate2', tip: '二访关单数/二访到访数' }, |
||||
|
{ label: '月总转', key: 'monthTrans', tip: '关单数(合计)/资源数' }, |
||||
|
{ label: '往月到访数', key: 'lastMonthVisit', tip: '本月资源在往月到访的到访数' }, |
||||
|
{ label: '月共到访', key: 'monthTotalVisit', tip: '本月资源任在本月到访/关单' }, |
||||
|
{ label: '往月关单数', key: 'lastMonthClose', tip: '本月关单-往月关单' }, |
||||
|
{ label: '月共关单', key: 'monthTotalClose', tip: '本月关单+往月关单' }, |
||||
|
], |
||||
|
data: [ |
||||
|
{ channel: '体检包(地推)', resource: 10, visit1: 5, visitRate1: '50%', visit2: 2, visitRate2: '40%', close1: 1, closeRate1: '20%', close2: 1, closeRate2: '50%', monthTrans: '10%', lastMonthVisit: 0, monthTotalVisit: 5, lastMonthClose: 0, monthTotalClose: 1 }, |
||||
|
], |
||||
|
tipContent: '', |
||||
|
expanded: {}, |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
showTip(content) { |
||||
|
this.tipContent = content |
||||
|
this.$refs.popup.open() |
||||
|
}, |
||||
|
toggleExpand(idx) { |
||||
|
this.$set(this.expanded, idx, !this.expanded[idx]) |
||||
|
}, |
||||
|
getVisibleColumns(idx) { |
||||
|
// 只展示前5个字段,展开后展示全部(不含渠道) |
||||
|
const cols = this.columns.slice(1) |
||||
|
if (this.expanded[idx]) return cols |
||||
|
return cols.slice(0, 5) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.dark-table-container { |
||||
|
background: #18181c; |
||||
|
color: #f1f1f1; |
||||
|
min-height: 100vh; |
||||
|
padding: 24rpx 12rpx; |
||||
|
} |
||||
|
.card { |
||||
|
background: #23232a; |
||||
|
border-radius: 18rpx; |
||||
|
box-shadow: 0 4rpx 24rpx rgba(0,0,0,0.18); |
||||
|
margin-bottom: 32rpx; |
||||
|
padding: 24rpx 20rpx 8rpx 20rpx; |
||||
|
transition: box-shadow 0.2s; |
||||
|
} |
||||
|
.card-title { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: bold; |
||||
|
margin-bottom: 18rpx; |
||||
|
color: #ffb300; |
||||
|
letter-spacing: 2rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
.expand-icon { |
||||
|
font-size: 24rpx; |
||||
|
color: #bdbdbd; |
||||
|
margin-left: 12rpx; |
||||
|
} |
||||
|
.card-content { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 12rpx; |
||||
|
overflow: hidden; |
||||
|
transition: max-height 0.3s; |
||||
|
} |
||||
|
.card-row { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
border-bottom: 1px dashed #333; |
||||
|
padding: 8rpx 0; |
||||
|
} |
||||
|
.card-label { |
||||
|
color: #bdbdbd; |
||||
|
font-size: 26rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
.card-value { |
||||
|
color: #f1f1f1; |
||||
|
font-size: 26rpx; |
||||
|
text-align: right; |
||||
|
max-width: 60%; |
||||
|
word-break: break-all; |
||||
|
} |
||||
|
.tip { |
||||
|
color: #ffb300; |
||||
|
margin-left: 8rpx; |
||||
|
font-size: 24rpx; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
.expand-btn { |
||||
|
color: #ffb300; |
||||
|
text-align: center; |
||||
|
font-size: 26rpx; |
||||
|
margin: 10rpx 0 0 0; |
||||
|
padding-bottom: 8rpx; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
.popup-content { |
||||
|
background: #23232a; |
||||
|
color: #fff; |
||||
|
padding: 32rpx; |
||||
|
border-radius: 16rpx; |
||||
|
min-width: 300rpx; |
||||
|
text-align: center; |
||||
|
} |
||||
|
</style> |
||||
Loading…
Reference in new issue