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