master
ihzero 2023-08-16 18:18:19 +08:00
parent a3474242a4
commit c0da01e9d1
14 changed files with 893 additions and 20 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,11 +1,11 @@
import axios from 'axios'
import { localCache } from '@/io/cache'
import { message } from 'ant-design-vue'
import { showToast } from 'vant';
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 200000,
withCredentials: true
withCredentials: false
})
@ -36,7 +36,7 @@ service.interceptors.response.use(
return res
} else {
if (res.status != 0) {
message.error(res.msg || 'Error')
showToast(res.msg || 'Error')
return Promise.reject(res.message || 'Error')
} else {
return res.data
@ -51,11 +51,11 @@ service.interceptors.response.use(
// localCache.remove('auth');
// localCache.remove('userInfo');
}
message.error(res.errmsg || 'Error')
showToast(res.errmsg || 'Error')
return Promise.reject(res)
} else {
message.error(res.errmsg || 'Error')
showToast(res.errmsg || 'Error')
return Promise.reject(error)
}

View File

@ -34,6 +34,22 @@ const router = createRouter({
},
component: () => import("@/views/macroeconomics/index.vue"),
},
{
path: "trend",
name: "Trend",
meta: {
title: "AI商情-洞见趋势详情",
},
component: () => import("@/views/macroeconomics/trend.vue"),
},
{
path: "vip",
name: "Vip",
meta: {
title: "会员专区",
},
component: () => import("@/views/vip/index.vue"),
},
{
path: "ucenter",
name: "ucenter",

View File

@ -1,8 +1,19 @@
<template>
<div>
<div class="text-24px text-white opacity-40">2023.06.12</div>
<div class="mt-31px text-22px text-white leading-34px">
大自然是人类赖以生存发展的基本条件尊重自然顺应自然保护自然是全面建设社会主
<div class="text-24px text-white opacity-40">
{{ formatDate(data.time, 'yyyy.MM.dd') }}
</div>
<div class="mt-31px text-22px text-white leading-34px line-clamp-2">
{{ data.content }}
</div>
</div>
</template>
</template>
<script setup>
import { formatDate } from '@/utils/format.js'
const props = defineProps({
data: {
type: Object,
default: () => ({}),
},
})
</script>

View File

@ -0,0 +1,32 @@
<template>
<div
@click="handleClick"
:style="{ backgroundImage: `url(${data.picture})` }"
class="h-full rounded-4px cursor-pointer bg-gray-500 bg-opacity-10 bg-img"
></div>
</template>
<script setup>
import { useRouter } from 'vue-router'
const props = defineProps({
data: {
type: Object,
default: () => ({}),
},
})
const router = useRouter()
const handleClick = () => {
const { link_config } = props.data
if (link_config.target_url) {
router.push(link_config.target_url)
}
}
</script>
<style scoped lang="scss">
.bg-img{
background-size: cover;
background-repeat: no-repeat;
background-position: center;
}
</style>

View File

@ -1,16 +1,14 @@
<template>
<div class="px-34px">
<div class="border border-[#A6A8AF] rounded-6px p-24px mt-62px">
<Title title="每周大事件" sub="new"></Title>
<van-divider class="my-20px" />
<div>
<NewsItem v-for="(item, i) in 3" class="my-48px" :key="i"></NewsItem>
</div>
</div>
<div class="border border-[#A6A8AF] rounded-6px p-24px mt-33px">
<Title title="内容分类"></Title>
<div class="grid grid-cols-2 gap-x-21px gap-y-27px py-37px">
<CardItem :data="{ name: '111' }"></CardItem>
<CardItem
v-for="(item, i) in contentTypes"
:key="i"
:data="item"
></CardItem>
</div>
<Title title="洞察趋势"></Title>
<div class="h-300px">
@ -31,27 +29,126 @@
class="h-full"
>
<swiper-slide
v-for="(item, i) in 3"
v-for="(item, i) in trendsList"
:key="i"
:data="item"
class="text-black relative bg-white !w-427px"
>
<div>{{ item }}</div>
<TrendItem :data="item">{{ item }}</TrendItem>
</swiper-slide>
</swiper>
</div>
</div>
<div
class="border border-[#A6A8AF] rounded-6px py-24px mt-62px max-h-1/2 overflow-hidden flex flex-col"
>
<div class="px-24px">
<Title title="每周大事件" sub="new"></Title>
</div>
<van-divider class="my-20px" />
<div class=" px-24px">
<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
@load="weekNewOnLoad"
>
<NewsItem
v-for="(item, i) in weekNewsList"
class="my-48px"
:key="i"
:data="item"
></NewsItem>
</van-list>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import http from '@/io/request'
import Title from './components/title.vue'
import NewsItem from './components/news-item.vue'
import CardItem from './components/card-item.vue'
import TrendItem from './components/trends-item.vue'
import { Swiper, SwiperSlide } from 'swiper/vue'
import { Mousewheel, Navigation, EffectCoverflow } from 'swiper/modules'
import 'swiper/scss'
import 'swiper/scss/mousewheel'
import 'swiper/css/effect-coverflow'
import 'swiper/css/navigation';
import 'swiper/css/navigation'
const modules = [Mousewheel, Navigation, EffectCoverflow]
const pagetSize = 100
const weekNewsPage = ref(1)
const weekNewsList = ref([])
const contentTypes = ref([])
const trendsList = ref([])
const loading = ref(false)
const finished = ref(false)
const weekNewOnLoad = () => {
http
.get('/api/topic', {
params: {
per_page: pagetSize,
page: weekNewsPage.value,
},
})
.then((res) => {
loading.value = false
const { total, data } = res
if (weekNewsPage.value === 1) weekNewsList.value = []
weekNewsList.value = weekNewsList.value.concat(data)
if (total <= weekNewsList.value.length) finished.value = true
weekNewsPage.value++
})
}
function getContentTypes() {
http
.get('/api/keywords', {
params: {
type_key: 'government',
},
})
.then((res) => {
contentTypes.value = res
})
}
function getTrendsList() {
http
.get('/api/banner', {
params: {
key: 'pc_trend_recommend',
},
})
.then((res) => {
trendsList.value = res
})
}
onMounted(() => {
getTrendsList()
getContentTypes()
})
</script>
<style lang="scss">
.swiper-button-prev,.swiper-button-next{
transform: translateY(-50%);
width: 60px;
height: 60px;
border: 1px solid white;
border-radius: 50%;
&::after{
font-size: 30px;
color: white;
}
}
</style>

View File

@ -0,0 +1,345 @@
<template>
<div class="px-30px">
<div class="flex items-center justify-center mt-54px" v-if="sortList.length">
<svg-icon
@click="handlePrev"
class="text-white text-53px transform -rotate-90 cursor-pointer"
:class="{ 'opacity-40': currentSortIndex === 0 }"
name="收起箭头小2"
></svg-icon>
<div class="text-27px font-bold min-w-150px text-center">
<span class="text-white">{{ currentSort.name }}</span>
</div>
<svg-icon
@click="handleNext"
class="text-white text-53px transform rotate-90 cursor-pointer"
name="收起箭头小2"
:class="{ 'opacity-40': currentSortIndex === sortList.length - 1 }"
></svg-icon>
</div>
<div class="h-533px w-full border-1px border-[#A6A8AF] mt-3">
<div class="w-full h-full" id="myEcharts"></div>
</div>
<div class="w-full grid grid-cols-2 gap-x-27px gap-y-18px my-50px">
<div
@click="changeChart(i)"
class="border-1px border-[#A6A8AF] rounded-4px text-center h-72px text-25px flex items-center justify-center cursor-pointer"
v-for="(item, i) in chartList"
:class="{
'bg-[#3662FE] text-white border-[#3662FE]': i === chartIndex,
}"
:key="i"
>
{{ item.title }}
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, computed, toRaw } from 'vue'
import { useRoute } from 'vue-router'
import * as echarts from 'echarts'
import http from '@/io/request'
let myEcharts = echarts
let chartDom = null
const route = useRoute()
const id = ref(route.query.id)
const currentSortIndex = ref(0)
const chartIndex = ref(0)
const sortList = ref([])
const chartList = ref([])
const currentSort = computed(() => sortList.value[currentSortIndex.value])
const currentChart = computed(() => chartList.value[chartIndex.value])
const handleNext = () => {
if (currentSortIndex.value < sortList.value.length - 1) {
currentSortIndex.value++
}
changeSort()
}
const handlePrev = () => {
if (currentSortIndex.value > 0) {
currentSortIndex.value--
}
changeSort()
}
const changeChart = (index) => {
if (chartIndex.value === index) return
chartIndex.value = index
initChart()
}
const changeSort = () => {
getTrend()
}
const getTrend = () => {
http
.get(`/api/trend`, {
params: {
category_id: currentSort.value.id,
per_page: 100,
page: 1,
},
})
.then((res) => {
chartIndex.value = 0
chartList.value = res.data
// chartList.value[1].
initChart()
})
}
const getSorts = () => {
http
.get(`/api/keywords`, {
params: {
type_key: 'trend',
},
})
.then((res) => {
sortList.value = res
getTrend()
})
}
onMounted(() => {
chartDom = myEcharts.init(document.getElementById('myEcharts'))
getSorts()
})
const setOption = (options) => {
if (chartDom) {
chartDom.setOption(options)
}
}
const initChart = () => {
const data = toRaw(currentChart.value)
if (data) {
const { chart_options } = data
if (chart_options.type == 'line') lineChart(data)
if (chart_options.type == 'bar') barChart(data)
}
}
const lineChart = (data) => {
const { chart_data, chart_options, title } = data
const xAxis = chart_data.xAxis.list
const series = chart_data.data.map((e) => {
return {
name: e.name,
type: chart_options.type,
color: '#3662FE',
smooth: true,
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(
0,
0,
0,
1,
[
{
offset: 0,
color: 'rgba(54, 98, 254, 0.3)',
},
{
offset: 0.8,
color: 'rgba(54, 98, 254, 0)',
},
],
false
),
shadowColor: 'rgba(54, 98, 254, 0.1)',
shadowBlur: 10,
},
},
symbol: 'circle',
symbolSize: 5,
data: e.list,
}
})
setOption({
grid: {
left: '2%',
right: '3%',
top: '22%',
bottom: '2%',
containLabel: true,
},
tooltip: {
trigger: 'axis',
},
title: {
text: title,
left: '2%',
top: '3%',
textStyle: {
color: '#fff',
fontSize: 18,
fontWeight: 'bold',
},
},
xAxis: {
type: 'category',
boundaryGap: false,
data: xAxis,
axisLabel: {
color: 'rgba(255, 255, 255, .4)',
fontSize: 14,
},
axisLine: {
show: true,
lineStyle: {
color: 'rgba(65, 69, 72, 1)',
},
},
},
yAxis: {
type: 'value',
name: '单位',
position: 'left',
nameTextStyle: {
color: 'rgba(255, 255, 255, .4)',
fontSize: 14,
},
axisLabel: {
color: 'rgba(255, 255, 255, .4)',
fontSize: 14,
},
axisLine: {
show: true,
lineStyle: {
color: 'rgba(65, 69, 72, 1)',
},
},
splitLine: {
lineStyle: {
type: 'dashed',
color: 'rgba(255, 255, 255, .4)',
},
},
},
series: series,
})
}
const barChart = (data) => {
const { chart_data, chart_options, title } = data
const xAxis = chart_data.xAxis.list
const series = chart_data.data.map((e) => {
return {
name: e.name,
type: chart_options.type,
color: '#3662FE',
smooth: true,
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(
0,
0,
0,
1,
[
{
offset: 0,
color: 'rgba(54, 98, 254, 0.3)',
},
{
offset: 0.8,
color: 'rgba(54, 98, 254, 0)',
},
],
false
),
shadowColor: 'rgba(54, 98, 254, 0.1)',
shadowBlur: 10,
},
},
symbol: 'circle',
symbolSize: 5,
data: e.list,
}
})
setOption({
grid: {
left: '2%',
right: '3%',
top: '22%',
bottom: '2%',
containLabel: true,
},
tooltip: {
trigger: 'axis',
},
title: {
text: title,
left: '2%',
top: '3%',
textStyle: {
color: '#fff',
fontSize: 18,
fontWeight: 'bold',
},
},
xAxis: {
type: 'category',
boundaryGap: false,
data: xAxis,
axisLabel: {
color: 'rgba(255, 255, 255, .4)',
fontSize: 14,
},
axisLine: {
show: true,
lineStyle: {
color: 'rgba(65, 69, 72, 1)',
},
},
},
yAxis: {
type: 'value',
name: '单位',
position: 'left',
nameTextStyle: {
color: 'rgba(255, 255, 255, .4)',
fontSize: 14,
},
axisLabel: {
color: 'rgba(255, 255, 255, .4)',
fontSize: 14,
},
axisLine: {
show: true,
lineStyle: {
color: 'rgba(65, 69, 72, 1)',
},
},
splitLine: {
lineStyle: {
type: 'dashed',
color: 'rgba(255, 255, 255, .4)',
},
},
},
series: series,
})
}
</script>

View File

@ -0,0 +1,222 @@
<template>
<div
class="bg w-full h-1229px"
:style="{ backgroundImage: `url('${current.bg}')` }"
>
<div class="text-39px font-bold pt-20px ml-110px text-[#19191A]">
{{ data.name }}
</div>
<div class="ml-78px mt-54px">
<span class="text-[#E73434] text-157px font-bold">{{
currentAttr.price
}}</span>
<span class="text-[#19191A] ml-28px text-39px font-bold"></span>
</div>
<div class="px-50px">
<div class="mt-28px flex items-center justify-between">
<div
class="bg-[#E73434] text-white text-28px font-bold rounded-4px px-18px h-43px flex items-center"
>
{{ currentAttr.discount }}
</div>
<div class="text-28px font-bold text-white line-through">
原价 {{ currentAttr.origin_price }}
</div>
</div>
<div class="mt-20px">
<van-dropdown-menu v-model="value" :overlay="false">
<van-dropdown-item title-class="w-299px" :options="options" />
</van-dropdown-menu>
<!-- <a-select
class="w-full cu-select"
v-model:value="value"
:options="options"
popupClassName="cu-select-popup"
:dropdownStyle="{
backgroundColor: '#A5937C',
color: 'white',
}"
></a-select> -->
</div>
<div
class="mt-80px text-35px text-[#886E43] font-bold"
:style="{ color: current.color }"
>
权益内容
</div>
<van-divider class="my-4 border-[#FFF4E0]" />
<div class="flex justify-between items-center text-31px text-[#232323]">
<div>字数上限</div>
<div>{{ formatNumber(data.equity.words) }}</div>
</div>
<van-divider class="my-4 border-[#FFF4E0]" />
<div
class="text-[#886E43] text-35px font-bold"
:style="{ color: current.color }"
>
更多权益
</div>
<div
:style="{ backgroundColor: current.color + 20 }"
v-html="data.description_html"
class="mt-20px bg-[#C1B9AF] py-24px px-20px text-[#695F56] h-248px text-31px"
></div>
<div
class="grid gap-x-5 mt-40px"
:class="[currentAttr.is_subscribe ? 'grid-cols-2' : 'grid-cols-1']"
>
<van-button
@click="handleSubscribe(0)"
type="primary"
:style="{ backgroundColor: current.color }"
class="bg-[#786B56] h-79px flex items-center justify-center text-white font-bold mt-7 rounded-4px !border-none"
>订阅</van-button
>
<van-button
@click="handleSubscribe(1)"
type="primary"
v-if="currentAttr.is_subscribe"
:style="{ backgroundColor: current.color }"
class="bg-[#786B56] h-79px flex items-center justify-center text-white font-bold mt-7 rounded-4px !border-none"
>连续订阅</van-button
>
</div>
</div>
</div>
</template>
<script setup>
import { computed, ref, toRaw } from 'vue'
import v1 from '@/assets/images/vip_card-01.png'
import v2 from '@/assets/images/vip_card-02.png'
import v3 from '@/assets/images/vip_card-03.png'
import v4 from '@/assets/images/vip_card-04.png'
const emit = defineEmits(['subscribe'])
const list = [
{
name: '试用版',
bg: v1,
color: '#786B56',
},
{
name: '普惠版',
bg: v2,
color: '#886E43',
},
{
name: '高级版',
bg: v3,
color: '#886E43',
},
{
name: '高阶版',
bg: v3,
color: '#886E43',
},
{
name: '专业版',
bg: v4,
color: '#A14739',
},
]
const props = defineProps({
data: {
type: Object,
default: () => ({}),
},
})
const value = ref('')
const current = computed(() => {
return list.find((item) => item.name == props.data.name) ?? {}
})
const moreEquity = computed(() => {
let text = ''
if (props.data.equity.pdf) {
text += '上传pdf文件、'
}
if (props.data.equity.online) {
text += `在线人数 ${props.data.equity.online}`
}
if (props.data.equity.article) {
text += `阅读文章数 ${props.data.equity.article}`
}
return text.replace(/、+$/, '')
})
const options = computed(() => {
const arr = props.data.attrs.map((item, i) => {
return {
...item,
text: item.name,
value: i,
}
})
if (value.value == '' && arr.length > 0) {
value.value = arr[0].value
}
return arr
})
const currentAttr = computed(() => {
return options.value.find((item) => item.value == value.value) ?? {}
})
const handleSubscribe = (e) => {
emit(
'subscribe',
{
data: toRaw(props.data),
value: value.value,
isSerial: e,
},
e == 1
)
}
function formatNumber(num, digits = 0) {
if (num < 10000) {
return num.toString()
} else {
return (num / 10000).toFixed(digits) + '万'
}
}
</script>
<style scoped lang="scss">
.bg {
// background: url('@/assets/images/vip_card-01.png');
background-repeat: no-repeat;
background-size: 100% 100%;
}
::v-deep(.cu-select) {
.ant-select-selector {
background-color: #a7957e !important;
border-color: #a7957e;
color: white;
}
.ant-select-arrow {
color: white;
}
}
</style>
<style lang="scss">
.cu-select-popup {
&.ant-select-dropdown
.ant-select-item-option-active:not(.ant-select-item-option-disabled) {
background-color: #847663;
}
&.ant-select-dropdown
.ant-select-item-option-selected:not(.ant-select-item-option-disabled) {
background-color: #847663;
}
&.ant-select-dropdown .ant-select-item {
color: white;
}
}
</style>

View File

@ -0,0 +1,150 @@
<template>
<div>
<div class="flex items-center justify-center text-4xl font-bold mt-54px">
<img
class="w-120px"
src="@/assets/images/vip_title-left.png"
alt=""
srcset=""
/>
<div class="text-40px font-bold text-white px-10px">会员收费标准</div>
<img
class="w-120px"
src="@/assets/images/vip_title-right.png"
alt=""
srcset=""
/>
</div>
<div class="flex justify-center mt-60px">
<div class="flex items-center justify-center border border-white">
<div
class="h-62px leading-60px w-200px text-white text-28px text-center -m-2px"
v-for="(item, i) in vipType"
@click="active = item.id"
:class="{ 'bg-[#3662FE]': active == item.id }"
>
{{ item.name }}
</div>
</div>
</div>
<div class="px-51px mt-60px">
<Item
class="mb-70px"
v-for="item in currentVipList"
:key="item.id"
:data="item"
@subscribe="handleSubscribe"
></Item>
</div>
</div>
</template>
<script setup>
// import { QRCode } from 'ant-design-vue'
import { ref, onMounted, computed, toRaw } from 'vue'
import http from '@/io/request'
import Item from './components/item.vue'
// import { message } from 'ant-design-vue'
import { useRouter } from 'vue-router'
const router = useRouter()
// const AQrcode = QRCode
const active = ref(null)
const loading = ref(false)
const open = ref(false)
const vipType = ref([])
const vipList = ref([])
const vipValue = ref(null)
const orderInfo = ref(null)
const currentVipType = computed(() => {
return vipType.value.find((item) => item.id == active.value) ?? {}
})
const orderQrcodeUrl = computed(() => {
return orderInfo.value?.qr_code ?? ''
})
const currentVipList = computed(() => {
return vipList.value.filter((item) => item.type_id == active.value)
})
const currentVipText = computed(() => {
if (!vipValue.value) return ''
const { value, data, isSerial } = toRaw(vipValue.value) ?? {}
const { attrs, name } = data ?? {}
const str = isSerial ? '连续订阅' : `${attrs[value].time}`
return `${name}${str}`
})
const getVipType = () => {
http
.get('/api/keywords', {
params: {
type_key: 'product_type',
},
})
.then((res) => {
vipType.value = res
if (active.value == null && vipType.value.length > 0) {
active.value = vipType.value[0].id
}
})
}
const getVipList = () => {
http.get('/api/product').then((res) => {
vipList.value = res
})
}
const handleSubscribe = (e) => {
vipValue.value = e
if (loading.value) return
loading.value = true
const { data, value } = e
const params = {
product_id: data.id,
attr: value,
pay: true,
}
http
.post('/api/order', {
...params,
})
.then((res) => {
open.value = true
loading.value = false
orderInfo.value = res
})
.catch(() => {
loading.value = false
})
}
const handleConfirmOrder = () => {
if (loading.value) return
open.value = false
router.push({
path: '/ucenter/order',
})
}
const handleCancelOrder = () => {
if (loading.value) return
open.value = false
}
onMounted(() => {
getVipList()
getVipType()
})
</script>
<style lang="scss"></style>