first commit
This commit is contained in:
50
src/views/components/workplace/index.vue
Normal file
50
src/views/components/workplace/index.vue
Normal file
@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<div class="m-auto mt-24px max-w-1000px">
|
||||
<Container title="推荐产品" class="body">
|
||||
<div class="flex flex-wrap">
|
||||
<Product
|
||||
v-for="product in products"
|
||||
:key="product.id"
|
||||
class="mt-20px ml-20px"
|
||||
:product="product"
|
||||
@refresh="getProductList"
|
||||
/>
|
||||
</div>
|
||||
</Container>
|
||||
<Container title="成功案例" class="body mt-24px">
|
||||
<div class="flex flex-wrap">
|
||||
<Case v-for="item in cases" :key="item.id" class="mt-20px ml-20px" :data="item"></Case>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import Container from '@/views/components/workplace/modules/container.vue';
|
||||
import Product from '@/views/components/workplace/modules/product.vue';
|
||||
import Case from '@/views/components/workplace/modules/case.vue';
|
||||
import { fetchProductList, fetchSuccessCaseList } from '@/api/all/index';
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
const products = ref([]);
|
||||
const cases = ref([]);
|
||||
onMounted(() => {
|
||||
getProductList();
|
||||
getSuccessCaseList();
|
||||
});
|
||||
const getProductList = async () => {
|
||||
products.value = await fetchProductList();
|
||||
};
|
||||
|
||||
const getSuccessCaseList = async () => {
|
||||
cases.value = await fetchSuccessCaseList();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.body {
|
||||
padding-left: 0;
|
||||
:deep(> .title) {
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
190
src/views/components/workplace/modules/case.vue
Normal file
190
src/views/components/workplace/modules/case.vue
Normal file
@ -0,0 +1,190 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<swiper
|
||||
:loop="props.data.files.length > 1"
|
||||
:autoplay="{
|
||||
delay: 2500,
|
||||
disableOnInteraction: false,
|
||||
}"
|
||||
pagination
|
||||
:modules="modules"
|
||||
class="carousel"
|
||||
>
|
||||
<swiper-slide v-for="(item, index) in props.data.files" :key="index" class="swiper-slide">
|
||||
<img v-if="props.data.type === Type.Image" :src="item" alt="" />
|
||||
<div v-if="props.data.type === Type.Video" class="position-relative">
|
||||
<video ref="videoRef" :src="item" />
|
||||
<div class="play flex item-center" @click="playVideo(index)">
|
||||
<img src="@/assets/play.svg" alt="" />
|
||||
<span>{{ Math.floor(durationMap[index] / 60) + ':' + Math.floor(durationMap[index] % 60) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</swiper-slide>
|
||||
</swiper>
|
||||
<div class="body">
|
||||
<img class="logo" :src="props.data.brand_logo" :alt="props.data.brand_name" />
|
||||
<h1 class="title">{{ props.data.title }}</h1>
|
||||
<p class="keywords-container">
|
||||
<span v-for="(item, index) in props.data.keywords" :key="index" class="keyword">{{ item }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="footer flex arco-row-justify-space-around flex-center">
|
||||
<div v-for="(item, index) in props.data.data" :key="index">
|
||||
<div class="value">
|
||||
{{ parseFloat(item.value) }}<span class="unit">{{ item.value.replace(parseFloat(item.value), '') }}</span>
|
||||
</div>
|
||||
<div class="label">{{ item.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Swiper, SwiperSlide } from 'swiper/vue';
|
||||
import 'swiper/css';
|
||||
import 'swiper/css/pagination';
|
||||
import { Pagination } from 'swiper/modules';
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
data: SuccessCase;
|
||||
}>();
|
||||
const modules = [Pagination];
|
||||
const videoRef = ref<HTMLVideoElement[] | null>(null);
|
||||
const durationMap = ref<number[]>([]);
|
||||
watch(videoRef, (videos) => {
|
||||
if (videos) {
|
||||
for (let i = 0; i < videos.length; i++) {
|
||||
videos[i].addEventListener('loadedmetadata', () => {
|
||||
durationMap.value[i] = videos[i]?.duration; // 获取时长并更新到响应式变量
|
||||
});
|
||||
videos[i].addEventListener('fullscreenchange', () => {
|
||||
if (!document.fullscreenElement) {
|
||||
videos[i].pause(); // 如果没有元素处于全屏状态,则暂停视频
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const playVideo = (index: number) => {
|
||||
if (videoRef.value) {
|
||||
videoRef.value[index].requestFullscreen();
|
||||
videoRef.value[index].play();
|
||||
}
|
||||
};
|
||||
|
||||
enum Type {
|
||||
Video = 0,
|
||||
Image = 1,
|
||||
}
|
||||
interface SuccessCase {
|
||||
id: number;
|
||||
type: Type;
|
||||
title: string;
|
||||
keywords: string[];
|
||||
files: string[];
|
||||
brand_name: string;
|
||||
brand_logo: string;
|
||||
data: { label: string; value: string }[];
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
width: 304px;
|
||||
height: 306px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--BG-300, rgba(230, 230, 232, 1));
|
||||
overflow: hidden;
|
||||
.carousel {
|
||||
width: 304px;
|
||||
height: 140px;
|
||||
img {
|
||||
width: 304px;
|
||||
height: 140px;
|
||||
}
|
||||
video {
|
||||
width: 304px;
|
||||
height: 140px;
|
||||
}
|
||||
.play {
|
||||
border-radius: 100px;
|
||||
width: 110px;
|
||||
height: 48px;
|
||||
border: 1px solid var(--BG-300, rgba(230, 230, 232, 1));
|
||||
position: absolute;
|
||||
top: 46px;
|
||||
left: 97px;
|
||||
background-color: #f4f4f6;
|
||||
img {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
margin-left: 7px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
span {
|
||||
font-family: HarmonyOS Sans SC, serif;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: var(--Text-4, rgba(147, 148, 153, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
.body {
|
||||
padding: 12px 20px;
|
||||
border-bottom: 1px solid var(--BG-300, rgba(230, 230, 232, 1));
|
||||
.logo {
|
||||
height: 14px;
|
||||
max-width: 100%;
|
||||
}
|
||||
.title {
|
||||
font-family: Alibaba PuHuiTi, serif;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
vertical-align: middle;
|
||||
margin-top: 8px;
|
||||
padding: 0;
|
||||
}
|
||||
.keywords-container {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
.keyword {
|
||||
font-family: Alibaba PuHuiTi, serif;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
color: var(--Text-4, rgba(147, 148, 153, 1));
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.footer {
|
||||
padding-top: 10px;
|
||||
.value {
|
||||
font-family: HarmonyOS Sans SC, serif;
|
||||
font-weight: 700;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
text-align: center;
|
||||
.unit {
|
||||
font-family: Alibaba PuHuiTi, serif;
|
||||
font-weight: 400;
|
||||
font-size: 10px;
|
||||
line-height: 24px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
.label {
|
||||
font-family: Alibaba PuHuiTi, serif;
|
||||
font-weight: 400;
|
||||
font-size: 10px;
|
||||
line-height: 100%;
|
||||
text-align: center;
|
||||
color: var(--Text-4, rgba(147, 148, 153, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
29
src/views/components/workplace/modules/container.vue
Normal file
29
src/views/components/workplace/modules/container.vue
Normal file
@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<h1 class="title">{{ props.title }}</h1>
|
||||
<div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
title: string;
|
||||
}>();
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
border: 1px solid var(--BG-300, rgba(230, 230, 232, 1));
|
||||
background: var(--BG-white, rgba(255, 255, 255, 1));
|
||||
padding: 16px 24px 20px 24px;
|
||||
}
|
||||
.title {
|
||||
font-family: Alibaba PuHuiTi, serif;
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
vertical-align: middle;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
</style>
|
||||
249
src/views/components/workplace/modules/product.vue
Normal file
249
src/views/components/workplace/modules/product.vue
Normal file
@ -0,0 +1,249 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="flex arco-row-justify-space-between flex-center">
|
||||
<img class="avatar" :src="props.product.image" :alt="props.product.name" />
|
||||
<a-tag v-if="props.product.status === Status.Enable" class="status status-enable">已开通</a-tag>
|
||||
<a-tag v-if="props.product.status === Status.Disable" class="status status-disable">未开通</a-tag>
|
||||
<a-tag v-if="props.product.status === Status.EXPIRED" class="status status-expired">已到期</a-tag>
|
||||
<a-tag v-if="props.product.status === Status.TRIAL_ENDS" class="status status-expired">试用结束</a-tag>
|
||||
<a-countdown
|
||||
v-if="props.product.status === Status.ON_TRIAL"
|
||||
class="status-on-trill"
|
||||
title="试用中"
|
||||
:value="1000 * (props.product.expired_at ?? 0)"
|
||||
:now="now()"
|
||||
format="D天H时m分s秒"
|
||||
/>
|
||||
</div>
|
||||
<div class="body">
|
||||
<h1 class="title">{{ props.product.name }}</h1>
|
||||
<p class="desc">
|
||||
{{ props.product.desc }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="footer flex arco-row-justify-start flex-center">
|
||||
<a-button
|
||||
v-if="props.product.status === Status.Enable || props.product.status === Status.ON_TRIAL"
|
||||
class="primary-button"
|
||||
type="primary"
|
||||
@click="gotoModule(props.product.menu_id)"
|
||||
>
|
||||
进入模块
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="props.product.status === Status.TRIAL_ENDS || props.product.status === Status.EXPIRED"
|
||||
class="primary-button"
|
||||
type="primary"
|
||||
@click="visible = true"
|
||||
>
|
||||
立即购买
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="props.product.status === Status.ON_TRIAL"
|
||||
class="outline-button"
|
||||
type="outline"
|
||||
@click="visible = true"
|
||||
>
|
||||
升级购买
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="props.product.status === Status.TRIAL_ENDS || props.product.status === Status.EXPIRED"
|
||||
class="outline-button"
|
||||
type="outline"
|
||||
@click="visible = true"
|
||||
>
|
||||
联系客服
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
focusLock
|
||||
title="试用产品"
|
||||
content="确定试用该产品吗?"
|
||||
@ok="handleTrial(props.product.id)"
|
||||
>
|
||||
<a-button v-if="props.product.status === Status.Disable" class="outline-button" type="outline">
|
||||
免费试用7天
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
<a-modal v-model:visible="visible">
|
||||
<template #title>
|
||||
扫描下面二维码联系客户
|
||||
</template>
|
||||
<div class="text-center">
|
||||
<img width="200" src="@/assets/customer-service.svg" alt="" />
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { now } from '@vueuse/core';
|
||||
import { trialProduct } from '@/api/all';
|
||||
import { useRouter } from 'vue-router';
|
||||
const props = defineProps<{
|
||||
product: Product;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
const visible = ref(false);
|
||||
const router = useRouter();
|
||||
|
||||
enum Status {
|
||||
Disable = 0, // 禁用
|
||||
Enable = 1, // 启用
|
||||
ON_TRIAL = 2, // 试用中
|
||||
EXPIRED = 3, // 已过期
|
||||
TRIAL_ENDS = 4, // 试用结束
|
||||
}
|
||||
|
||||
interface Product {
|
||||
id: number;
|
||||
status: Status;
|
||||
name: string;
|
||||
image: string;
|
||||
desc: string;
|
||||
menu_id: number;
|
||||
expired_at?: number;
|
||||
}
|
||||
|
||||
const handleTrial = async (id: any) => {
|
||||
await trialProduct(id);
|
||||
AMessage.success('试用成功!');
|
||||
emit('refresh');
|
||||
};
|
||||
|
||||
const gotoModule = (menuId: number) => {
|
||||
router.push({ name: 'dataEngine' });
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
width: 304px;
|
||||
height: 220px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(230, 230, 232, 1);
|
||||
padding: 20px;
|
||||
|
||||
.avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.status {
|
||||
height: 20px;
|
||||
border-radius: 4px;
|
||||
padding-right: 8px;
|
||||
padding-left: 8px;
|
||||
font-family: Alibaba PuHuiTi, serif;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.status-enable {
|
||||
background: rgba(235, 247, 242, 1);
|
||||
color: rgba(27, 174, 113, 1);
|
||||
}
|
||||
|
||||
.status-disable {
|
||||
background: rgba(242, 243, 245, 1);
|
||||
color: rgba(147, 148, 153, 1);
|
||||
}
|
||||
|
||||
.status-expired {
|
||||
background: rgba(255, 231, 228, 1);
|
||||
color: rgba(197, 60, 39, 1);
|
||||
}
|
||||
|
||||
.status-expired {
|
||||
background: rgba(255, 231, 228, 1);
|
||||
color: rgba(197, 60, 39, 1);
|
||||
}
|
||||
|
||||
.status-on-trill {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
height: 36px;
|
||||
border-radius: 4px;
|
||||
background: rgba(255, 245, 222, 1);
|
||||
|
||||
:deep(.arco-statistic-title) {
|
||||
font-family: Alibaba PuHuiTi, serif;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: var(--Functional-Warning-7, rgba(204, 139, 0, 1));
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:deep(.arco-statistic-value) {
|
||||
font-family: Alibaba PuHuiTi, serif;
|
||||
font-weight: 400;
|
||||
font-size: 10px;
|
||||
color: var(--Functional-Warning-7, rgba(204, 139, 0, 1));
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
height: 88px;
|
||||
margin-top: 12px;
|
||||
|
||||
.title {
|
||||
font-family: Alibaba PuHuiTi, serif;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
vertical-align: middle;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-family: Alibaba PuHuiTi, serif;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding-top: 16px;
|
||||
|
||||
.primary-button {
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
gap: 8px;
|
||||
padding: 2px 12px;
|
||||
background-color: rgba(109, 76, 254, 1) !important;
|
||||
font-family: Alibaba PuHuiTi, serif;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
text-align: right;
|
||||
vertical-align: middle;
|
||||
color: rgba(255, 255, 255, 1);
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.outline-button {
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
gap: 8px;
|
||||
padding: 2px 12px;
|
||||
border: 1px solid var(--Brand-Brand-6, rgba(109, 76, 254, 1));
|
||||
font-family: Alibaba PuHuiTi, serif;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
text-align: right;
|
||||
vertical-align: middle;
|
||||
color: rgba(109, 76, 254, 1);
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user