initiate
This commit is contained in:
90
src/views/comment/detail.vue
Normal file
90
src/views/comment/detail.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<div class="normal-title f-b text-center inboxTitle p-10">{{ workInbox.inboxTitle }}</div>
|
||||
<div class="text-center">
|
||||
<van-tag type="primary" size="medium" v-if="workInbox.stockCode">
|
||||
{{ workInbox.stockCode }}【{{ workInbox.stockName }}】
|
||||
</van-tag>
|
||||
<van-tag type="success" size="medium" class="ms-10">{{ workInbox.inboxTypeSecondName }}</van-tag>
|
||||
</div>
|
||||
<div class="content text-end mb-5 pe-16">{{ workInbox.firstChargeUserName }}</div>
|
||||
<div class="content text-end pe-16">{{ workInbox.createTime }}</div>
|
||||
<div v-html="workInbox.inboxContent" class="p-16 inboxContent content"></div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import emitter from '@/utils/mitt'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const init = () => {
|
||||
if (route.query && route.query.inboxId) {
|
||||
emitter.emit('showLoading', '')
|
||||
let inboxId = route.query.inboxId
|
||||
getWorkInboxDetail(inboxId)
|
||||
} else {
|
||||
router.push({ name: '404' })
|
||||
}
|
||||
}
|
||||
|
||||
let workInbox = ref({
|
||||
inboxTitle: '',
|
||||
stockCode: '',
|
||||
stockName: '',
|
||||
inboxTypeSecondName: '',
|
||||
firstChargeUserName: '',
|
||||
createTime: '',
|
||||
inboxContent: ''
|
||||
})
|
||||
|
||||
import { workInboxInfo } from '@/utils/api'
|
||||
const getWorkInboxDetail = async (inboxId: any) => {
|
||||
const { data } = await workInboxInfo(inboxId)
|
||||
workInbox.value = data.workInbox
|
||||
emitter.emit('setTitle', { title: workInbox.value.inboxTitle, type: 'comment' })
|
||||
emitter.emit('hiddenLoading', '')
|
||||
}
|
||||
init()
|
||||
</script>
|
||||
<style lang='scss' scoped>
|
||||
.inboxContent {
|
||||
margin: 12px 16px 0;
|
||||
width: calc(100vw - 32px);
|
||||
line-height: 20px;
|
||||
font-size: 16px;
|
||||
overflow-y: auto;
|
||||
height: calc(100vh - 140px);
|
||||
|
||||
@media screen and (min-width: 678px) {
|
||||
margin: 12px 0;
|
||||
width: calc(674px);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(table) {
|
||||
border-top: 1px solid #ccc;
|
||||
border-left: 1px solid #ccc;
|
||||
}
|
||||
|
||||
|
||||
:deep(td) {
|
||||
border-bottom: 1px solid #ccc;
|
||||
border-right: 1px solid #ccc;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
:deep(img) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(pre) {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.inboxTitle {
|
||||
max-width: 678px;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
125
src/views/comment/list.vue
Normal file
125
src/views/comment/list.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<div class="page-content" ref="pageContent">
|
||||
<div class="head">
|
||||
<van-image class="bg" src="/img/commentBg.jpg" fit="cover" />
|
||||
<div class="headDiv p-16">
|
||||
<div class="headTop">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<div class="border-bottom-1">
|
||||
<span class="day">{{ day }}</span>
|
||||
<span class="month1 ms-10">{{ month1 }}</span>
|
||||
</div>
|
||||
<div class="textSize text-end">{{ year }}</div>
|
||||
</div>
|
||||
<van-image class="logo border-radius-4" src="/img/completeLogo.jpg" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<div class="border-radius-4 border-1 text-center p-8">
|
||||
<div>{{ month2 }}</div>
|
||||
<div>{{ week }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="textSize">
|
||||
<div>第一上海</div>
|
||||
<div class="mt-2">精选点评,让你了解更多行业新闻</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh" v-if="list.length > 0">
|
||||
<van-list class="mt-10" v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad"
|
||||
:immediate-check="false">
|
||||
<van-cell v-for="item in list" :key="item.inboxId"
|
||||
@click="$router.push({ name: 'comment-detail', query: { inboxId: item.inboxId } })">
|
||||
<div class="normal-title text-start balck-text-color f-b ellipsis-one">{{ item.inboxTitle }}</div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="content">{{ item.stockName ? item.stockName : '' }}</div>
|
||||
<div class="content">{{ item.createTime }}</div>
|
||||
</div>
|
||||
</van-cell>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
<van-empty v-else />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { getWeek } from '@/utils/index'
|
||||
import moment from 'moment'
|
||||
import { listLoadAndRefresh } from '@/mixins/list-load-and-refresh'
|
||||
import { scrollList } from '@/mixins/scroll-list'
|
||||
import { onBeforeRouteLeave } from 'vue-router'
|
||||
|
||||
let year = ref(moment().format('YYYY'))
|
||||
let month1 = ref(moment().format('MMM'))
|
||||
let month2 = ref(moment().format('M') + '月')
|
||||
let week = ref(getWeek(moment().format('d')))
|
||||
let day = ref(moment().format('DD'))
|
||||
|
||||
let pageContent = ref(null)
|
||||
const scrollPosition = ref(0)
|
||||
|
||||
const refresh = () => {
|
||||
onRefresh()
|
||||
}
|
||||
|
||||
import { workInboxList } from '@/utils/api'
|
||||
|
||||
const getData = async () => {
|
||||
const { data } = await workInboxList({
|
||||
curPage: curPage.value,
|
||||
limit: 20,
|
||||
inboxType: 4
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
const { refreshing, finished, loading, curPage, list, onLoad, onRefresh } = listLoadAndRefresh(getData, 'comment')
|
||||
const { setScrollTop, setScrollPositionAndRefreshFlag } = scrollList(pageContent, scrollPosition, refresh)
|
||||
|
||||
setScrollTop()
|
||||
onBeforeRouteLeave((to, from) => {
|
||||
setScrollPositionAndRefreshFlag(to, 'comment-detail')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.page-content {
|
||||
|
||||
.head {
|
||||
position: relative;
|
||||
|
||||
.bg {
|
||||
width: 100%;
|
||||
height: 220px;
|
||||
}
|
||||
|
||||
.headDiv {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
color: #fff;
|
||||
background: #171b2eb3;
|
||||
|
||||
.headTop {
|
||||
.day {
|
||||
font-size: 60px;
|
||||
}
|
||||
|
||||
.month1 {
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 57px;
|
||||
width: 132px;
|
||||
border: 2px solid #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
11
src/views/common/error.vue
Normal file
11
src/views/common/error.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<van-empty description="404" image="network">
|
||||
<van-button size="mini" type="primary" plain @click="$router.go(-1)" class="bottom-button">返回上一页</van-button>
|
||||
<van-button size="mini" type="success" @click="$router.push({ name: 'home' })"
|
||||
class="bottom-button">进入首页</van-button>
|
||||
</van-empty>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts"></script>
|
||||
<style lang="scss"></style>
|
||||
195
src/views/common/loading-page.vue
Normal file
195
src/views/common/loading-page.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="loading-page">
|
||||
<div>
|
||||
<div class="grid">
|
||||
<div class="item-animation item-animation1">
|
||||
<img
|
||||
src="data:image/jpeg;base64,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" />
|
||||
</div>
|
||||
<div class="item-animation item-animation2">
|
||||
<img
|
||||
src="data:image/jpeg;base64,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" />
|
||||
</div>
|
||||
<div class="item-animation item-animation3">
|
||||
<img
|
||||
src="data:image/jpeg;base64,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" />
|
||||
</div>
|
||||
<div class="item-animation item-animation4">
|
||||
<img
|
||||
src="data:image/jpeg;base64,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">
|
||||
</div>
|
||||
<div class="item-animation item-animation5">
|
||||
<img
|
||||
src="data:image/jpeg;base64,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">
|
||||
</div>
|
||||
<div class="item-animation item-animation6">
|
||||
<img
|
||||
src="data:image/jpeg;base64,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">
|
||||
</div>
|
||||
<div class="item-animation item-animation7">
|
||||
<img
|
||||
src="data:image/jpeg;base64,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">
|
||||
</div>
|
||||
<div class="item-animation item-animation8">
|
||||
<img
|
||||
src="data:image/jpeg;base64,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">
|
||||
</div>
|
||||
<div class="item-animation item-animation9">
|
||||
<img
|
||||
src="data:image/jpeg;base64,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">
|
||||
</div>
|
||||
</div>
|
||||
<div class="normal-title f-b mb-5 mt-5">第一上海證券有限公司/第一上海期貸有限公司</div>
|
||||
<div class="sub-title mb-5">FIRST SHANGHAI SECURITIES LIMITED</div>
|
||||
<div class="tip mb-5">香港中環德輔道中 71 號永安集團大廈 19 樓D</div>
|
||||
<div class="tip mb-5">19/F, Wing On House, 71 Des Voeux Road Central, Hong Kong</div>
|
||||
<div class="tip mb-5">電話 Tel:(852)2532 1580 傳真 Fax:(852)2537 6911</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router'
|
||||
const router = useRouter()
|
||||
import userInfoStore from '@/stores/userInfo'
|
||||
const userInfo = userInfoStore()
|
||||
const getCode = () => {
|
||||
const code = getUrlParam('code') // 截取路径中的code,如果没有就去微信授权,如果已经获取到了就直接传code给后台获取openId
|
||||
const local = window.location.href
|
||||
if (code == null || code === '') {
|
||||
window.location.href = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx1d52f91f259a691f&redirect_uri=' + encodeURIComponent(local) + '&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect'
|
||||
} else {
|
||||
userInfo.code = code
|
||||
window.location.href = 'http://h5.szzztec.com/login'
|
||||
}
|
||||
}
|
||||
|
||||
const getUrlParam = (name: string) => {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)')
|
||||
var r = window.location.search.substr(1).match(reg)
|
||||
if (r != null) return unescape(r[2])
|
||||
return null
|
||||
}
|
||||
|
||||
getCode()
|
||||
</script>
|
||||
<style lang='scss' scoped>
|
||||
.loading-page {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
max-width: 678px;
|
||||
height: 100vh;
|
||||
margin: 0 auto;
|
||||
|
||||
.grid {
|
||||
height: 120px;
|
||||
width: 120px;
|
||||
margin: 0 auto;
|
||||
|
||||
.item-animation {
|
||||
width: 33%;
|
||||
height: 33%;
|
||||
float: left;
|
||||
-webkit-animation: itemFlicker 1.3s infinite ease-in-out;
|
||||
animation: itemFlicker 1.3s infinite ease-in-out;
|
||||
background-size: cover;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.item-animation1 {
|
||||
-webkit-animation-delay: 0.2s;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.item-animation2 {
|
||||
-webkit-animation-delay: 0.3s;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
.item-animation3 {
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
.item-animation4 {
|
||||
-webkit-animation-delay: 0.1s;
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
|
||||
.item-animation5 {
|
||||
-webkit-animation-delay: 0.2s;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.item-animation6 {
|
||||
-webkit-animation-delay: 0.3s;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
.item-animation7 {
|
||||
-webkit-animation-delay: 0;
|
||||
animation-delay: 0;
|
||||
}
|
||||
|
||||
.item-animation8 {
|
||||
-webkit-animation-delay: 0.1s;
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
|
||||
.item-animation9 {
|
||||
-webkit-animation-delay: 0.2s;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
}
|
||||
|
||||
.text1 {
|
||||
font-size: 18px;
|
||||
margin-top: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.text2 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.text3 {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes itemFlicker {
|
||||
|
||||
0%,
|
||||
70%,
|
||||
100% {
|
||||
-webkit-transform: scale3D(1, 1, 1);
|
||||
transform: scale3D(1, 1, 1);
|
||||
}
|
||||
|
||||
35% {
|
||||
-webkit-transform: scale3D(0, 0, 1);
|
||||
transform: scale3D(0, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes itemFlicker {
|
||||
|
||||
0%,
|
||||
70%,
|
||||
100% {
|
||||
-webkit-transform: scale3D(1, 1, 1);
|
||||
transform: scale3D(1, 1, 1);
|
||||
}
|
||||
|
||||
35% {
|
||||
-webkit-transform: scale3D(0, 0, 1);
|
||||
transform: scale3D(0, 0, 1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
112
src/views/common/log.vue
Normal file
112
src/views/common/log.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<div class="title">請求詳情</div>
|
||||
<div class="request">
|
||||
<div class="request-detail">
|
||||
<div class="name">note</div>
|
||||
<div class="content">{{ logData.note }}</div>
|
||||
</div>
|
||||
<div class="request-detail">
|
||||
<div class="name">userName</div>
|
||||
<div class="content">{{ logData.userName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="title mt-16">日志详情</div>
|
||||
<div class="card mt-16" v-for="item in logList" :key="item.id">
|
||||
<div class="d-flex pb-16 border-bottom-1 justify-content-between">
|
||||
<div>类型:<span :style="{ color: item.logLevelColor }">{{ item.logLevelName }}</span></div>
|
||||
<div class="create-time">创建时间:{{ item.createTime }}</div>
|
||||
</div>
|
||||
<div class="d-flex mt-16">
|
||||
详情:
|
||||
<div class="note flex-1" :class="!item.showAll ? 'text-ellipsis' : ''">{{ item.note }}
|
||||
<div style="margin-top: 4px;" v-if="item.logLevel === 40000">错误信息:{{ item.errorMsg }}</div>
|
||||
</div>
|
||||
<span class="activeColor" @click="item.showAll = !item.showAll">{{ item.showAll ? '收起' : '展开' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { type IUserLog } from '@/utils/types'
|
||||
const logData = reactive<IUserLog>({
|
||||
note: '',
|
||||
userName: ''
|
||||
})
|
||||
const logList = ref<any[]>([])
|
||||
import emitter from '@/utils/mitt'
|
||||
import { sysLog } from '@/utils/api'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const requestId = ref()
|
||||
const init = () => {
|
||||
if (route.query && route.query.requestId) {
|
||||
requestId.value = route.query.requestId
|
||||
getData()
|
||||
} else {
|
||||
router.push({ name: '404' })
|
||||
}
|
||||
}
|
||||
const getData = async () => {
|
||||
emitter.emit('showLoading', '')
|
||||
const { data } = await sysLog(requestId.value)
|
||||
logData.note = data.sysUserLog.note
|
||||
logData.userName = data.sysUserLog.userName
|
||||
logList.value = data.sysUserLogDetail.map((ele: any) => {
|
||||
ele.showAll = false
|
||||
return ele
|
||||
})
|
||||
emitter.emit('hiddenLoading', '')
|
||||
}
|
||||
init()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
padding: 10px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.pb-10{
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
table {
|
||||
width: 100vw;
|
||||
overflow: auto;
|
||||
}
|
||||
.create-time {
|
||||
font-size: 12px;
|
||||
color: #909090;
|
||||
}
|
||||
.title {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
padding: 4px 0;
|
||||
background-color: #d9d9d9;
|
||||
}
|
||||
.request {
|
||||
border: 1px solid #e2e2e2;
|
||||
.request-detail {
|
||||
display: flex;
|
||||
border-bottom: 1px solid #e2e2e2;
|
||||
.name {
|
||||
width: 100px;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
border-right: 1px solid #e2e2e2;
|
||||
}
|
||||
.content {
|
||||
word-break: break-all;
|
||||
padding: 10px;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
color: #1989fa;
|
||||
}
|
||||
}
|
||||
}
|
||||
.note {
|
||||
word-break: break-all;
|
||||
margin-right: 4px;
|
||||
}
|
||||
</style>
|
||||
182
src/views/common/login.vue
Normal file
182
src/views/common/login.vue
Normal file
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<div class="page-div">
|
||||
<div class="form-container d-flex align-center">
|
||||
<div class="form">
|
||||
<div class="text-center pt-20">
|
||||
<van-image height="100" width="100" src="/img/smallLogo.jpg" round />
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<h2 class="big-title f-b mt-16">第一上海</h2>
|
||||
<div class="sub-title mt-16">欢迎登录数据管理系统</div>
|
||||
</div>
|
||||
<van-form class="control-area" @submit="onSubmit" validate-trigger="onSubmit">
|
||||
<van-field v-model="username" name="username" placeholder="请输入您的账户" label="账户"
|
||||
:rules="[{ required: true, message: '请输入您的账户' }]" autocomplete="new-password" />
|
||||
<van-field v-model="password" name="password" placeholder="请输入您的密码" label="密码"
|
||||
:rules="[{ required: true, message: '请输入您的密码' }]" type="password" autocomplete="new-password" />
|
||||
<div class="opr-area">
|
||||
<van-button round block type="primary" :disabled="submitFlag" native-type="submit">
|
||||
确定
|
||||
</van-button>
|
||||
</div>
|
||||
</van-form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="company-info">
|
||||
<div class="text-center mt-16 mb-16">
|
||||
<div class="mb-16 normal-title">第一上海证券有限公司</div>
|
||||
<div class="mb-8 sub-title">FIRST SHANGHAI SECURITIES LIMITED</div>
|
||||
<div class="mb-5 tip">香港中環德輔道中 71 號永安集團大廈 19 樓D</div>
|
||||
<div class="mb-5 tip">19/F, Wing On House, 71 Des Voeux Road Central, Hong Kong</div>
|
||||
<div class="mb-5 tip">电话 Tel:(852)2532 1580 传真 Fax:(852)2537 6911</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import userInfoStore from '@/stores/userInfo'
|
||||
const userInfo = userInfoStore()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
let username = ref(userInfo.username || '')
|
||||
let password = ref(userInfo.password || '')
|
||||
let submitFlag = ref(false)
|
||||
import { encrypt } from '@/utils/index'
|
||||
import { sysLogin, sysUserInfo, sysMenuNav } from '@/utils/api'
|
||||
const onSubmit = async () => {
|
||||
submitFlag.value = true
|
||||
const data: any = await sysLogin(
|
||||
{
|
||||
username: username.value,
|
||||
password: encrypt(password.value),
|
||||
}
|
||||
)
|
||||
if (data && data.code === 0) {
|
||||
userInfo.username = username.value
|
||||
userInfo.password = password.value
|
||||
userInfo.token = data.token
|
||||
getUserInfo()
|
||||
} else {
|
||||
submitFlag.value = false
|
||||
showToast('账号密码错误')
|
||||
}
|
||||
}
|
||||
|
||||
const getUserInfo = async () => {
|
||||
const data: any = await sysUserInfo()
|
||||
userInfo.id = data.user.userId
|
||||
userInfo.realname = data.user.realname
|
||||
userInfo.headPic = data.user.headPic
|
||||
userInfo.roleId = data.user.roleId + ''
|
||||
getsysMenuNav()
|
||||
}
|
||||
|
||||
const getsysMenuNav = async () => {
|
||||
const data: any = await sysMenuNav()
|
||||
userInfo.permissions = data.permissions
|
||||
let redirect = route.query.redirect || '/'
|
||||
if (typeof redirect !== 'string') {
|
||||
redirect = '/'
|
||||
}
|
||||
router.replace(redirect)
|
||||
}
|
||||
</script>
|
||||
<style lang='scss' scoped>
|
||||
.form-container {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
|
||||
.form {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
padding: 16px;
|
||||
margin: 16px;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
padding: 40px;
|
||||
margin: 40px;
|
||||
}
|
||||
|
||||
.pt-20 {
|
||||
padding-top: 20px;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
padding-top: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.control-area {
|
||||
margin: 32px auto;
|
||||
padding: 0 16px;
|
||||
width: 100%;
|
||||
@media screen and (min-width: 768px) {
|
||||
width: 100%;
|
||||
margin: 40px auto;
|
||||
}
|
||||
|
||||
.van-cell {
|
||||
padding: 16px;
|
||||
font-size: 16px;
|
||||
margin: 0 0 16px;
|
||||
width: 100%;
|
||||
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, .1);
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
padding: 20px;
|
||||
font-size: 18px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:deep(.van-field__label) {
|
||||
padding-right: 16px;
|
||||
|
||||
label {
|
||||
max-width: 5em;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.opr-area {
|
||||
padding: 16px 0;
|
||||
|
||||
.van-button {
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.van-button--disabled {
|
||||
color: rgba(0, 0, 0, 0.15);
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.company-info {
|
||||
margin-top: 30px;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
padding: 30px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
53
src/views/common/subscription.vue
Normal file
53
src/views/common/subscription.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<van-list class="pt-10">
|
||||
<van-cell v-for="item in subscriptionList" :key="item.id" :title="item.subscriptionItemName">
|
||||
<template #value>
|
||||
<van-switch size="16" :disabled="item.disabled" v-model="item.isSubscription" @change="changeSub(item)" />
|
||||
</template>
|
||||
</van-cell>
|
||||
</van-list>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { getSubscriptionList, delSubscription, saveSubscription } from '@/utils/api'
|
||||
import { ref } from 'vue'
|
||||
const subscriptionList = ref()
|
||||
const getSubList = async () => {
|
||||
const { data } = await getSubscriptionList()
|
||||
subscriptionList.value = data.map((item: any) => {
|
||||
return {
|
||||
...item,
|
||||
disabled: false,
|
||||
isSubscription: !!item.userId
|
||||
}
|
||||
})
|
||||
}
|
||||
getSubList()
|
||||
const changeSub = async (item: any) => {
|
||||
item.disabled = true
|
||||
if (!item.isSubscription) {
|
||||
await delSubscription(item.id)
|
||||
} else {
|
||||
await saveSubscription(item.subscriptionItem)
|
||||
}
|
||||
await getSubList()
|
||||
item.disabled = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pt-10 {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.van-cell {
|
||||
margin: 12px 16px 0;
|
||||
width: calc(100vw - 32px);
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
|
||||
@media screen and (min-width: 678px) {
|
||||
width: 646px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
449
src/views/communicate/add-cus.vue
Normal file
449
src/views/communicate/add-cus.vue
Normal file
@@ -0,0 +1,449 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<div class="add-cusUser-detail detailClass">
|
||||
<van-form @submit="onSubmit" validate-trigger="onSubmit">
|
||||
<van-field name="cusName" v-model="popObj.cusName" label="机构名" placeholder="请输入机构名" :required="true"
|
||||
:rules="[{ required: true, message: '请填写机构名' }]" />
|
||||
<van-field clickable name="cusLevelName" v-model="popObj.cusLevelName" label="机构评级" placeholder="点击选择机构评级"
|
||||
@click="popObj.handleShowPop1('cusLevel', '机构评级')" readonly />
|
||||
<van-popup v-model:show="popObj.showPop1" position="bottom">
|
||||
<van-picker show-toolbar :title="popObj.popTitle" :columns="popObj[`${popObj.type}List`]"
|
||||
@confirm="popObj.onConfirm1($event)" @cancel="popObj.showPop1 = false" v-model="popObj.value1" />
|
||||
</van-popup>
|
||||
<van-field clickable name="cusUserName" v-model="popObj.cusUserName" label="核心联系人" placeholder="点击选择机构"
|
||||
@click="popObj.handleShowPop2('cusUser', '核心联系人')" readonly />
|
||||
<van-popup v-model:show="popObj.showPop2" position="bottom">
|
||||
<div class="d-flex align-items-center">
|
||||
<van-field v-model="popObj.searchValue" :placeholder="'请输入' + popObj.popTitle"
|
||||
@input="popObj.onSearchInput" />
|
||||
<van-button block type="success" size="small" native-type="button" @click="toAddCusUser"
|
||||
style="width: 60px; margin: 12px 16px 0 0">
|
||||
新增
|
||||
</van-button>
|
||||
</div>
|
||||
<van-picker show-toolbar :title="popObj.popTitle" :columns="popObj.showPop2List"
|
||||
@confirm="popObj.onConfirm2($event)" @cancel="popObj.showPop2 = false" />
|
||||
</van-popup>
|
||||
<van-field clickable name="companyTypeName" v-model="popObj.companyTypeName" label="机构类型" placeholder="点击选择机构类型"
|
||||
@click="popObj.handleShowPop1('companyType', '机构类型')" readonly />
|
||||
<van-field clickable name="cityName" v-model="popObj.cityName" label="城市" placeholder="点击选择城市"
|
||||
@click="popObj.handleShowPop1('city', '城市')" readonly />
|
||||
<van-field name="address" v-model="popObj.address" label="地址" placeholder="请输入地址" />
|
||||
<van-field name="radio" label="是否开户">
|
||||
<template #input>
|
||||
<van-radio-group v-model="popObj.accountStatus" direction="horizontal">
|
||||
<van-radio name="0">否</van-radio>
|
||||
<van-radio name="1">是</van-radio>
|
||||
</van-radio-group>
|
||||
</template>
|
||||
</van-field>
|
||||
<van-field v-show="popObj.accountStatus === '1'" clickable name="accountTime" v-model="popObj.accountTime"
|
||||
label="开户时间" placeholder="点击选择日期" @click="popObj.handleShowPop4('accountTime', '开户时间')" readonly />
|
||||
<van-popup v-model:show="popObj.showPop4" position="bottom">
|
||||
<van-date-picker v-model="popObj.value4" :title="popObj.popTitle" @confirm="popObj.onConfirm4($event)"
|
||||
@cancel="popObj.showPop4 = false" />
|
||||
</van-popup>
|
||||
<van-field v-show="popObj.accountStatus === '1'" clickable name="accountTypeNames"
|
||||
v-model="popObj.accountTypeNames" label="开户类型" placeholder="点击选择开户类型"
|
||||
@click="popObj.handleShowPop3('accountType', '开户类型')" readonly />
|
||||
<van-popup v-model:show="popObj.showPop3" position="bottom">
|
||||
<div class="p-16 box-popup">
|
||||
<van-row class="mb-16">
|
||||
<van-col span="8">
|
||||
<van-button class="van-picker__cancel" native-type="button"
|
||||
@click="popObj.showPop3 = false">取消</van-button>
|
||||
</van-col>
|
||||
<van-col span="8" class="text-center">
|
||||
<span class="sub-title f-b">请选择{{ popObj.popTitle }}</span>
|
||||
</van-col>
|
||||
<van-col span="8" class="text-end">
|
||||
<van-button class="van-picker__confirm" native-type="button" @click="popObj.onConfirm3">确认</van-button>
|
||||
</van-col>
|
||||
</van-row>
|
||||
<van-checkbox-group v-model="popObj.value3" direction="horizontal">
|
||||
<van-checkbox v-for="(item, index) in popObj[`${popObj.type}List`]" :key="index" :name="item.value"
|
||||
shape="square">
|
||||
{{ item.text }}
|
||||
</van-checkbox>
|
||||
</van-checkbox-group>
|
||||
</div>
|
||||
</van-popup>
|
||||
<van-field v-show="popObj.accountStatus === '1'" name="radio" label="是否交易">
|
||||
<template #input>
|
||||
<van-radio-group v-model="popObj.isMoney" direction="horizontal">
|
||||
<van-radio name="0">否</van-radio>
|
||||
<van-radio name="1">是</van-radio>
|
||||
</van-radio-group>
|
||||
</template>
|
||||
</van-field>
|
||||
<van-field v-show="popObj.accountStatus === '1' && popObj.isMoney === '1'" name="tradeMoney"
|
||||
v-model="popObj.tradeMoney" label="交易总金额" placeholder="请输入交易总金额" />
|
||||
<van-field v-show="popObj.accountStatus === '1' && popObj.isMoney === '1'" clickable name="capitalScaleName"
|
||||
v-model="popObj.capitalScaleName" label="资金规模(亿)" placeholder="点击选择资金规模"
|
||||
@click="popObj.handleShowPop1('capitalScale', '资金规模')" readonly />
|
||||
<van-field v-show="popObj.accountStatus === '1' && popObj.isMoney === '1'" name="capitalScaleValue"
|
||||
v-model="popObj.capitalScaleValue" label="资金规模(亿)" placeholder="请输入资金规模" @blur="popObj.setAccountStatus" />
|
||||
<van-field name="fundNum" v-model="popObj.fundNum" label="基金数量" placeholder="请输入基金数量" />
|
||||
<van-field name="investOverseasPer" v-model="popObj.investOverseasPer" label="海外投资占比" placeholder="请输入海外投资占比" />
|
||||
<van-field name="investResearchNum" v-model="popObj.investResearchNum" label="投研人数" placeholder="请输入投研人数" />
|
||||
<van-field clickable name="investChannelName" v-model="popObj.investChannelName" label="投资渠道"
|
||||
placeholder="点击选择投资渠道" @click="popObj.handleShowPop1('investChannel', '投资渠道')" readonly />
|
||||
<van-field name="website" v-model="popObj.website" label="公司网站" placeholder="请输入公司网站" />
|
||||
<van-field clickable name="companyCreateTime" v-model="popObj.companyCreateTime" label="开户时间"
|
||||
placeholder="点击选择公司创建时间" @click="popObj.handleShowPop4('companyCreateTime', '公司创建时间')" readonly />
|
||||
<van-field name="introduction" v-model="popObj.introduction" label="公司简介" rows="3" type="textarea" autosize />
|
||||
<van-field clickable name="saleUserNames" v-model="popObj.saleUserNames" label="负责人" placeholder="点击选择负责人"
|
||||
@click="popObj.handleShowPop3('saleUser', '负责人')" readonly />
|
||||
<div class="m-16 d-flex justify-content-center">
|
||||
<van-button round block type="primary" size="small" native-type="submit"
|
||||
:disabled="submitFlag">提交</van-button>
|
||||
</div>
|
||||
</van-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, onUnmounted } from 'vue'
|
||||
import moment from 'moment'
|
||||
import emitter from '@/utils/mitt'
|
||||
import { sysDicListByType, sysUserGetSaleAndManagerList, cusUserLookUp, cusInfoSave } from '@/utils/api'
|
||||
let submitFlag = ref(false)
|
||||
|
||||
import { debounceThrottle } from '@/mixins/debounce-throttle'
|
||||
const { throttle } = debounceThrottle()
|
||||
import { type CusObj } from '@/utils/types'
|
||||
const popObj: CusObj = reactive({
|
||||
showPop1: false,
|
||||
showPop2: false,
|
||||
showPop3: false,
|
||||
showPop4: false,
|
||||
popTitle: '',
|
||||
type: '',
|
||||
value1: [],
|
||||
value3: [],
|
||||
value4: [],
|
||||
cusName: '',
|
||||
cusLevelName: '',
|
||||
selectcusLevel: '',
|
||||
cusLevelList: [],
|
||||
cusUserName: '',
|
||||
selectcusUser: '',
|
||||
searchValue: '',
|
||||
showPop2List: [],
|
||||
companyTypeName: '',
|
||||
selectcompanyType: '',
|
||||
companyTypeList: [],
|
||||
cityName: '',
|
||||
selectcity: '',
|
||||
cityList: [],
|
||||
address: '',
|
||||
accountStatus: '1',
|
||||
accountTime: '',
|
||||
selectaccountTime: [],
|
||||
accountTypeNames: '',
|
||||
selectaccountTypeIds: [],
|
||||
accountTypeList: [],
|
||||
isMoney: '0',
|
||||
tradeMoney: '',
|
||||
capitalScaleName: '',
|
||||
selectcapitalScale: '',
|
||||
capitalScaleList: [],
|
||||
capitalScaleValue: '',
|
||||
fundNum: '',
|
||||
investOverseasPer: '',
|
||||
investResearchNum: '',
|
||||
investChannelName: '',
|
||||
selectinvestChannel: '',
|
||||
investChannelList: [],
|
||||
website: '',
|
||||
companyCreateTime: '',
|
||||
selectcompanyCreateTime: [],
|
||||
introduction: '',
|
||||
saleUserNames: '',
|
||||
selectsaleUserIds: [],
|
||||
saleUserList: [],
|
||||
handleShowPop1: (val: string, title: string) => {
|
||||
popObj.showPop1 = true
|
||||
popObj.popTitle = title
|
||||
popObj.type = val
|
||||
popObj.value1 = [popObj[`select${val}`]]
|
||||
},
|
||||
handleShowPop2: (val: string, title: string) => {
|
||||
popObj.showPop2 = true
|
||||
popObj.popTitle = title
|
||||
popObj.type = val
|
||||
popObj.searchValue = popObj[`${val}Name`]
|
||||
popObj.showPop2List = popObj[`select${val}`] ? [{ text: popObj[`${val}Name`], value: popObj[`select${val}`] }] : []
|
||||
},
|
||||
handleShowPop3: (val: string, title: string) => {
|
||||
popObj.showPop3 = true
|
||||
popObj.popTitle = title
|
||||
popObj.type = val
|
||||
popObj.value3 = popObj[`select${val}Ids`]
|
||||
},
|
||||
handleShowPop4: (val: string, title: string) => {
|
||||
popObj.popTitle = title
|
||||
popObj.type = val
|
||||
if (popObj[`select${val}`].length === 0) {
|
||||
let nowDay = moment().format('YYYY-MM-DD HH:mm:ss')
|
||||
popObj.value4 = nowDay.slice(0, 10).split('-')
|
||||
} else {
|
||||
popObj.value4 = popObj[`select${val}`]
|
||||
}
|
||||
popObj.showPop4 = true
|
||||
},
|
||||
onConfirm1: ({ selectedOptions }) => {
|
||||
if (selectedOptions) {
|
||||
popObj[`select${popObj.type}`] = selectedOptions[0].value
|
||||
popObj[`${popObj.type}Name`] = selectedOptions[0].text
|
||||
if (popObj.type === 'capitalScale') {
|
||||
if (popObj.capitalScale === '1') {
|
||||
if (Number(popObj.capitalScaleValue) > 10) {
|
||||
popObj.capitalScaleValue = ''
|
||||
}
|
||||
} else if (popObj.capitalScale === '2') {
|
||||
if (Number(popObj.capitalScaleValue) > 100 || Number(popObj.capitalScale) < 10) {
|
||||
popObj.capitalScaleValue = ''
|
||||
}
|
||||
} else if (popObj.capitalScale === '3') {
|
||||
if (Number(popObj.capitalScaleValue) < 100) {
|
||||
popObj.capitalScaleValue = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
popObj.showPop1 = false
|
||||
},
|
||||
onSearchInput: throttle(
|
||||
async () => {
|
||||
if (popObj.searchValue) {
|
||||
const { data } = await cusUserLookUp({ cusUserName: popObj.searchValue })
|
||||
popObj.showPop2List = data.map((ele: any) => {
|
||||
ele.text = `${ele.cusUserName}${ele.positionName ? '【' + ele.positionName + '】' : ''}`
|
||||
ele.value = ele.cusUserId
|
||||
return ele
|
||||
})
|
||||
} else {
|
||||
popObj.showPop2List = []
|
||||
}
|
||||
},
|
||||
500
|
||||
),
|
||||
onConfirm2: ({ selectedOptions }) => {
|
||||
if (selectedOptions && selectedOptions[0]) {
|
||||
popObj[`select${popObj.type}`] = selectedOptions[0].value
|
||||
popObj[`${popObj.type}Name`] = selectedOptions[0].text
|
||||
popObj.showPop2 = false
|
||||
} else {
|
||||
showToast(`请选择${popObj.popTitle}后,再确认`)
|
||||
}
|
||||
},
|
||||
onConfirm3: () => {
|
||||
popObj[`select${popObj.type}Ids`] = popObj.value3
|
||||
const sameList = popObj[`${popObj.type}List`].filter((ele: any) => popObj.value3.some((res: any) => {
|
||||
return ele.value === res
|
||||
}))
|
||||
let names = ''
|
||||
sameList.forEach((ele: any) => {
|
||||
names += `${ele.text},`
|
||||
})
|
||||
if (names) {
|
||||
names = names.substring(0, names.length - 1)
|
||||
}
|
||||
popObj[`${popObj.type}Names`] = names
|
||||
popObj.showPop3 = false
|
||||
},
|
||||
onConfirm4: ({ selectedValues }) => {
|
||||
if (selectedValues) {
|
||||
popObj[popObj.type] = `${selectedValues[0]}-${selectedValues[1]}-${selectedValues[2]}`
|
||||
popObj[`select${popObj.type}`] = selectedValues
|
||||
}
|
||||
popObj.showPop4 = false
|
||||
},
|
||||
setAccountStatus: () => {
|
||||
if (Number(popObj.capitalScaleValue) >= 0 && Number(popObj.capitalScaleValue) < 10) {
|
||||
popObj.selectcapitalScale = '1'
|
||||
} else if (Number(popObj.capitalScaleValue) >= 10 && Number(popObj.capitalScaleValue) < 100) {
|
||||
popObj.selectcapitalScale = '2'
|
||||
} else if (Number(popObj.capitalScaleValue) >= 100) {
|
||||
popObj.selectcapitalScale = '3'
|
||||
} else {
|
||||
popObj.selectcapitalScale = ''
|
||||
}
|
||||
let index = popObj.capitalScaleList.findIndex((ele: any) => {
|
||||
return ele.dicKey === popObj.selectcapitalScale
|
||||
})
|
||||
if (index > -1) {
|
||||
popObj.capitalScaleName = popObj.capitalScaleList[index].dicValue
|
||||
} else {
|
||||
popObj.capitalScaleName = ''
|
||||
}
|
||||
}
|
||||
})
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
import saveCusInfoStore from '@/stores/saveCusInfo'
|
||||
const saveCusInfo: any = saveCusInfoStore()
|
||||
import saveInfoStore from '@/stores/saveInfo'
|
||||
const saveInfo: any = saveInfoStore()
|
||||
let linkId: any = ''
|
||||
const getSelectList = async () => {
|
||||
linkId = route.query.linkId
|
||||
emitter.emit('showLoading', '')
|
||||
const data1 = await sysDicListByType({ dicType: 'cus_level' })
|
||||
popObj.cusLevelList = data1.data.map((ele: any) => {
|
||||
ele.text = ele.dicValue
|
||||
ele.value = ele.dicKey
|
||||
return ele
|
||||
})
|
||||
const data2 = await sysDicListByType({ dicType: 'sale_company_type' })
|
||||
popObj.companyTypeList = data2.data.map((ele: any) => {
|
||||
ele.text = ele.dicValue
|
||||
ele.value = ele.dicKey
|
||||
return ele
|
||||
})
|
||||
const data3 = await sysDicListByType({ dicType: 'cus_city' })
|
||||
popObj.cityList = data3.data.map((ele: any) => {
|
||||
ele.text = ele.dicValue
|
||||
ele.value = ele.dicKey
|
||||
return ele
|
||||
})
|
||||
const data4 = await sysDicListByType({ dicType: 'account_type' })
|
||||
popObj.accountTypeList = data4.data.map((ele: any) => {
|
||||
ele.text = ele.dicValue
|
||||
ele.value = ele.dicKey
|
||||
return ele
|
||||
})
|
||||
const data5 = await sysDicListByType({ dicType: 'invest_channel' })
|
||||
popObj.investChannelList = data5.data.map((ele: any) => {
|
||||
ele.text = ele.dicValue
|
||||
ele.value = ele.dicKey
|
||||
return ele
|
||||
})
|
||||
const data6 = await sysUserGetSaleAndManagerList()
|
||||
popObj.saleUserList = data6.data.map((ele: any) => {
|
||||
ele.value = ele.userId
|
||||
ele.text = ele.userName
|
||||
return ele
|
||||
})
|
||||
const data7 = await sysDicListByType({ dicType: 'sale_capital_scale' })
|
||||
popObj.capitalScaleList = data7.data.map((ele: any) => {
|
||||
ele.text = ele.dicValue
|
||||
ele.value = ele.dicKey
|
||||
return ele
|
||||
})
|
||||
for (let i in saveCusInfo) {
|
||||
if (Object.prototype.hasOwnProperty.call(popObj, i)) {
|
||||
popObj[i] = saveCusInfo[i]
|
||||
}
|
||||
}
|
||||
if (saveInfo.type === 'cusUser') {
|
||||
popObj.cusUserName = `${saveInfo.name}${saveInfo.positionName ? '【' + saveInfo.positionName + '】' : ''}`
|
||||
popObj.selectcusUser = saveInfo.id
|
||||
saveInfo.$reset()
|
||||
}
|
||||
emitter.emit('hiddenLoading', '')
|
||||
}
|
||||
|
||||
const handleKeyUp = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('keyup', handleKeyUp)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('keyup', handleKeyUp)
|
||||
})
|
||||
|
||||
const toAddCusUser = () => {
|
||||
for (let i in saveCusInfo) {
|
||||
if (Object.prototype.hasOwnProperty.call(popObj, i)) {
|
||||
saveCusInfo[i] = popObj[i]
|
||||
}
|
||||
}
|
||||
if (linkId) {
|
||||
router.push({ name: 'add-cusUser', query: { linkId: linkId, from: 'cus' } })
|
||||
} else {
|
||||
router.push({ name: 'add-cusUser', query: { linkId: linkId, from: 'cus' } })
|
||||
}
|
||||
}
|
||||
|
||||
const onSubmit = async () => {
|
||||
submitFlag.value = true
|
||||
const res: any = {
|
||||
cusName: popObj.cusName,
|
||||
cusLevel: popObj.selectcusLevel || 'E',
|
||||
cusUserId: popObj.selectcusUser || null,
|
||||
cusUserIdList: popObj.selectcusUser ? [popObj.selectcusUser] : [],
|
||||
companyType: popObj.selectcompanyType || null,
|
||||
city: popObj.selectcity || null,
|
||||
address: popObj.address,
|
||||
accountStatus: popObj.accountStatus,
|
||||
accountTypeList: popObj.accountStatus === '1' && popObj.selectaccountTypeIds.length > 0 ?
|
||||
popObj.selectaccountTypeIds : null,
|
||||
accountTime: popObj.accountStatus === '1' && popObj.accountTime ?
|
||||
popObj.accountTime : null,
|
||||
tradeMoney: popObj.accountStatus === '1' && (popObj.tradeMoney === '1' || popObj.tradeMoney === '0') ?
|
||||
popObj.tradeMoney : null,
|
||||
capitalScaleValue: popObj.capitalScaleValue || null,
|
||||
capitalScale: popObj.selectcapitalScale || null,
|
||||
fundNum: popObj.fundNum || null,
|
||||
investOverseasPer: popObj.investOverseasPer || null,
|
||||
investResearchNum: popObj.investResearchNum || null,
|
||||
investChannel: popObj.selectinvestChannel || null,
|
||||
website: popObj.website || null,
|
||||
companyCreateTime: popObj.companyCreateTime || null,
|
||||
introduction: popObj.introduction || null,
|
||||
saleUserIdList: popObj.selectsaleUserIds || null
|
||||
}
|
||||
const data: any = await cusInfoSave(res)
|
||||
if (data.code === 0) {
|
||||
showToast('提交成功')
|
||||
saveInfo.id = data.data.cusId
|
||||
saveInfo.name = data.data.cusName
|
||||
saveInfo.type = 'cus'
|
||||
saveCusInfo.$reset()
|
||||
router.go(-1)
|
||||
} else {
|
||||
showToast(data.msg)
|
||||
submitFlag.value = false
|
||||
}
|
||||
}
|
||||
|
||||
getSelectList()
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.add-cusUser-detail {
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.detailClass {
|
||||
.van-cell {
|
||||
margin: 12px 16px 0;
|
||||
width: calc(100vw - 32px);
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
|
||||
|
||||
@media screen and (min-width: 678px) {
|
||||
width: 646px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.van-field__control) {
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
:deep(.van-checkbox) {
|
||||
margin-right: 0 !important;
|
||||
width: 50%;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
</style>
|
||||
222
src/views/communicate/add-cusUser.vue
Normal file
222
src/views/communicate/add-cusUser.vue
Normal file
@@ -0,0 +1,222 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<div class="add-cus-detail detailClass">
|
||||
<van-form @submit="onSubmit" validate-trigger="onSubmit">
|
||||
<van-field name="cusUserName" v-model="popObj.cusUserName" label="联系人" placeholder="请输入联系人" :required="true"
|
||||
:rules="[{ required: true, message: '请填写联系人' }]" />
|
||||
<van-field clickable name="positionName" v-model="popObj.positionName" label="职位" placeholder="点击选择职位"
|
||||
@click="popObj.handleShowPop1('position', '职位')" readonly :required="true"
|
||||
:rules="[{ required: true, message: '请填写职位' }]" />
|
||||
<van-popup v-model:show="popObj.showPop1" position="bottom">
|
||||
<van-picker show-toolbar :title="popObj.popTitle" :columns="popObj[`${popObj.type}List`]"
|
||||
@confirm="popObj.onConfirm1($event)" @cancel="popObj.showPop1 = false" v-model="popObj.value1" />
|
||||
</van-popup>
|
||||
<van-field clickable name="saleUserNames" v-model="popObj.saleUserNames" label="跟进人" placeholder="点击选择跟进人"
|
||||
@click="popObj.handleShowPop3('saleUser', '跟进人')" readonly />
|
||||
<van-popup v-model:show="popObj.showPop3" position="bottom">
|
||||
<div class="p-16 box-popup">
|
||||
<van-row class="mb-16">
|
||||
<van-col span="8">
|
||||
<van-button class="van-picker__cancel" native-type="button"
|
||||
@click="popObj.showPop3 = false">取消</van-button>
|
||||
</van-col>
|
||||
<van-col span="8" class="text-center">
|
||||
<span class="sub-title f-b">请选择{{ popObj.popTitle }}</span>
|
||||
</van-col>
|
||||
<van-col span="8" class="text-end">
|
||||
<van-button class="van-picker__confirm" native-type="button" @click="popObj.onConfirm3">确认</van-button>
|
||||
</van-col>
|
||||
</van-row>
|
||||
<van-checkbox-group v-model="popObj.value3" direction="horizontal">
|
||||
<van-checkbox v-for="(item, index) in popObj[`${popObj.type}List`]" :key="index" :name="item.value"
|
||||
shape="square">
|
||||
{{ item.text }}
|
||||
</van-checkbox>
|
||||
</van-checkbox-group>
|
||||
</div>
|
||||
</van-popup>
|
||||
<van-field name="phone" v-model="popObj.phone" label="座机号" placeholder="请输入座机号" />
|
||||
<van-field name="mobile" v-model="popObj.mobile" label="手机号" placeholder="请输入手机号" />
|
||||
<van-field name="email" v-model="popObj.email" label="邮箱" placeholder="请输入邮箱" />
|
||||
<van-field name="wxName" v-model="popObj.wxName" label="微信名" placeholder="请输入微信名" />
|
||||
<van-field name="address" v-model="popObj.address" label="地址" placeholder="请输入地址" />
|
||||
<van-field name="preference" v-model="popObj.preference" label="擅长" placeholder="请输入擅长" />
|
||||
<div class="m-16 d-flex justify-content-center">
|
||||
<van-button round block type="primary" size="small" native-type="submit"
|
||||
:disabled="submitFlag">提交</van-button>
|
||||
</div>
|
||||
</van-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, onUnmounted } from 'vue'
|
||||
import emitter from '@/utils/mitt'
|
||||
import { cusUserCusPositionList, sysUserGetSaleAndManagerList, cusUserSave } from '@/utils/api'
|
||||
let submitFlag = ref(false)
|
||||
|
||||
import { type CusUserObj } from '@/utils/types'
|
||||
const popObj: CusUserObj = reactive({
|
||||
showPop1: false,
|
||||
showPop3: false,
|
||||
popTitle: '',
|
||||
type: '',
|
||||
value1: [],
|
||||
value3: [],
|
||||
cusUserName: '',
|
||||
positionName: '',
|
||||
selectposition: '',
|
||||
positionList: [],
|
||||
saleUserNames: '',
|
||||
selectsaleUserIds: [],
|
||||
saleUserList: [],
|
||||
selectcusUser: '',
|
||||
phone: '',
|
||||
mobile: '',
|
||||
email: '',
|
||||
wxName: '',
|
||||
address: '',
|
||||
preference: '',
|
||||
handleShowPop1: (val: string, title: string) => {
|
||||
popObj.showPop1 = true
|
||||
popObj.popTitle = title
|
||||
popObj.type = val
|
||||
popObj.value1 = [popObj[`select${val}`]]
|
||||
},
|
||||
handleShowPop3: (val: string, title: string) => {
|
||||
popObj.showPop3 = true
|
||||
popObj.popTitle = title
|
||||
popObj.type = val
|
||||
popObj.value3 = popObj[`select${val}Ids`]
|
||||
},
|
||||
onConfirm1: ({ selectedOptions }) => {
|
||||
if (selectedOptions) {
|
||||
popObj[`select${popObj.type}`] = selectedOptions[0].value
|
||||
popObj[`${popObj.type}Name`] = selectedOptions[0].text
|
||||
}
|
||||
popObj.showPop1 = false
|
||||
},
|
||||
onConfirm3: () => {
|
||||
popObj[`select${popObj.type}Ids`] = popObj.value3
|
||||
const sameList = popObj[`${popObj.type}List`].filter((ele: any) => popObj.value3.some((res: any) => {
|
||||
return ele.value === res
|
||||
}))
|
||||
let names = ''
|
||||
sameList.forEach((ele: any) => {
|
||||
names += `${ele.text},`
|
||||
})
|
||||
if (names) {
|
||||
names = names.substring(0, names.length - 1)
|
||||
}
|
||||
popObj[`${popObj.type}Names`] = names
|
||||
popObj.showPop3 = false
|
||||
}
|
||||
})
|
||||
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
let cusId: any = ''
|
||||
let linkId: any = ''
|
||||
let from: any = ''
|
||||
const getSelectList = async () => {
|
||||
if (route.query && route.query.from) {
|
||||
cusId = route.query && route.query.cusId ? route.query.cusId : ''
|
||||
linkId = route.query && route.query.linkId ? route.query.linkId : ''
|
||||
from = route.query.from
|
||||
emitter.emit('showLoading', '')
|
||||
const data1 = await cusUserCusPositionList()
|
||||
popObj.positionList = data1.data.map((ele: any) => {
|
||||
ele.text = ele.dicValue
|
||||
ele.value = ele.dicKey
|
||||
return ele
|
||||
})
|
||||
const data2 = await sysUserGetSaleAndManagerList()
|
||||
popObj.saleUserList = data2.data.map((ele: any) => {
|
||||
ele.value = ele.userId
|
||||
ele.text = ele.userName
|
||||
return ele
|
||||
})
|
||||
emitter.emit('hiddenLoading', '')
|
||||
} else {
|
||||
router.push({ name: '404' })
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeyUp = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('keyup', handleKeyUp)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('keyup', handleKeyUp)
|
||||
})
|
||||
|
||||
import saveInfoStore from '@/stores/saveInfo'
|
||||
const saveInfo: any = saveInfoStore()
|
||||
const onSubmit = async () => {
|
||||
submitFlag.value = true
|
||||
const res: any = {
|
||||
cusUserName: popObj.cusUserName,
|
||||
positionId: popObj.selectposition || null,
|
||||
saleUserIdList: popObj.selectsaleUserIds || null,
|
||||
phone: popObj.phone || null,
|
||||
mobile: popObj.mobile || null,
|
||||
email: popObj.email || null,
|
||||
wxName: popObj.wxName || null,
|
||||
address: popObj.address || null,
|
||||
preference: popObj.preference || null
|
||||
}
|
||||
if (cusId) {
|
||||
res.cusId = cusId
|
||||
}
|
||||
const data: any = await cusUserSave(res)
|
||||
if (data.code === 0) {
|
||||
showToast('提交成功')
|
||||
saveInfo.id = data.data.cusUserId
|
||||
saveInfo.name = data.data.cusUserName
|
||||
saveInfo.positionName = data.data.positionName
|
||||
saveInfo.type = 'cusUser'
|
||||
router.go(-1)
|
||||
} else {
|
||||
submitFlag.value = false
|
||||
}
|
||||
}
|
||||
|
||||
getSelectList()
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.add-cus-detail {
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.detailClass {
|
||||
.van-cell {
|
||||
margin: 12px 16px 0;
|
||||
width: calc(100vw - 32px);
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
|
||||
|
||||
@media screen and (min-width: 678px) {
|
||||
width: 646px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.van-field__control) {
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
:deep(.van-checkbox) {
|
||||
margin-right: 0 !important;
|
||||
width: 50%;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
</style>
|
||||
1038
src/views/communicate/detail.vue
Normal file
1038
src/views/communicate/detail.vue
Normal file
File diff suppressed because it is too large
Load Diff
254
src/views/communicate/list.vue
Normal file
254
src/views/communicate/list.vue
Normal file
@@ -0,0 +1,254 @@
|
||||
<template>
|
||||
<div class="page-content top-select-list" ref="pageContent">
|
||||
<div v-if="userInfo.roleId === '1' || userInfo.roleId === '9' || userInfo.roleId === '10' || userInfo.roleId === '6'
|
||||
|| userInfo.roleId === '7' || userInfo.roleId === '16'" class="addDiv success-background-color"
|
||||
@click="$router.push({ name: 'communicate-detail', query: { addDay: selectDay } })">
|
||||
<div class="text-center addDiv1">新增</div>
|
||||
<div class="text-center addDiv2">沟通</div>
|
||||
</div>
|
||||
<div class="top-select border-bottom-1">
|
||||
<van-dropdown-menu>
|
||||
<van-dropdown-item :title="cusName" ref="itemRef">
|
||||
<van-field v-model="searchValue" placeholder="请输入机构" @input="onSearchInput"
|
||||
:right-icon="searchValue ? 'clear' : ''" @click-right-icon.stop="clearSearch" />
|
||||
<van-picker show-toolbar title="机构列表" :columns="list" @confirm="onConfirm" @cancel="onCancel" />
|
||||
</van-dropdown-item>
|
||||
<van-dropdown-item v-model="linkType" :options="linkTypeList" @change="getLinkList" />
|
||||
<van-dropdown-item v-model="saleId" :options="saleList" @change="getLinkList" />
|
||||
</van-dropdown-menu>
|
||||
</div>
|
||||
<van-calendar title="" :poppable="false" :show-confirm="false" switch-mode="year-month" first-day-of-week="1"
|
||||
:style="{ height: '410px' }" :formatter="formatter" :default-date="defaultDate" @panel-change="panelChange"
|
||||
@select="onDateConfirm" />
|
||||
<communicate-item class="pb-16" :list="list" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { scrollList } from '@/mixins/scroll-list'
|
||||
import { convertToUrl } from '@/utils'
|
||||
import userInfoStore from '@/stores/userInfo'
|
||||
const userInfo = userInfoStore()
|
||||
|
||||
import { cusLinkInfo, cusLinkList, sysUserGetSaleAndManagerList, cusInfoLookUp } from '@/utils/api'
|
||||
let cusName = ref('机构')
|
||||
let cusId = ref('')
|
||||
let searchValue = ref('')
|
||||
let itemRef = ref(null)
|
||||
import { debounceThrottle } from '@/mixins/debounce-throttle'
|
||||
const { throttle } = debounceThrottle()
|
||||
const onSearchInput = throttle(
|
||||
async () => {
|
||||
if (searchValue.value) {
|
||||
const { data } = await cusInfoLookUp({ cusName: searchValue.value })
|
||||
list.value = data.map((ele: any) => {
|
||||
ele.text = ele.cusName
|
||||
ele.value = ele.cusId
|
||||
return ele
|
||||
})
|
||||
} else {
|
||||
list.value = []
|
||||
}
|
||||
},
|
||||
500
|
||||
)
|
||||
|
||||
const clearSearch = () => {
|
||||
searchValue.value = ''
|
||||
}
|
||||
|
||||
const onConfirm = (e: any) => {
|
||||
console.log(e)
|
||||
if (e.selectedValues.length > 0) {
|
||||
cusName.value = e.selectedOptions[0].cusName
|
||||
cusId.value = e.selectedOptions[0].cusId
|
||||
searchValue.value = e.selectedOptions[0].cusName
|
||||
getLinkList()
|
||||
}
|
||||
itemRef.value.toggle()
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
itemRef.value.toggle()
|
||||
}
|
||||
|
||||
let linkType = ref('31,32,34')
|
||||
let linkTypeList: any = ref([{
|
||||
text: '全部类型',
|
||||
value: '31,32,34'
|
||||
}, {
|
||||
text: '上市公司按需会议',
|
||||
value: '31'
|
||||
}, {
|
||||
text: '机构路演',
|
||||
value: '32'
|
||||
}, {
|
||||
text: '机构沟通',
|
||||
value: '34'
|
||||
}])
|
||||
|
||||
let saleId = ref('')
|
||||
let saleList: any = ref([])
|
||||
const getSaleList = async () => {
|
||||
const { data } = await sysUserGetSaleAndManagerList()
|
||||
saleList.value = data.map((ele: any) => {
|
||||
ele.text = ele.userName
|
||||
ele.value = ele.userId
|
||||
return ele
|
||||
})
|
||||
saleList.value.unshift({ text: '全部负责人', value: '' })
|
||||
}
|
||||
getSaleList()
|
||||
|
||||
let defaultDate = ref()
|
||||
import moment from 'moment'
|
||||
const panelChange = (e: any) => {
|
||||
dateBegin.value = moment(e.date).startOf('month').format('YYYY-MM-DD')
|
||||
dateEnd.value = moment(e.date).endOf('month').format('YYYY-MM-DD')
|
||||
getLinkList()
|
||||
}
|
||||
|
||||
const onDateConfirm = (e: any) => {
|
||||
defaultDate.value = e
|
||||
getList()
|
||||
}
|
||||
|
||||
let list: any = ref([])
|
||||
let selectDay = ref('')
|
||||
const getList = () => {
|
||||
list.value = []
|
||||
selectDay.value = moment(defaultDate.value).format('YYYY-MM-DD')
|
||||
linkList.value.forEach((ele: any) => {
|
||||
if (ele.sysDay === selectDay.value) {
|
||||
list.value = ele.list
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const formatter = computed(() => {
|
||||
if (linkList.value.length === 0) {
|
||||
return (day: any) => day
|
||||
}
|
||||
return (day: any) => {
|
||||
let time = moment(day.date).format('YYYY-MM-DD')
|
||||
linkList.value.forEach((ele: any) => {
|
||||
if (ele.sysDay === time) {
|
||||
day.topInfo = ele.num
|
||||
}
|
||||
})
|
||||
return day
|
||||
}
|
||||
})
|
||||
|
||||
const dateBegin = ref()
|
||||
const dateEnd = ref()
|
||||
const linkList: any = ref([])
|
||||
const getLinkList = async () => {
|
||||
let url = getLinkParams()
|
||||
const data = await cusLinkList(url)
|
||||
let map = new Map()
|
||||
data.forEach((item: any, index: any, arr: any) => {
|
||||
if (!map.has(item.sysDay)) {
|
||||
map.set(
|
||||
item.sysDay,
|
||||
arr.filter((a: any) => a.sysDay == item.sysDay)
|
||||
)
|
||||
}
|
||||
})
|
||||
let res = Array.from(map).map((item) => [...item[1]])
|
||||
linkList.value = []
|
||||
res.forEach((ele: any) => {
|
||||
linkList.value.push({ sysDay: ele[0].sysDay, num: ele.length, list: ele })
|
||||
})
|
||||
getList()
|
||||
}
|
||||
|
||||
const getLinkParams = () => {
|
||||
const res = {
|
||||
dateBegin: dateBegin.value,
|
||||
dateEnd: dateEnd.value,
|
||||
limit: 999,
|
||||
dataStatus: 1,
|
||||
curPage: 1,
|
||||
saleId: saleId.value,
|
||||
linkTypes: linkType.value,
|
||||
cusId: cusId.value
|
||||
}
|
||||
const url = convertToUrl(res)
|
||||
return url
|
||||
}
|
||||
|
||||
let pageContent = ref(null)
|
||||
const scrollPosition = ref(0)
|
||||
const refresh = async (type: any, id: any) => {
|
||||
if (type === 'delete' || type === 'refresh') {
|
||||
let dayIndex = linkList.value.findIndex((ele: any) => {
|
||||
return ele.sysDay === selectDay.value
|
||||
})
|
||||
if (dayIndex > -1) {
|
||||
let index = linkList.value[dayIndex].list.findIndex((ele: any) => {
|
||||
return Number(ele.linkId) === Number(id)
|
||||
})
|
||||
if (type === 'delete') {
|
||||
if (index > -1) {
|
||||
linkList.value[dayIndex].list.splice(index, 1)
|
||||
linkList.value[dayIndex].num -= 1
|
||||
}
|
||||
} else {
|
||||
if (index > -1) {
|
||||
const { data } = await cusLinkInfo(id)
|
||||
switch (data.linkTypeMenu) {
|
||||
case '32':
|
||||
data.tagColor = 'danger-background-color'
|
||||
break
|
||||
case '34':
|
||||
data.tagColor = 'primary-background-color'
|
||||
break
|
||||
case '31':
|
||||
data.tagColor = 'success-background-color'
|
||||
break
|
||||
}
|
||||
linkList.value[dayIndex].list[index] = data
|
||||
}
|
||||
getList()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
defaultDate.value = new Date()
|
||||
dateBegin.value = moment().startOf('month').format('YYYY-MM-DD')
|
||||
dateEnd.value = moment().endOf('month').format('YYYY-MM-DD')
|
||||
getLinkList()
|
||||
}
|
||||
}
|
||||
const { setScrollTop, setScrollPositionAndRefreshFlag } = scrollList(pageContent, scrollPosition, refresh)
|
||||
setScrollTop()
|
||||
import { onBeforeRouteLeave } from 'vue-router'
|
||||
onBeforeRouteLeave((to, from) => {
|
||||
setScrollPositionAndRefreshFlag(to, 'communicate-detail')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
:deep(.van-calendar__header-title) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(.van-calendar__top-info) {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
left: calc(100vw / 7 - 20px);
|
||||
border-radius: 100%;
|
||||
background-color: #f56c6c;
|
||||
color: #fff;
|
||||
|
||||
@media screen and (min-width: 678px) {
|
||||
left: 58px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.van-calendar__day) {
|
||||
border: 1px solid #f2f2f2;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
90
src/views/flow/company.vue
Normal file
90
src/views/flow/company.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<div class="tabbar-list" ref="pageContent">
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh" v-if="list.length > 0">
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad"
|
||||
:immediate-check="false">
|
||||
<van-grid :border="false" :clickable="true" :column-num="3">
|
||||
<van-grid-item v-for="item in list" :key="item.id"
|
||||
@click="$router.push({ name: 'flow', query: { stockCode: item.stockCode } })">
|
||||
<div class="itemImage">
|
||||
<svg class="stockIcon" aria-hidden="true">
|
||||
<use :xlink:href="`#icon-${item.companyIcon}`"></use>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="itemName">{{ item.stockName }}</div>
|
||||
</van-grid-item>
|
||||
</van-grid>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
<van-empty v-else />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { onBeforeRouteLeave } from 'vue-router'
|
||||
import { stockCompanyFlowInfoListCompany } from '@/utils/api'
|
||||
let pageContent = ref(null)
|
||||
const scrollPosition = ref(0)
|
||||
const getData = async () => {
|
||||
const { data } = await stockCompanyFlowInfoListCompany()
|
||||
return { list: data, totalCount: data.length }
|
||||
}
|
||||
|
||||
const refresh = () => {
|
||||
onRefresh()
|
||||
}
|
||||
import { listLoadAndRefresh } from '@/mixins/list-load-and-refresh'
|
||||
const { refreshing, finished, loading, list, onLoad, onRefresh } = listLoadAndRefresh(getData, 'stock')
|
||||
import { scrollList } from '@/mixins/scroll-list'
|
||||
const { setScrollTop, setScrollPositionAndRefreshFlag } = scrollList(pageContent, scrollPosition, refresh)
|
||||
|
||||
setScrollTop()
|
||||
onBeforeRouteLeave((to, from) => {
|
||||
setScrollPositionAndRefreshFlag(to, 'track')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.tabbar-list {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
:deep(.van-grid-item) {
|
||||
padding-right: 20px;
|
||||
padding-bottom: 26px;
|
||||
}
|
||||
|
||||
:deep(.van-grid-item:nth-child(3n)) {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
:deep(.van-grid-item__content) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.itemImage {
|
||||
margin-top: 18px;
|
||||
height: 36px;
|
||||
|
||||
.stockIcon {
|
||||
max-height: 36px;
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.itemName {
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
margin-top: 15px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
47
src/views/flow/flow-detail.vue
Normal file
47
src/views/flow/flow-detail.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div class="page-content p-10">
|
||||
<div class="d-flex justify-content-between">
|
||||
<van-tag type="primary" size="large">{{ flowData.followType }}</van-tag>
|
||||
<div>{{ flowData.sysDate }}</div>
|
||||
</div>
|
||||
<div class="mt-10" v-if="flowData.fileId">
|
||||
<audio :id="`auto${flowData.id}`" :src="flowData.fileUrl" controls></audio>
|
||||
</div>
|
||||
<div class="mt-10">
|
||||
{{ flowData.introduce }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import emitter from '@/utils/mitt'
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
let id: any = ref('')
|
||||
let flowData: any = ref({})
|
||||
const init = () => {
|
||||
if (route.query && route.query.id) {
|
||||
id.value = route.query.id
|
||||
emitter.emit('showLoading', '')
|
||||
getData()
|
||||
} else {
|
||||
router.push({ name: '404' })
|
||||
}
|
||||
}
|
||||
import { stockCompanyFlowInfoInfo } from '@/utils/api'
|
||||
const getData = async () => {
|
||||
const { data }: any = await stockCompanyFlowInfoInfo(id.value)
|
||||
data.playFlag = false
|
||||
flowData.value = data
|
||||
emitter.emit('setTitle', { title: data.sysDate + data.stockName + data.flowType, type: 'comment' })
|
||||
emitter.emit('hiddenLoading', '')
|
||||
}
|
||||
init()
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
audio {
|
||||
height: 36px;
|
||||
}
|
||||
</style>
|
||||
60
src/views/flow/flow.vue
Normal file
60
src/views/flow/flow.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh" v-if="list.length > 0">
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad"
|
||||
:immediate-check="false">
|
||||
<van-cell v-for="item in list" :key="item.id"
|
||||
@click="$router.push({ name: 'flow-detail', query: { id: item.id } })">
|
||||
<div class="text-start"><van-tag type="primary" size="large">{{ item.followType }}</van-tag></div>
|
||||
<van-text-ellipsis class="text-start mt-10" :content="item.introduce" rows="3" expand-text="展开"
|
||||
collapse-text="收起" @click-action="showOrHiddenText" />
|
||||
<div>{{ item.sysDate }}</div>
|
||||
</van-cell>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
<van-empty v-else />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router'
|
||||
let pageContent = ref(null)
|
||||
const scrollPosition = ref(0)
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
let stockCode: any = ref('')
|
||||
const refresh = () => {
|
||||
if (route.query && route.query.stockCode) {
|
||||
stockCode.value = route.query.stockCode
|
||||
onRefresh()
|
||||
} else {
|
||||
router.push({ name: '404' })
|
||||
}
|
||||
}
|
||||
import { stockCompanyFlowInfoList } from '@/utils/api'
|
||||
const getData = async () => {
|
||||
const { data }: any = await stockCompanyFlowInfoList({
|
||||
stockCode: stockCode.value,
|
||||
curPage: curPage.value,
|
||||
limit: 20
|
||||
})
|
||||
data.list.map((ele: any) => {
|
||||
ele.playFlag = false
|
||||
})
|
||||
return data
|
||||
}
|
||||
const showOrHiddenText = (event: any) => {
|
||||
event.stopPropagation()
|
||||
}
|
||||
import { listLoadAndRefresh } from '@/mixins/list-load-and-refresh'
|
||||
const { refreshing, finished, loading, curPage, list, onLoad, onRefresh } = listLoadAndRefresh(getData, 'stock')
|
||||
import { scrollList } from '@/mixins/scroll-list'
|
||||
const { setScrollTop, setScrollPositionAndRefreshFlag } = scrollList(pageContent, scrollPosition, refresh)
|
||||
|
||||
setScrollTop()
|
||||
onBeforeRouteLeave((to, from) => {
|
||||
setScrollPositionAndRefreshFlag(to, 'track-detail')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped></style>
|
||||
202
src/views/home/index.vue
Normal file
202
src/views/home/index.vue
Normal file
@@ -0,0 +1,202 @@
|
||||
<template>
|
||||
<div class="page-content tabbar-list">
|
||||
<div class="home-top" :class="isNight ? 'night' : ''">
|
||||
<div class="title">{{ tips }}</div>
|
||||
<div class="top-content">
|
||||
<van-image class="banner" src="/img/home-banner.jpg" alt="" />
|
||||
<div class="name">第一上海</div>
|
||||
<div class="welcome">数据管理系统欢迎您</div>
|
||||
</div>
|
||||
</div>
|
||||
<van-grid :column-num="4" :border=false>
|
||||
<van-grid-item v-if="userInfo.roleId === '10' || userInfo.roleId === '1'" class="btn"
|
||||
@click="$router.push({ name: 'organization-list' })">
|
||||
<div class="icon-box organization"><van-icon name="newspaper-o" size="24" /></div>
|
||||
<span>机构</span>
|
||||
</van-grid-item>
|
||||
<van-grid-item class="btn" @click="$router.push({ name: 'meeting-list' })">
|
||||
<div class="icon-box meeting"><van-icon name="calendar-o" size="24" /></div>
|
||||
<span>会议</span>
|
||||
</van-grid-item>
|
||||
<van-grid-item class="btn" @click="$router.push({ name: 'communicate-list' })">
|
||||
<div class="icon-box communicate"><van-icon name="comment-o" size="24" /></div>
|
||||
<span>沟通</span>
|
||||
</van-grid-item>
|
||||
<van-grid-item class="btn" @click="$router.push({ name: 'comment-list' })">
|
||||
<div class="icon-box comment"><van-icon name="diamond-o" size="24" /></div>
|
||||
<span>点评</span>
|
||||
</van-grid-item>
|
||||
<van-grid-item class="btn" @click="$router.push({ name: 'report-list' })">
|
||||
<div class="icon-box report"><van-icon name="description-o" size="24" /></div>
|
||||
<span>报告</span>
|
||||
</van-grid-item>
|
||||
<van-grid-item class="btn" @click="$router.push({ name: 'target-table' })">
|
||||
<div class="icon-box cash"><van-icon name="guide-o" size="24" /></div>
|
||||
<span>指标</span>
|
||||
</van-grid-item>
|
||||
</van-grid>
|
||||
<div class="card">
|
||||
<div class="card-title">今日沟通</div>
|
||||
<communicate-item :list="communicateList" />
|
||||
</div>
|
||||
<tabbar />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import userInfoStore from '@/stores/userInfo'
|
||||
const userInfo = userInfoStore()
|
||||
const time = moment().hour()
|
||||
const isNight = computed(() => time < 6 || time >= 22)
|
||||
const tips = computed(() => {
|
||||
if (time < 6 || time >= 22) {
|
||||
return '夜深了,早点休息'
|
||||
} else if (time >= 19) {
|
||||
return `晚上好,${userInfo.realname}`
|
||||
} else if (time >= 12) {
|
||||
return `下午好,${userInfo.realname}`
|
||||
} else {
|
||||
return `早上好,${userInfo.realname}`
|
||||
}
|
||||
})
|
||||
import { cusLinkList } from '@/utils/api'
|
||||
import { convertToUrl } from '@/utils'
|
||||
import moment from 'moment'
|
||||
const isLogin = computed(() => !!userInfo.token)
|
||||
const communicateList = ref([])
|
||||
const getCusLink = async () => {
|
||||
if (!isLogin.value) return
|
||||
const url = convertToUrl({
|
||||
limit: 3,
|
||||
curPage: 1,
|
||||
dateBegin: moment().format('YYYY-MM-DD')
|
||||
})
|
||||
const data = await cusLinkList(url)
|
||||
communicateList.value = data
|
||||
}
|
||||
getCusLink()
|
||||
import { workMeetingList } from '@/utils/api'
|
||||
const meetingList = ref<Array<any>>([])
|
||||
const getMeetingList = async () => {
|
||||
if (!isLogin.value) return
|
||||
const url = convertToUrl({
|
||||
dateBegin: moment().format('YYYY-MM-DD'),
|
||||
dateEnd: moment().format('YYYY-MM-DD'),
|
||||
meetingType: '1,2,3,16,17,4,5,15,6,10,11,12,13,14,7'
|
||||
})
|
||||
const { data } = await workMeetingList(url)
|
||||
meetingList.value = data.slice(0, 3)
|
||||
}
|
||||
getMeetingList()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.night {
|
||||
background-image: url('/img/night.png');
|
||||
background-repeat: no-repeat;
|
||||
padding-top: 30px;
|
||||
color: #fff;
|
||||
}
|
||||
.home-top {
|
||||
padding: 20px 10px 0;
|
||||
.top-content {
|
||||
height: 120px;
|
||||
z-index: 3;
|
||||
position: relative;
|
||||
@media screen and (min-width: 678px) {
|
||||
height: 160px;
|
||||
}
|
||||
}
|
||||
.title {
|
||||
font-size: 16px;
|
||||
margin-bottom: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.name {
|
||||
padding-top: 30px;
|
||||
font-size: 18px;
|
||||
color: #fff;
|
||||
margin-left: 20px;
|
||||
}
|
||||
.welcome {
|
||||
font-size: 14px;
|
||||
color: #eee;
|
||||
margin-left: 40px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.banner {
|
||||
border-radius: 6px;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
z-index: -1;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
}
|
||||
.btn {
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
transition: all 0.2s ease;
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
opacity: 0.8;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.icon-box {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.organization {
|
||||
background-color: #ff8f00;
|
||||
}
|
||||
.meeting {
|
||||
background-color: #a3cf7d;
|
||||
}
|
||||
.communicate {
|
||||
background-color: #ffc700;
|
||||
padding-top: 2px;
|
||||
}
|
||||
.comment {
|
||||
background-color: #ff758f;
|
||||
}
|
||||
.report {
|
||||
background-color: #5bb1ff;
|
||||
}
|
||||
.cash {
|
||||
background-color: #f15b6c;
|
||||
}
|
||||
}
|
||||
.card {
|
||||
padding: 10px 0;
|
||||
// margin-top: 10px;
|
||||
margin: 10px 12px;
|
||||
.card-title {
|
||||
margin-left: 16px;
|
||||
padding: 0 4px;
|
||||
font-weight: 600;
|
||||
border-left: 4px solid #5bb1ff;
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
.empty {
|
||||
color: #808080;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
:deep(.van-empty__image) {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
</style>
|
||||
135
src/views/internal/meeting.vue
Normal file
135
src/views/internal/meeting.vue
Normal file
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<div class="tabbar-list top-select-list" ref="pageContent">
|
||||
<div class="top-select border-bottom-1">
|
||||
<van-grid :border="false" :gutter="10" :column-num="2">
|
||||
<van-grid-item>
|
||||
<top-select1 :type="'speak_type'" :initialName="'类型'" @refresh="refreshBySpeakType"
|
||||
:selectName="speakTypeName" :selectValue="speakType" />
|
||||
</van-grid-item>
|
||||
</van-grid>
|
||||
</div>
|
||||
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh" v-if="list.length > 0">
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad"
|
||||
:immediate-check="false">
|
||||
<van-cell v-for="(item, index) in list" :key="item.speakId" @click="showDetail(item, index)"
|
||||
:id="item.speakId">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="name1 text-start ">
|
||||
<van-tag :class="item.tagColor" size="medium">{{ item.speakTypeName }}</van-tag>
|
||||
</div>
|
||||
<div class="name1 stockNames balck-text-color">{{ item.sysDate }}</div>
|
||||
</div>
|
||||
</van-cell>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
<van-empty v-else />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, nextTick, onUnmounted } from 'vue'
|
||||
import TopSelect1 from '@/components/top-select1.vue'
|
||||
import emitter from '@/utils/mitt'
|
||||
import { workSpeakList } from '@/utils/api'
|
||||
|
||||
import speakInfoStore from '@/stores/speakInfo'
|
||||
const speakInfo = speakInfoStore()
|
||||
|
||||
import { listLoadAndRefresh } from '@/mixins/list-load-and-refresh'
|
||||
const scrollPage = () => {
|
||||
nextTick(() => {
|
||||
if (speakInfo.speakId && speakInfo.index) {
|
||||
speakTypeName.value = speakInfo.speakTypeName
|
||||
speakType.value = speakInfo.speakType
|
||||
let pageIndex1 = speakInfo.index / 20
|
||||
let pageIndex2 = speakInfo.index % 20
|
||||
if (pageIndex2 > 1) {
|
||||
pageIndex1++
|
||||
}
|
||||
if (pageIndex1 > curPage.value) {
|
||||
onLoad()
|
||||
} else {
|
||||
const el = document.getElementById(speakInfo.speakId)
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
speakInfo.speakId = ''
|
||||
speakInfo.index = 0
|
||||
emitter.emit('hiddenLoading', '')
|
||||
}
|
||||
} else {
|
||||
emitter.emit('hiddenLoading', '')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getData = async () => {
|
||||
const { data } = await workSpeakList({
|
||||
curPage: curPage.value,
|
||||
limit: 20,
|
||||
speakType: speakType.value || null
|
||||
})
|
||||
data.list.map((ele: any) => {
|
||||
switch (ele.speakType) {
|
||||
case 'CH':
|
||||
ele.tagColor = 'primary-background-color'
|
||||
break
|
||||
case 'CF':
|
||||
ele.tagColor = 'danger-background-color'
|
||||
break
|
||||
case 'YJZH':
|
||||
ele.tagColor = 'success-background-color'
|
||||
break
|
||||
case 'SALE':
|
||||
ele.tagColor = 'warning-background-color'
|
||||
break
|
||||
}
|
||||
})
|
||||
return data
|
||||
}
|
||||
const { refreshing, finished, loading, list, onLoad, onRefresh, curPage } = listLoadAndRefresh(getData, 'internal', scrollPage)
|
||||
|
||||
let speakTypeName = ref('类型')
|
||||
let speakType = ref('')
|
||||
|
||||
const refreshBySpeakType = (e: any) => {
|
||||
speakType.value = e.value
|
||||
speakTypeName.value = e.name
|
||||
onRefresh()
|
||||
}
|
||||
|
||||
import { showFile } from '@/mixins/show-file'
|
||||
const { loadingFile } = showFile()
|
||||
const showDetail = (item: any, index: number) => {
|
||||
speakInfo.index = index
|
||||
speakInfo.speakId = item.speakId
|
||||
speakInfo.speakTypeName = speakTypeName.value
|
||||
speakInfo.speakType = speakType.value
|
||||
loadingFile(item.tencentRecordUrl)
|
||||
}
|
||||
|
||||
const onRefresh1 = () => {
|
||||
speakTypeName.value = speakInfo.speakTypeName
|
||||
speakType.value = speakInfo.speakType
|
||||
onRefresh()
|
||||
|
||||
}
|
||||
onRefresh1()
|
||||
|
||||
onUnmounted(() => {
|
||||
speakInfo.speakTypeName = '类型'
|
||||
speakInfo.speakType = ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.grid-item-text {
|
||||
max-width: calc(50vw - 28px) !important;
|
||||
|
||||
@media screen and (min-width: 678px) {
|
||||
max-width: 310px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
683
src/views/meeting/add-or-update-meeting.vue
Normal file
683
src/views/meeting/add-or-update-meeting.vue
Normal file
@@ -0,0 +1,683 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<div class="add-meeting" :class="disabledFlag ? 'detailClass2' : 'detailClass1'">
|
||||
<van-form @submit="onSubmit" validate-trigger="onSubmit">
|
||||
<van-field name="meetingTitle" v-model="popObj.meetingTitle" label="会议标题"
|
||||
:placeholder="disabledFlag ? '' : '请输入会议标题'" :required="true"
|
||||
:rules="[{ required: true, message: '请填写会议标题' }]" :disabled="disabledFlag" />
|
||||
<van-field clickable name="sysDay" v-model="popObj.sysDay" label="会议日期"
|
||||
:placeholder="disabledFlag ? '' : '点击选择日期'" @click="popObj.handleShowPop4('sysDay', '选择日期', 4)"
|
||||
:required="true" :rules="[{ required: true, message: '请选择会议日期' }]" readonly :disabled="disabledFlag" />
|
||||
<van-popup v-model:show="popObj.showPop4" position="bottom">
|
||||
<van-date-picker v-model="popObj.value4" :title="popObj.popTitle" @confirm="popObj.onConfirm4($event)"
|
||||
@cancel="popObj.showPop4 = false" />
|
||||
</van-popup>
|
||||
<van-field clickable name="sysTime" v-model="popObj.sysTime" label="会议时间"
|
||||
:placeholder="disabledFlag ? '' : '点击选择时间'" @click="popObj.handleShowPop4('sysTime', '会议时间', 5)"
|
||||
:required="true" :rules="[{ required: true, message: '请选择会议时间' }]" readonly :disabled="disabledFlag" />
|
||||
<van-popup v-model:show="popObj.showPop5" position="bottom">
|
||||
<van-time-picker v-model="popObj.value5" :title="popObj.popTitle" @confirm="popObj.onConfirm4($event)"
|
||||
@cancel="popObj.showPop5 = false" />
|
||||
</van-popup>
|
||||
<van-field clickable name="meetingTypeName" v-model="popObj.meetingTypeName" label="会议类型"
|
||||
:placeholder="disabledFlag ? '' : '点击选择会议类型'" @click="popObj.handleShowPop1('meetingType', '会议类型')"
|
||||
:required="true" :rules="[{ required: true, message: '请选择会议类型' }]" readonly :disabled="disabledFlag" />
|
||||
<van-popup v-model:show="popObj.showPop1" position="bottom">
|
||||
<van-picker show-toolbar :title="popObj.popTitle" :columns="popObj[`${popObj.type}List`]"
|
||||
@confirm="popObj.onConfirm1($event)" @cancel="popObj.showPop1 = false" v-model="popObj.value1" />
|
||||
</van-popup>
|
||||
<van-field clickable name="meetingByName" v-model="popObj.meetingByName" label="会议形式"
|
||||
:placeholder="disabledFlag ? '' : '点击选择会议形式'" @click="popObj.handleShowPop1('meetingBy', '会议形式')"
|
||||
:required="true" :rules="[{ required: true, message: '请选择会议形式' }]" readonly :disabled="disabledFlag" />
|
||||
<div v-for="(item, index) in popObj.stockCodeList" :key="index">
|
||||
<van-field clickable name="text" v-model="item.text" :label="`股票代码${index + 1}`"
|
||||
:placeholder="disabledFlag ? '' : '点击选择股票'" @click="popObj.handleShowPop6('stockCode', '股票', index)"
|
||||
type="text" autosize readonly :right-icon="disabledFlag ? '' : 'close'"
|
||||
@click-right-icon.stop="popObj.deleteItemByType('stockCode', index)" :disabled="disabledFlag" />
|
||||
</div>
|
||||
<van-field clickable name="newItem" v-model="newItem" label="股票代码" placeholder="点击新增股票"
|
||||
@click="popObj.handleShowPop6('stockCode', '股票', -1)" readonly v-show="!disabledFlag" />
|
||||
<van-popup v-model:show="popObj.showPop2" position="bottom">
|
||||
<van-field v-model="popObj.searchValue" :placeholder="'请输入' + popObj.popTitle"
|
||||
@input="popObj.onSearchInput" />
|
||||
<van-picker show-toolbar :title="popObj.popTitle" :columns="popObj.showPop2List"
|
||||
@confirm="popObj.onConfirm2($event)" @cancel="popObj.showPop2 = false" />
|
||||
</van-popup>
|
||||
<div v-for="(item, index) in popObj.meetingGuestList" :key="index">
|
||||
<van-field clickable name="text" v-model="item.text" :label="`嘉宾${index + 1}`"
|
||||
:placeholder="disabledFlag ? '' : '点击选择宾'" @click="popObj.handleShowPop6('meetingGuest', '嘉宾', index)"
|
||||
type="text" autosize readonly :right-icon="disabledFlag ? '' : 'close'"
|
||||
@click-right-icon.stop="popObj.deleteItemByType('meetingGuest', index)" :disabled="disabledFlag" />
|
||||
</div>
|
||||
<van-field clickable name="newItem" v-model="newItem" label="嘉宾" placeholder="点击新增嘉宾"
|
||||
@click="popObj.handleShowPop6('meetingGuest', '嘉宾', -1)" readonly v-show="!disabledFlag" />
|
||||
<van-field clickable name="linkFromName" v-model="popObj.linkFromName" label="发起人"
|
||||
:placeholder="disabledFlag ? '' : '点击添加发起人'" @click="popObj.handleShowPop5('linkFrom', '发起人')" readonly
|
||||
:disabled="disabledFlag" />
|
||||
<van-popup v-model:show="popObj.showPop6" position="bottom">
|
||||
<div class="p-16 box-popup">
|
||||
<van-row class="mb-16">
|
||||
<van-col span="8">
|
||||
<van-button class="van-picker__cancel" native-type="button"
|
||||
@click="popObj.showPop6 = false">取消</van-button>
|
||||
</van-col>
|
||||
<van-col span="8" class="text-center">
|
||||
<span class="sub-title f-b">请选择{{ popObj.popTitle }}</span>
|
||||
</van-col>
|
||||
<van-col span="8" class="text-end">
|
||||
<van-button class="van-picker__confirm" native-type="button" @click="popObj.onConfirm5">确认</van-button>
|
||||
</van-col>
|
||||
</van-row>
|
||||
<van-radio-group v-model="popObj.linkFrom" @click="popObj.changeLinkFrom">
|
||||
<van-radio :name="1">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="radioLabel">机构人员</div>
|
||||
<van-field class="flex-1 radioContent1 border-1 ms-10" v-model="popObj.linkFromField"
|
||||
placeholder="录入发起人" :disabled="popObj.linkFrom !== 1" />
|
||||
</div>
|
||||
</van-radio>
|
||||
<van-radio :name="2" class="align-items-start-radio">
|
||||
<div class="d-flex align-items-start mt-10">
|
||||
<div class="radioLabel">第一上海</div>
|
||||
<div class="flex-1 radioContent2">
|
||||
<van-radio-group class="ms-10" v-model="popObj.linkFromRadio" direction="horizontal"
|
||||
:disabled="popObj.linkFrom !== 2">
|
||||
<van-radio v-for="(item, index) in popObj.linkFromList" :key="index" :name="item.userId">
|
||||
{{ item.userName }}
|
||||
</van-radio>
|
||||
</van-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
</van-radio>
|
||||
</van-radio-group>
|
||||
</div>
|
||||
</van-popup>
|
||||
<van-field name="phoneBy" v-model="popObj.phoneBy" label="电话拨入方式" :placeholder="disabledFlag ? '' : '请输入电话拨入方式'"
|
||||
:disabled="disabledFlag" />
|
||||
<van-field name="netBy" v-model="popObj.netBy" label="网络端登录方式" :placeholder="disabledFlag ? '' : '请输入网络端登录方式'"
|
||||
:disabled="disabledFlag" />
|
||||
<van-field v-if="popObj.netBy" name="meetingPassword" v-model="popObj.meetingPassword" label="会议密码"
|
||||
placeholder="请输入会议密码" :disabled="disabledFlag" />
|
||||
<van-field clickable name="organizerNames" v-model="popObj.organizerNames" label="相关研究员"
|
||||
:placeholder="disabledFlag ? '' : '点击选择相关研究员'" @click="popObj.handleShowPop3('organizer', '相关研究员')" readonly
|
||||
:disabled="disabledFlag" />
|
||||
<van-popup v-model:show="popObj.showPop3" position="bottom">
|
||||
<div class="p-16 box-popup">
|
||||
<van-row class="mb-16">
|
||||
<van-col span="8">
|
||||
<van-button class="van-picker__cancel" native-type="button"
|
||||
@click="popObj.showPop3 = false">取消</van-button>
|
||||
</van-col>
|
||||
<van-col span="8" class="text-center">
|
||||
<span class="sub-title f-b">请选择{{ popObj.popTitle }}</span>
|
||||
</van-col>
|
||||
<van-col span="8" class="text-end">
|
||||
<van-button class="van-picker__confirm" native-type="button" @click="popObj.onConfirm3">确认</van-button>
|
||||
</van-col>
|
||||
</van-row>
|
||||
<van-checkbox-group v-model="popObj.value3" direction="horizontal">
|
||||
<van-checkbox v-for="(item, index) in popObj[`${popObj.type}List`]" :key="index" :name="item.value"
|
||||
shape="square">
|
||||
{{ item.text }}
|
||||
</van-checkbox>
|
||||
</van-checkbox-group>
|
||||
</div>
|
||||
</van-popup>
|
||||
<van-field name="content" v-model="popObj.content" label="备注" rows="3" type="textarea" autosize
|
||||
:disabled="disabledFlag" />
|
||||
<van-field clickable name="meetingByName" v-model="popObj.visibleName" label="可见范围"
|
||||
:placeholder="disabledFlag ? '' : '点击选择可见范围'" @click="popObj.handleShowPop1('visible', '可见范围')" readonly
|
||||
:disabled="disabledFlag" />
|
||||
<van-field v-if="popObj.selectvisible === '3'" clickable name="staffNames" v-model="popObj.staffNames"
|
||||
label="可见员工" :placeholder="disabledFlag ? '' : '点击选择可见员工'" @click="popObj.handleShowPop3('staff', '可见员工')"
|
||||
readonly :rules="[{ required: popObj.selectvisible === '3', message: '请选择可见员工' }]"
|
||||
:required="popObj.selectvisible === '3'" :disabled="disabledFlag" />
|
||||
<div class="m-16 d-flex justify-content-center">
|
||||
<van-button round block type="warning" size="small" native-type="button" @click="changeDisabled"
|
||||
v-if="meetingId">
|
||||
{{ disabledFlag ? '修改' : '取消' }}
|
||||
</van-button>
|
||||
<van-button v-show="!disabledFlag" round block type="primary" size="small" native-type="submit"
|
||||
:disabled="submitFlag">提交</van-button>
|
||||
</div>
|
||||
</van-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, onUnmounted } from 'vue'
|
||||
import emitter from '@/utils/mitt'
|
||||
import {
|
||||
sysDicListByType, sysUserGetStaffAndManagerList, sysUserGetCompanyUserList, stockStockInfoLookUp,
|
||||
cusUserLookUp, workMeetingUpdateOrSave, workMeetingInfo, workMeetingUserList
|
||||
} from '@/utils/api'
|
||||
let submitFlag = ref(false)
|
||||
let disabledFlag = ref(true)
|
||||
let newItem = ref('')
|
||||
let listIndex = ref(0)
|
||||
let meetingId: any = ref('')
|
||||
import { debounceThrottle } from '@/mixins/debounce-throttle'
|
||||
const { throttle } = debounceThrottle()
|
||||
import { type MeetingObj } from '@/utils/types'
|
||||
const popObj: MeetingObj = reactive({
|
||||
showPop1: false,
|
||||
showPop2: false,
|
||||
showPop3: false,
|
||||
showPop4: false,
|
||||
showPop5: false,
|
||||
showPop6: false,
|
||||
popTitle: '',
|
||||
type: '',
|
||||
value1: [],
|
||||
value3: [],
|
||||
meetingTitle: '',
|
||||
sysDay: '',
|
||||
selectsysDay: [],
|
||||
sysTime: '',
|
||||
selectsysTime: [],
|
||||
meetingTypeName: '',
|
||||
selectmeetingType: '',
|
||||
meetingTypeList: [],
|
||||
meetingByName: '',
|
||||
selectmeetingBy: '',
|
||||
meetingByList: [],
|
||||
stockCodeList: [],
|
||||
searchValue: '',
|
||||
showPop2List: [],
|
||||
meetingGuestList: [],
|
||||
linkFromName: '',
|
||||
linkFrom: 1,
|
||||
linkFromRadio: '',
|
||||
linkFromField: '',
|
||||
linkFromList: [],
|
||||
phoneBy: '',
|
||||
netBy: '',
|
||||
meetingPassword: '',
|
||||
organizerNames: '',
|
||||
selectorganizerIds: [],
|
||||
organizerList: [],
|
||||
content: '',
|
||||
visibleName: '公开',
|
||||
selectvisible: '1',
|
||||
visibleList: [{
|
||||
value: '1',
|
||||
text: '公开'
|
||||
}, {
|
||||
value: '3',
|
||||
text: '员工'
|
||||
}],
|
||||
staffNames: '',
|
||||
staffList: [],
|
||||
selectstaffIds: [],
|
||||
handleShowPop1: (val: string, title: string) => {
|
||||
if (!disabledFlag.value) {
|
||||
popObj.showPop1 = true
|
||||
popObj.popTitle = title
|
||||
popObj.type = val
|
||||
popObj.value1 = [popObj[`select${val}`]]
|
||||
}
|
||||
},
|
||||
handleShowPop3: (val: string, title: string) => {
|
||||
if (!disabledFlag.value) {
|
||||
popObj.showPop3 = true
|
||||
popObj.popTitle = title
|
||||
popObj.type = val
|
||||
popObj.value3 = popObj[`select${val}Ids`]
|
||||
}
|
||||
},
|
||||
handleShowPop4: (val: string, title: string, index: number) => {
|
||||
if (!disabledFlag.value) {
|
||||
popObj.popTitle = title
|
||||
popObj.type = val
|
||||
popObj[`value${index}`] = popObj[`select${val}`]
|
||||
popObj[`showPop${index}`] = true
|
||||
}
|
||||
},
|
||||
handleShowPop5: (val: string, title: string) => {
|
||||
if (!disabledFlag.value) {
|
||||
if (popObj.linkFrom === 1) {
|
||||
popObj.linkFromRadio = ''
|
||||
popObj.linkFromField = popObj.linkFromName
|
||||
} else {
|
||||
const index = popObj.linkFromList.findIndex((ele: any) => {
|
||||
return ele.userName === popObj.linkFromName
|
||||
})
|
||||
if (index > -1) {
|
||||
popObj.linkFromRadio = popObj.linkFromList[index].userId
|
||||
}
|
||||
}
|
||||
popObj.showPop6 = true
|
||||
popObj.popTitle = title
|
||||
popObj.type = val
|
||||
}
|
||||
},
|
||||
handleShowPop6: (val: string, title: string, index: number) => {
|
||||
if (!disabledFlag.value) {
|
||||
popObj.showPop2 = true
|
||||
popObj.popTitle = title
|
||||
popObj.type = val
|
||||
popObj.searchValue = index > -1 ?
|
||||
popObj[`${val}List`][index][`${val === 'stockCode' ? 'value' : 'cusUserName'}`] : ''
|
||||
popObj.showPop2List = index > -1 && popObj[`${val}List`][index].value ?
|
||||
[{ text: popObj[`${val}List`][index].text, value: popObj[`${val}List`][index].value }] : []
|
||||
listIndex.value = index
|
||||
}
|
||||
},
|
||||
onSearchInput: throttle(
|
||||
async () => {
|
||||
if (popObj.searchValue) {
|
||||
if (popObj.type === 'stockCode') {
|
||||
const { data } = await stockStockInfoLookUp({ stockCode: popObj.searchValue })
|
||||
popObj.showPop2List = data.map((ele: any) => {
|
||||
ele.text = `${ele.stockCode}【${ele.stockName}】`
|
||||
ele.value = ele.stockCode
|
||||
return ele
|
||||
})
|
||||
} else if (popObj.type === 'meetingGuest') {
|
||||
const { data } = await cusUserLookUp({ cusUserName: popObj.searchValue })
|
||||
popObj.showPop2List = data.map((ele: any) => {
|
||||
ele.text = `${ele.cusUserName}${ele.cusName ? '【' + ele.cusName + '】' : ''}`
|
||||
ele.value = ele.cusUserId
|
||||
return ele
|
||||
})
|
||||
}
|
||||
} else {
|
||||
popObj.showPop2List = []
|
||||
}
|
||||
},
|
||||
500
|
||||
),
|
||||
deleteItemByType: (type: string, index: number) => {
|
||||
if (!disabledFlag.value) {
|
||||
popObj[`${type}List`].splice(index, 1)
|
||||
}
|
||||
},
|
||||
onConfirm1: ({ selectedOptions }) => {
|
||||
if (selectedOptions) {
|
||||
popObj[`select${popObj.type}`] = selectedOptions[0].value
|
||||
popObj[`${popObj.type}Name`] = selectedOptions[0].text
|
||||
if (popObj.type === 'meetingBy') {
|
||||
if (selectedOptions[0].value === '1') {
|
||||
popObj.phoneBy = '香港:852-3005-1318\n大陆:400-810-5222\n'
|
||||
} else if (selectedOptions[0].value == 2) {
|
||||
popObj.phoneBy = '+8675536550000\n+85230018898'
|
||||
} else {
|
||||
popObj.phoneBy = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
popObj.showPop1 = false
|
||||
},
|
||||
onConfirm2: ({ selectedOptions }) => {
|
||||
if (selectedOptions && selectedOptions[0]) {
|
||||
if (listIndex.value > -1) {
|
||||
popObj[`${popObj.type}List`][listIndex.value].value = selectedOptions[0].value
|
||||
popObj.stockCodeList[listIndex.value][`${popObj.type === 'stockCode' ? 'stockName' : 'cusUserName'}`] =
|
||||
selectedOptions[0][`${popObj.type === 'stockCode' ? 'stockName' : 'cusUserName'}`]
|
||||
popObj[`${popObj.type}List`][listIndex.value].text = selectedOptions[0].text
|
||||
} else {
|
||||
let data: any = {
|
||||
value: selectedOptions[0].value,
|
||||
text: selectedOptions[0].text
|
||||
}
|
||||
if (popObj.type === 'stockCode') {
|
||||
data.stockName = selectedOptions[0].stockName
|
||||
} else if (popObj.type === 'meetingGuest') {
|
||||
data.cusUserName = selectedOptions[0].cusUserName
|
||||
}
|
||||
popObj[`${popObj.type}List`].push(data)
|
||||
}
|
||||
popObj.showPop2 = false
|
||||
} else {
|
||||
if (popObj.type === 'stockCode') {
|
||||
showToast(`请选择${popObj.popTitle}后,再确认`)
|
||||
} else {
|
||||
popObj.meetingGuestList.push({
|
||||
value: '',
|
||||
cusUserName: popObj.searchValue,
|
||||
text: popObj.searchValue
|
||||
})
|
||||
popObj.showPop2 = false
|
||||
}
|
||||
}
|
||||
},
|
||||
onConfirm3: () => {
|
||||
popObj[`select${popObj.type}Ids`] = popObj.value3
|
||||
const sameList = popObj[`${popObj.type}List`].filter((ele: any) => popObj.value3.some((res: any) => {
|
||||
return ele.value === res
|
||||
}))
|
||||
let names = ''
|
||||
sameList.forEach((ele: any) => {
|
||||
names += `${ele.text},`
|
||||
})
|
||||
if (names) {
|
||||
names = names.substring(0, names.length - 1)
|
||||
}
|
||||
popObj[`${popObj.type}Names`] = names
|
||||
popObj.showPop3 = false
|
||||
},
|
||||
onConfirm4: ({ selectedValues }) => {
|
||||
if (selectedValues) {
|
||||
popObj[popObj.type] = `${selectedValues[0]}${popObj.type === 'sysDay' ? '-' + selectedValues[1] + '-' + selectedValues[2] : ':' + selectedValues[1]}`
|
||||
popObj[`select${popObj.type}`] = selectedValues
|
||||
}
|
||||
if (popObj.type === 'sysDay') {
|
||||
popObj.showPop4 = false
|
||||
} else {
|
||||
popObj.showPop5 = false
|
||||
}
|
||||
},
|
||||
changeLinkFrom: () => {
|
||||
if (popObj.linkFrom === 1) {
|
||||
popObj.linkFromRadio = ''
|
||||
} else if (popObj.linkFrom === 2) {
|
||||
popObj.linkFromField = ''
|
||||
}
|
||||
},
|
||||
onConfirm5: () => {
|
||||
if (popObj.linkFrom === 1) {
|
||||
popObj.linkFromName = popObj.linkFromField
|
||||
popObj.linkFromRadio = ''
|
||||
popObj.showPop6 = false
|
||||
} else if (popObj.linkFrom === 2) {
|
||||
popObj.linkFromField = ''
|
||||
const index = popObj.linkFromList.findIndex((ele: any) => {
|
||||
return ele.userId === popObj.linkFromRadio
|
||||
})
|
||||
if (index > -1) {
|
||||
popObj.linkFromName = popObj.linkFromList[index].userName
|
||||
popObj.showPop6 = false
|
||||
} else {
|
||||
showToast('请选择发起人')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
import moment from 'moment'
|
||||
const getSelectList = async () => {
|
||||
emitter.emit('showLoading', '')
|
||||
const data1 = await sysDicListByType({ dicType: 'meeting_type' })
|
||||
popObj.meetingTypeList = data1.data.map((ele: any) => {
|
||||
ele.text = ele.dicValue
|
||||
ele.value = ele.dicKey
|
||||
return ele
|
||||
})
|
||||
const data2 = await sysDicListByType({ dicType: 'meeting_by' })
|
||||
popObj.meetingByList = data2.data.map((ele: any) => {
|
||||
ele.text = ele.dicValue
|
||||
ele.value = ele.dicKey
|
||||
return ele
|
||||
})
|
||||
const data3 = await sysUserGetStaffAndManagerList()
|
||||
popObj.organizerList = data3.data.map((ele: any) => {
|
||||
ele.value = ele.userId
|
||||
ele.text = ele.userName
|
||||
return ele
|
||||
})
|
||||
const data4 = await sysUserGetCompanyUserList()
|
||||
popObj.staffList = data4.data.map((ele: any) => {
|
||||
ele.value = ele.userId
|
||||
ele.text = ele.userName
|
||||
return ele
|
||||
})
|
||||
popObj.linkFromList = data4.data
|
||||
if (route.query && route.query.addDay) {
|
||||
disabledFlag.value = false
|
||||
popObj.sysDay = route.query.addDay
|
||||
popObj.selectsysDay = popObj.sysDay.split('-')
|
||||
popObj.sysTime = '08:00'
|
||||
popObj.selectsysTime = ['08', '00']
|
||||
emitter.emit('hiddenLoading', '')
|
||||
} else if (route.query && route.query.meetingId) {
|
||||
disabledFlag.value = true
|
||||
meetingId.value = route.query.meetingId
|
||||
getMeeingInfo()
|
||||
emitter.emit('setTitle', { title: '修改会议', type: 'meetingType' })
|
||||
} else {
|
||||
disabledFlag.value = false
|
||||
let nowDay = moment().format('YYYY-MM-DD')
|
||||
popObj.sysDay = nowDay
|
||||
popObj.selectsysDay = nowDay.split('-')
|
||||
popObj.sysTime = '08:00'
|
||||
popObj.selectsysTime = ['08', '00']
|
||||
emitter.emit('setTitle', { title: '新增会议', type: 'meetingType' })
|
||||
emitter.emit('hiddenLoading', '')
|
||||
}
|
||||
}
|
||||
|
||||
const getMeeingInfo = async () => {
|
||||
const { data } = await workMeetingInfo(meetingId.value)
|
||||
popObj.meetingTitle = data.meetingTitle
|
||||
popObj.sysDay = data.meetingTime.slice(0, 10)
|
||||
popObj.selectsysDay = data.meetingTime.split('-')
|
||||
popObj.sysTime = data.meetingTime.slice(11, 16)
|
||||
popObj.selectsysTime = data.meetingTime.split(':')
|
||||
popObj.meetingTypeName = data.meetingTypeName
|
||||
popObj.selectmeetingType = data.meetingType + ''
|
||||
popObj.meetingByName = data.meetingByName
|
||||
popObj.selectmeetingBy = data.meetingBy + ''
|
||||
let codeList = data.stockCodes ? data.stockCodes.split(',') : []
|
||||
let nameList = data.stockNames ? data.stockNames.split(',') : []
|
||||
popObj.stockCodeList = codeList.map((ele: any, index: number) => {
|
||||
return {
|
||||
value: ele,
|
||||
text: `${ele}${nameList.length > index ? '【' + nameList[index] + '】' : ''}`,
|
||||
stockName: nameList.length > index ? nameList[index] : '',
|
||||
}
|
||||
})
|
||||
let guestNameList = data.meetingGuestNames ? data.meetingGuestNames.split(',') : []
|
||||
let guestIdList = data.meetingGuestIds ? data.meetingGuestIds.split(',') : []
|
||||
popObj.meetingGuestList = guestNameList.map((ele: any, index: number) => {
|
||||
return {
|
||||
value: guestIdList.length > index ? guestIdList[index] : '',
|
||||
text: ele,
|
||||
cusUserName: ele,
|
||||
}
|
||||
})
|
||||
popObj.linkFrom = data.oranizerId && data.oranizerId !== -1 ? 2 : 1
|
||||
popObj.linkFromName = data.oranizerName
|
||||
popObj.phoneBy = data.phoneBy
|
||||
popObj.netBy = data.netBy
|
||||
popObj.meetingPassword = data.meetingPassword
|
||||
popObj.organizerNames = data.oranizerName
|
||||
popObj.selectorganizerIds = data.oranizerId ? data.oranizerId.split(',').map(Number) : []
|
||||
popObj.content = data.content
|
||||
popObj.visibleName = data.visible === 1 ? '公开' : '员工'
|
||||
popObj.selectvisible = data.visible + ''
|
||||
if (data.visible === 3) {
|
||||
getVisible()
|
||||
}
|
||||
emitter.emit('hiddenLoading', '')
|
||||
}
|
||||
|
||||
const getVisible = async () => {
|
||||
const { data } = await workMeetingUserList({ meetingId: meetingId.value })
|
||||
let staffNames = ''
|
||||
let selectstaffIds: any = []
|
||||
data.forEach((ele: any, index: number) => {
|
||||
staffNames += `${ele.userName}${index < data.length - 1 ? ',' : ''}`
|
||||
selectstaffIds.push(ele.userId)
|
||||
})
|
||||
popObj.staffNames = staffNames
|
||||
popObj.selectstaffIds = selectstaffIds
|
||||
}
|
||||
|
||||
const handleKeyUp = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('keyup', handleKeyUp)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('keyup', handleKeyUp)
|
||||
})
|
||||
|
||||
import listRefreshInfoStore from '@/stores/listRefreshInfo'
|
||||
const listRefreshInfo = listRefreshInfoStore()
|
||||
const onSubmit = async () => {
|
||||
submitFlag.value = true
|
||||
let stockCodeList: any = []
|
||||
popObj.stockCodeList.forEach((ele: any) => {
|
||||
if (ele.value) {
|
||||
stockCodeList.push({
|
||||
stockCode: ele.value,
|
||||
stockName: ele.stockName
|
||||
})
|
||||
}
|
||||
})
|
||||
let meetingGuestList: any = []
|
||||
popObj.meetingGuestList.forEach((ele: any) => {
|
||||
if (ele.cusUserName) {
|
||||
meetingGuestList.push({
|
||||
cusUserId: ele.value || -1,
|
||||
cusUserName: ele.cusUserName
|
||||
})
|
||||
}
|
||||
})
|
||||
const res: any = {
|
||||
meetingTitle: popObj.meetingTitle,
|
||||
meetingTime: `${popObj.sysDay} ${popObj.sysTime}:00`,
|
||||
meetingType: popObj.selectmeetingType,
|
||||
meetingBy: popObj.selectmeetingBy,
|
||||
stockCodeList: stockCodeList || [],
|
||||
meetingGuestList: meetingGuestList || [],
|
||||
linkFrom: popObj.linkFromName || null,
|
||||
linkFromId: popObj.linkFrom === 2 && popObj.linkFromRadio ? popObj.linkFromRadio : null,
|
||||
phoneBy: popObj.phoneBy || null,
|
||||
netBy: popObj.netBy || null,
|
||||
meetingPassword: popObj.netBy && popObj.meetingPassword ? popObj.meetingPassword : null,
|
||||
organizerIdList: popObj.selectorganizerIds || [],
|
||||
content: popObj.content || null,
|
||||
visible: popObj.selectvisible,
|
||||
userIdList: popObj.selectvisible === '3' ? popObj.selectstaffIds : [],
|
||||
meetingId: meetingId.value || null
|
||||
}
|
||||
const data: any = await workMeetingUpdateOrSave(meetingId.value, res)
|
||||
if (data.code === 0) {
|
||||
showToast('提交成功')
|
||||
if (meetingId.value) {
|
||||
listRefreshInfo.refreshId = meetingId.value
|
||||
} else {
|
||||
listRefreshInfo.addId = '1'
|
||||
}
|
||||
router.go(-1)
|
||||
} else {
|
||||
submitFlag.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const changeDisabled = () => {
|
||||
disabledFlag.value = !disabledFlag.value
|
||||
if (disabledFlag.value) {
|
||||
getMeeingInfo()
|
||||
}
|
||||
}
|
||||
|
||||
getSelectList()
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.add-meeting {
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.detailClass1 {
|
||||
.van-cell {
|
||||
margin: 12px 16px 0;
|
||||
width: calc(100vw - 32px);
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
|
||||
|
||||
@media screen and (min-width: 678px) {
|
||||
width: 646px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.detailClass2 {
|
||||
.van-cell {
|
||||
margin: 0 16px;
|
||||
width: calc(100vw - 32px);
|
||||
box-shadow: 0 0;
|
||||
|
||||
@media screen and (min-width: 678px) {
|
||||
width: 646px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.van-field--disabled) .van-field__label {
|
||||
color: #646566;
|
||||
}
|
||||
|
||||
:deep(.van-field__control:disabled) {
|
||||
-webkit-text-fill-color: #000 !important;
|
||||
}
|
||||
|
||||
:deep(.van-field__control) {
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
:deep(.van-checkbox) {
|
||||
margin-right: 0 !important;
|
||||
width: 50%;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
:deep(.van-radio) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.radioLabel {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.radioContent1 {
|
||||
max-width: 220px;
|
||||
margin-left: 10px !important;
|
||||
}
|
||||
|
||||
.radioContent2 {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
|
||||
.van-radio {
|
||||
margin-right: 0 !important;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
width: 50%;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.align-items-start-radio) {
|
||||
align-items: flex-start !important;
|
||||
max-height: 300px;
|
||||
|
||||
.van-radio__icon {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.van-radio-group {
|
||||
.van-cell {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ms-10) {
|
||||
.van-radio__icon {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
436
src/views/meeting/detail.vue
Normal file
436
src/views/meeting/detail.vue
Normal file
@@ -0,0 +1,436 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<div class="meeting-detail">
|
||||
<div class="normal-title text-center p-10 f-b">
|
||||
{{ meetingData.meetingTitle }}
|
||||
<van-icon v-if="isAuth('calendar:update') || userInfo.id === meetingData.createUser" name="edit"
|
||||
class="warning-text-color"
|
||||
@click="$router.push({ name: 'add-or-update-meeting', query: { meetingId: meetingId } })" />
|
||||
<van-icon v-if="isAuth('calendar:update') || userInfo.id === meetingData.createUser" name="delete-o"
|
||||
class="danger-text-color" @click="deleteMeeting" />
|
||||
</div>
|
||||
<div class="text-center m-5 f-b">{{ meetingData.meetingTime }}</div>
|
||||
<div class=" text-center m-5">
|
||||
<van-tag type="success" size="medium">{{ meetingData.meetingTypeName }}</van-tag>
|
||||
</div>
|
||||
<van-grid class="baseData" :column-num="2">
|
||||
<van-grid-item>会议形式</van-grid-item>
|
||||
<van-grid-item>{{ meetingData.meetingByName }}</van-grid-item>
|
||||
<van-grid-item>股票名称</van-grid-item>
|
||||
<van-grid-item>
|
||||
<div class="grid-item-text">{{ meetingData.stockNames }}</div>
|
||||
</van-grid-item>
|
||||
<van-grid-item>股票代码</van-grid-item>
|
||||
<van-grid-item>
|
||||
<div class="grid-item-text">{{ meetingData.stockCodes }}</div>
|
||||
</van-grid-item>
|
||||
<van-grid-item>电话拨入方式</van-grid-item>
|
||||
<van-grid-item>
|
||||
<div class="grid-item-text">{{ meetingData.phoneBy }}</div>
|
||||
</van-grid-item>
|
||||
<van-grid-item>网络端登录方式</van-grid-item>
|
||||
<van-grid-item>
|
||||
<div class="grid-item-text">{{ meetingData.netBy }}</div>
|
||||
</van-grid-item>
|
||||
<van-grid-item>会议密码</van-grid-item>
|
||||
<van-grid-item>
|
||||
<div class="grid-item-text">{{ meetingData.meetingPassword }}</div>
|
||||
</van-grid-item>
|
||||
<van-grid-item>嘉宾</van-grid-item>
|
||||
<van-grid-item>
|
||||
<div class="grid-item-text">{{ meetingData.meetingGuestList }}</div>
|
||||
</van-grid-item>
|
||||
<van-grid-item>研究员</van-grid-item>
|
||||
<van-grid-item>
|
||||
<div class="grid-item-text">{{ meetingData.oranizerName }}</div>
|
||||
</van-grid-item>
|
||||
<van-grid-item>发起人</van-grid-item>
|
||||
<van-grid-item>
|
||||
<div class="grid-item-text">{{ meetingData.organizer }}</div>
|
||||
</van-grid-item>
|
||||
<van-grid-item>IS参会人数</van-grid-item>
|
||||
<van-grid-item>{{ meetingData.meetingCusUserNum }}</van-grid-item>
|
||||
<van-grid-item>IS提问人数</van-grid-item>
|
||||
<van-grid-item>{{ meetingData.questionNum }}</van-grid-item>
|
||||
<van-grid-item>总参会人数</van-grid-item>
|
||||
<van-grid-item>{{ meetingData.userNumHigh }}</van-grid-item>
|
||||
<van-grid-item>总提问人数</van-grid-item>
|
||||
<van-grid-item>{{ meetingData.queationNumAll }}</van-grid-item>
|
||||
<van-grid-item>可见范围</van-grid-item>
|
||||
<van-grid-item>
|
||||
<div class="grid-item-text">{{ meetingData.visible === 1 ? '公开' : meetingData.visibleName }}</div>
|
||||
</van-grid-item>
|
||||
<van-grid-item>备注</van-grid-item>
|
||||
<van-grid-item>
|
||||
<div class="grid-item-text">{{ meetingData.content }}</div>
|
||||
</van-grid-item>
|
||||
</van-grid>
|
||||
<div class="sub-title m-16">会议资料</div>
|
||||
<file-list ref="meetingFile" />
|
||||
<div class="sub-title m-16">参会资料</div>
|
||||
<file-list ref="joinMeetingFile" />
|
||||
<div class="sub-title m-16 d-flex align-items-center">
|
||||
参会人员
|
||||
<van-button type="primary" size="mini" @click="addJoinPerson(1)" style="margin-left: 4px">报名</van-button>
|
||||
<van-button type="primary" size="mini" @click="addJoinPerson(2)">签到</van-button>
|
||||
</div>
|
||||
<van-popup v-model:show="showPersonPopup" position="bottom">
|
||||
<div class="p-10 box-popup">
|
||||
<div class="personTitle">当前添加联系人:{{ addPerson }}</div>
|
||||
<van-field v-model="searchCuseUser" placeholder="查找联系人" @input="onSearchInput"
|
||||
class="border-1 border-radius-8" />
|
||||
<div class="tip">
|
||||
<van-row v-if="personType === 2 || personType === 3">
|
||||
<van-col span="12">
|
||||
<van-field name="switch" label="签到">
|
||||
<template #input>
|
||||
<van-switch v-model="attend" size="20" :disabled="personType === 2" />
|
||||
</template>
|
||||
</van-field>
|
||||
</van-col>
|
||||
<van-col span="12">
|
||||
<van-field name="switch" label="提问" v-show="attend">
|
||||
<template #input>
|
||||
<van-switch v-model="question" size="20" />
|
||||
</template>
|
||||
</van-field>
|
||||
</van-col>
|
||||
</van-row>
|
||||
<van-picker show-toolbar title="" :columns="cusUserList" @click-option="onCusUserConfirm"
|
||||
@cancel="showPersonPopup = false">
|
||||
<template #confirm>
|
||||
<span></span>
|
||||
</template>
|
||||
</van-picker>
|
||||
</div>
|
||||
</div>
|
||||
</van-popup>
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
|
||||
<van-swipe-cell v-for="(item, index) in joinPersonList" :key="item.cusUserId">
|
||||
<van-cell>
|
||||
<div class="sub-title text-start balck-text-color">{{ item.cusUserName }}</div>
|
||||
<div class="d-flex">
|
||||
<div class="flex-1">
|
||||
<div class="content text-start">{{ item.cusName }}</div>
|
||||
<div class="content text-start">{{ item.positionName }}</div>
|
||||
</div>
|
||||
<div class="flex-1 text-end">
|
||||
<div class="content" :class="item.attend ? 'primary-text-color' : ''">
|
||||
{{ item.attend ? '已到场' : '已报名' }}
|
||||
</div>
|
||||
<div class="content" v-if="item.attend" :class="item.question ? 'danger-text-color' : ''">
|
||||
{{ item.attend ? item.question ? '已提问' : '未提问' : '' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-cell>
|
||||
<template #right>
|
||||
<van-button square type="danger" text="删除" @click="deleteJoinPerson(item, index)" />
|
||||
<van-button square type="primary" text="修改" @click="changeJoinPerson(item)" />
|
||||
</template>
|
||||
</van-swipe-cell>
|
||||
</van-list>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, nextTick, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import FileList from '@/components/file-list.vue'
|
||||
import emitter from '@/utils/mitt'
|
||||
import { isAuth } from '@/utils/index'
|
||||
import userInfoStore from '@/stores/userInfo'
|
||||
const userInfo = userInfoStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
let meetingData = ref({
|
||||
meetingTime: '',
|
||||
meetingTitle: '',
|
||||
meetingTypeName: '',
|
||||
meetingByName: '',
|
||||
stockCodes: '',
|
||||
stockNames: '',
|
||||
phoneBy: '',
|
||||
netBy: '',
|
||||
meetingPassword: '',
|
||||
meetingCusUserNum: '',
|
||||
questionNum: '',
|
||||
userNumHigh: '',
|
||||
queationNumAll: '',
|
||||
meetingGuestList: '',
|
||||
organizer: '',
|
||||
oranizerName: '',
|
||||
visible: undefined,
|
||||
visibleName: '',
|
||||
content: '',
|
||||
createUser: ''
|
||||
})
|
||||
let meetingId: any = ref('')
|
||||
const meetingFile: any = ref(null)
|
||||
const joinMeetingFile: any = ref(null)
|
||||
import fileInfoStore from '@/stores/fileInfo'
|
||||
const fileInfo = fileInfoStore()
|
||||
|
||||
const init = () => {
|
||||
if (route.query && route.query.meetingId) {
|
||||
meetingId.value = route.query.meetingId
|
||||
emitter.emit('showLoading', '')
|
||||
getMeetingDetail()
|
||||
} else {
|
||||
router.push({ name: '404' })
|
||||
}
|
||||
}
|
||||
|
||||
import {
|
||||
workMeetingInfo, workMeetingUserList, workMeetingGetFileId, workMeetinGetUserFileMenuId,
|
||||
cusUserMeetingLogList, cusUserMeetingLogDelete, cusUserLookUp, cusUserMeetingLogUpdateOrSave,
|
||||
workMeetingDelete
|
||||
} from '@/utils/api'
|
||||
const getMeetingDetail = async () => {
|
||||
const data1: any = await workMeetingInfo(meetingId.value)
|
||||
let meetingGuestNameList = data1.data.meetingGuestNames
|
||||
? data1.data.meetingGuestNames.split(',')
|
||||
: []
|
||||
let meetingGuestList = ''
|
||||
meetingGuestNameList.forEach((item: any, index: number) => {
|
||||
meetingGuestList +=
|
||||
`
|
||||
${item}
|
||||
${index < meetingGuestNameList.length - 1 ? ',' : ''}
|
||||
`
|
||||
})
|
||||
data1.data.meetingGuestList = meetingGuestList
|
||||
meetingData.value = data1.data
|
||||
emitter.emit('setTitle', { title: meetingData.value.meetingTitle, type: 'meeting' })
|
||||
if (data1.data.visible === 3) {
|
||||
getVisible()
|
||||
}
|
||||
const data2: any = await workMeetingGetFileId({ meetingId: meetingId.value })
|
||||
if (meetingFile.value) {
|
||||
meetingFile.value.init(data2.data)
|
||||
}
|
||||
const data3: any = await workMeetinGetUserFileMenuId({ meetingId: meetingId.value })
|
||||
if (joinMeetingFile.value) {
|
||||
joinMeetingFile.value.init(data3.data)
|
||||
}
|
||||
nextTick(() => {
|
||||
if (fileInfo.fileId) {
|
||||
const el = document.getElementById(fileInfo.fileId)
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
fileInfo.fileId = ''
|
||||
emitter.emit('hiddenLoading', '')
|
||||
} else {
|
||||
emitter.emit('hiddenLoading', '')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getVisible = async () => {
|
||||
const { data } = await workMeetingUserList({ meetingId: meetingId.value })
|
||||
let name = ''
|
||||
data.forEach((ele: any, index: number) => {
|
||||
name += `${ele.userName}${index < data.length - 1 ? ',' : ''}`
|
||||
})
|
||||
meetingData.value.visibleName = name
|
||||
}
|
||||
let finished = ref(false)
|
||||
let loading = ref(false)
|
||||
let curPage = ref(1)
|
||||
let joinPersonList = ref<{ cusUserId: string, cusUserName: string, cusName: string, positionName: string, attend: boolean, question: boolean }[]>([])
|
||||
const onLoad = async () => {
|
||||
loading.value = true
|
||||
const { data } = await cusUserMeetingLogList({
|
||||
curPage: curPage.value,
|
||||
limit: 20,
|
||||
meetingId: meetingId.value
|
||||
})
|
||||
|
||||
data.list.map((ele: any) => {
|
||||
ele.question = !!ele.question
|
||||
ele.attend = !!ele.attend
|
||||
})
|
||||
joinPersonList.value = joinPersonList.value.concat(data.list)
|
||||
if (joinPersonList.value.length >= data.totalCount) {
|
||||
finished.value = true
|
||||
} else {
|
||||
curPage.value++
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
let attend = ref(false)
|
||||
let question = ref(false)
|
||||
let searchCuseUser = ref('')
|
||||
let meetingUserId = ref('')
|
||||
let personType = ref()
|
||||
let showPersonPopup = ref(false)
|
||||
let addPerson = ref('')
|
||||
let cusUserList = ref<{ text: string, value: string, cusUserName: string, cusName: string, positionName: string, cusUserId: string }[]>([])
|
||||
const addJoinPerson = (index: number) => {
|
||||
personType.value = index
|
||||
searchCuseUser.value = ''
|
||||
attend.value = false
|
||||
if (index === 2) {
|
||||
attend.value = true
|
||||
}
|
||||
question.value = false
|
||||
cusUserList.value = []
|
||||
meetingUserId.value = ''
|
||||
showPersonPopup.value = true
|
||||
}
|
||||
|
||||
const changeJoinPerson = (e: any) => {
|
||||
personType.value = 3
|
||||
searchCuseUser.value = e.cusUserName
|
||||
attend.value = e.attend
|
||||
question.value = e.question
|
||||
cusUserList.value = [{
|
||||
cusUserName: e.cusUserName,
|
||||
cusName: e.cusName,
|
||||
positionName: e.positionName,
|
||||
cusUserId: e.cusUserId,
|
||||
text: `${e.cusUserName}${e.cusName ? '【' + e.cusName + '】' : ''}${e.positionName ? '(' + e.positionName + ')' : ''}`,
|
||||
value: e.cusUserId
|
||||
}]
|
||||
meetingUserId.value = e.meetingUserId
|
||||
showPersonPopup.value = true
|
||||
}
|
||||
|
||||
import { debounceThrottle } from '@/mixins/debounce-throttle'
|
||||
const { throttle } = debounceThrottle()
|
||||
const onSearchInput = throttle(
|
||||
async () => {
|
||||
if (searchCuseUser.value) {
|
||||
const { data } = await cusUserLookUp({ cusUserName: searchCuseUser.value })
|
||||
cusUserList.value = data.map((ele: any) => {
|
||||
ele.text = `${ele.cusUserName}${ele.cusName ? '【' + ele.cusName + '】' : ''}${ele.positionName ? '(' + ele.positionName + ')' : ''}`
|
||||
ele.value = ele.cusUserId
|
||||
return ele
|
||||
})
|
||||
if (data.length === 1) {
|
||||
const res = {
|
||||
cusUserId: data[0].cusUserId,
|
||||
attend: attend.value ? 1 : 0,
|
||||
question: question.value ? 1 : 0,
|
||||
meetingId: meetingId.value,
|
||||
meetingUserId: meetingUserId.value || null
|
||||
}
|
||||
await cusUserMeetingLogUpdateOrSave(meetingUserId.value, res)
|
||||
addPerson.value = `${data[0].cusUserName},${addPerson.value}`
|
||||
searchCuseUser.value = ''
|
||||
cusUserList.value = []
|
||||
}
|
||||
} else {
|
||||
cusUserList.value = []
|
||||
}
|
||||
},
|
||||
500
|
||||
)
|
||||
|
||||
const onCusUserConfirm = async (e: any) => {
|
||||
if (e.selectedIndexes[0] > -1) {
|
||||
const res = {
|
||||
cusUserId: e.selectedOptions[0].value,
|
||||
attend: attend.value ? 1 : 0,
|
||||
question: question.value ? 1 : 0,
|
||||
meetingId: meetingId.value,
|
||||
meetingUserId: meetingUserId.value || null
|
||||
}
|
||||
await cusUserMeetingLogUpdateOrSave(meetingUserId.value, res)
|
||||
addPerson.value = `${e.selectedOptions[0].cusUserName},${addPerson.value}`
|
||||
searchCuseUser.value = ''
|
||||
cusUserList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => showPersonPopup.value, (val) => {
|
||||
if (!val) {
|
||||
curPage.value = 1
|
||||
joinPersonList.value = []
|
||||
finished.value = false
|
||||
addPerson.value = ''
|
||||
onLoad()
|
||||
}
|
||||
})
|
||||
|
||||
const deleteJoinPerson = (e: any, index: number) => {
|
||||
showConfirmDialog({
|
||||
title: '提示',
|
||||
message:
|
||||
`是否确认删除${e.cusUserName}参加${meetingData.value.meetingTime} ${meetingData.value.meetingTitle}记录`,
|
||||
}).then(async () => {
|
||||
const data: any = await cusUserMeetingLogDelete([e.meetingUserId])
|
||||
if (data.code === 0) {
|
||||
showToast('删除成功')
|
||||
joinPersonList.value.splice(index, 1)
|
||||
}
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
init()
|
||||
|
||||
import listRefreshInfoStore from '@/stores/listRefreshInfo'
|
||||
const listRefreshInfo = listRefreshInfoStore()
|
||||
const deleteMeeting = () => {
|
||||
showConfirmDialog({
|
||||
title: '提示',
|
||||
message:
|
||||
`是否确认删除会议${meetingData.value.meetingTitle}`,
|
||||
}).then(async () => {
|
||||
const data: any = await workMeetingDelete(meetingId.value)
|
||||
if (data.code === 0) {
|
||||
showToast('删除成功')
|
||||
listRefreshInfo.deleteId = meetingId.value
|
||||
router.go(-1)
|
||||
}
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.meeting-detail {
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.baseData {
|
||||
:deep(.van-grid-item:nth-child(2n)) {
|
||||
.van-grid-item__content {
|
||||
color: #1989fa;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.van-grid-item__content) {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
padding: 8px 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.grid-item-text {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
max-width: calc(50vw - 16px);
|
||||
|
||||
@media screen and (min-width: 678px) {
|
||||
max-width: 320px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.van-swipe-cell__right) {
|
||||
.van-button {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.personTitle {
|
||||
height: 60px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
237
src/views/meeting/list.vue
Normal file
237
src/views/meeting/list.vue
Normal file
@@ -0,0 +1,237 @@
|
||||
<template>
|
||||
<div class="page-content tabbar-list top-select-list" ref="pageContent">
|
||||
<div class="addDiv success-background-color" v-if="isAuth('calendar:update')"
|
||||
@click=" $router.push({ name: 'add-or-update-meeting' , query: { addDay: selectDay } })">
|
||||
<div class="text-center addDiv1">新增</div>
|
||||
<div class="text-center addDiv2">会议</div>
|
||||
</div>
|
||||
<div class="top-select border-bottom-1">
|
||||
<van-dropdown-menu>
|
||||
<van-dropdown-item :title="searchContent" ref="itemRef">
|
||||
<van-field v-model="search" placeholder="搜索" :right-icon="search ? 'clear' : ''"
|
||||
@click-right-icon.stop="clearSearch" />
|
||||
<div style="padding: 5px 16px;">
|
||||
<van-button type="primary" size="small" block round @click="onConfirm">
|
||||
确认
|
||||
</van-button>
|
||||
</div>
|
||||
</van-dropdown-item>
|
||||
<van-dropdown-item v-model="meetingType" :options="meetingTypeList" @change="getMeetingList" />
|
||||
<van-dropdown-item v-model="createUser" :options="createUserList" @change="getMeetingList" />
|
||||
</van-dropdown-menu>
|
||||
</div>
|
||||
<van-calendar title="" :poppable="false" :show-confirm="false" switch-mode="year-month" first-day-of-week="1"
|
||||
:style="{ height: '410px' }" :formatter="formatter" :default-date="defaultDate" @panel-change="panelChange"
|
||||
@select="onDateConfirm">
|
||||
</van-calendar>
|
||||
<div v-if="list.length > 0">
|
||||
<van-cell v-for="item in list" :key="item.meetingId"
|
||||
@click="$router.push({ name: 'meeting-detail', query: { meetingId: item.meetingId } })">
|
||||
<div class="text-start activeColor sub-title">{{ item.meetingHour }}</div>
|
||||
<div class="normal-title text-start balck-text-color mb-5 mt-5">{{ item.meetingTitle }}</div>
|
||||
<div class="text-end">
|
||||
<van-tag type="primary" size="medium">{{ item.meetingTypeName }}</van-tag>
|
||||
<van-tag type="danger" class="ms-10" size="medium">{{ item.meetingByName }}</van-tag>
|
||||
</div>
|
||||
</van-cell>
|
||||
</div>
|
||||
<van-empty v-else description="暂无会议" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { sysDicListByType, workMeetingList, workMeetingInfo, workStaffLookup } from '@/utils/api'
|
||||
import { convertToUrl } from '@/utils'
|
||||
import { scrollList } from '@/mixins/scroll-list'
|
||||
import { isAuth } from '@/utils/index'
|
||||
|
||||
let search = ref('')
|
||||
let searchContent = ref('搜索')
|
||||
let itemRef = ref()
|
||||
const clearSearch = () => {
|
||||
search.value = ''
|
||||
searchContent.value = '搜索'
|
||||
}
|
||||
const onConfirm = () => {
|
||||
searchContent.value = search.value || '搜索'
|
||||
itemRef.value.toggle()
|
||||
getMeetingList()
|
||||
}
|
||||
|
||||
let meetingType = ref('1,2,3,16,17,4,5,15,6,10,11,12,13,14,7')
|
||||
let meetingTypeList: any = ref([])
|
||||
const getMeetingTypeList = async () => {
|
||||
const { data } = await sysDicListByType({ dicType: 'meeting_type' })
|
||||
meetingTypeList.value = data.map((ele: any) => {
|
||||
ele.text = ele.dicValue
|
||||
ele.value = ele.dicKey
|
||||
return ele
|
||||
})
|
||||
meetingTypeList.value.unshift({ text: '全部类型', value: '1,2,3,16,17,4,5,15,6,10,11,12,13,14,7' })
|
||||
}
|
||||
getMeetingTypeList()
|
||||
|
||||
let createUser = ref('')
|
||||
let createUserList: any = ref([])
|
||||
const getCreateUserList = async () => {
|
||||
const { data } = await workStaffLookup()
|
||||
createUserList.value = data.map((ele: any) => {
|
||||
ele.text = ele.staffName
|
||||
ele.value = ele.staffId
|
||||
return ele
|
||||
})
|
||||
createUserList.value.unshift({ text: '全部创建人', value: '' })
|
||||
}
|
||||
getCreateUserList()
|
||||
|
||||
let defaultDate = ref()
|
||||
import moment from 'moment'
|
||||
const panelChange = (e: any) => {
|
||||
dateBegin.value = moment(e.date).startOf('month').format('YYYY-MM-DD')
|
||||
dateEnd.value = moment(e.date).endOf('month').format('YYYY-MM-DD')
|
||||
getMeetingList()
|
||||
}
|
||||
|
||||
const onDateConfirm = (e: any) => {
|
||||
defaultDate.value = e
|
||||
getList()
|
||||
}
|
||||
|
||||
let list: any = ref([])
|
||||
let selectDay = ref('')
|
||||
const getList = () => {
|
||||
list.value = []
|
||||
selectDay.value = moment(defaultDate.value).format('YYYY-MM-DD')
|
||||
meetingList.value.forEach((ele: any) => {
|
||||
if (ele.meetingDate === selectDay.value) {
|
||||
list.value = ele.list
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const formatter = computed(() => {
|
||||
if (meetingList.value.length === 0) {
|
||||
return (day: any) => day
|
||||
}
|
||||
return (day: any) => {
|
||||
let time = moment(day.date).format('YYYY-MM-DD')
|
||||
meetingList.value.forEach((ele: any) => {
|
||||
if (ele.meetingDate === time) {
|
||||
day.topInfo = ele.num
|
||||
}
|
||||
})
|
||||
return day
|
||||
}
|
||||
})
|
||||
|
||||
const dateBegin = ref()
|
||||
const dateEnd = ref()
|
||||
const meetingList: any = ref([])
|
||||
const getMeetingList = async () => {
|
||||
let url = getMeetingParams()
|
||||
const { data } = await workMeetingList(url)
|
||||
let map = new Map()
|
||||
data.forEach((item: any, index: any, arr: any) => {
|
||||
if (!map.has(item.meetingDate)) {
|
||||
map.set(
|
||||
item.meetingDate,
|
||||
arr.filter((a: any) => a.meetingDate == item.meetingDate)
|
||||
)
|
||||
}
|
||||
})
|
||||
let res = Array.from(map).map((item) => [...item[1]])
|
||||
meetingList.value = []
|
||||
res.forEach((ele: any) => {
|
||||
meetingList.value.push({ meetingDate: ele[0].meetingDate, num: ele.length, list: ele })
|
||||
})
|
||||
getList()
|
||||
}
|
||||
|
||||
const getMeetingParams = () => {
|
||||
const res = {
|
||||
dateBegin: dateBegin.value,
|
||||
dateEnd: dateEnd.value,
|
||||
createUser: createUser.value,
|
||||
cusName: search.value,
|
||||
meetingType: meetingType.value
|
||||
}
|
||||
const url = convertToUrl(res)
|
||||
return url
|
||||
}
|
||||
|
||||
let pageContent = ref(null)
|
||||
const scrollPosition = ref(0)
|
||||
const refresh = async (type: any, id: any) => {
|
||||
if (type === 'delete' || type === 'refresh') {
|
||||
let dayIndex = meetingList.value.findIndex((ele: any) => {
|
||||
return ele.meetingDate === selectDay.value
|
||||
})
|
||||
if (dayIndex > -1) {
|
||||
let index = meetingList.value[dayIndex].list.findIndex((ele: any) => {
|
||||
return Number(ele.meetingId) === Number(id)
|
||||
})
|
||||
if (type === 'delete') {
|
||||
if (index > -1) {
|
||||
meetingList.value[dayIndex].list.splice(index, 1)
|
||||
meetingList.value[dayIndex].num -= 1
|
||||
}
|
||||
} else {
|
||||
if (index > -1) {
|
||||
const { data } = await workMeetingInfo(id)
|
||||
meetingList.value[dayIndex].list[index] = data
|
||||
}
|
||||
getList()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
defaultDate.value = new Date()
|
||||
dateBegin.value = moment().startOf('month').format('YYYY-MM-DD')
|
||||
dateEnd.value = moment().endOf('month').format('YYYY-MM-DD')
|
||||
getMeetingList()
|
||||
}
|
||||
}
|
||||
const { setScrollTop, setScrollPositionAndRefreshFlag } = scrollList(pageContent, scrollPosition, refresh)
|
||||
setScrollTop()
|
||||
import { onBeforeRouteLeave } from 'vue-router'
|
||||
onBeforeRouteLeave((to, from) => {
|
||||
setScrollPositionAndRefreshFlag(to, 'meeting-detail')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
:deep(.van-calendar__header-title) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(.van-calendar__day) {
|
||||
border: 1px solid #f2f2f2;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:deep(.van-calendar__top-info) {
|
||||
width: 14px;
|
||||
left: calc(100vw / 7 - 18px);
|
||||
border-radius: 100%;
|
||||
background-color: #f56c6c;
|
||||
color: #fff;
|
||||
|
||||
@media screen and (min-width: 678px) {
|
||||
left: 74px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.van-calendar__selected-day) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.meetingList {
|
||||
overflow-y: auto;
|
||||
height: calc(100vh - 160px);
|
||||
}
|
||||
</style>
|
||||
487
src/views/organization/detail.vue
Normal file
487
src/views/organization/detail.vue
Normal file
@@ -0,0 +1,487 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<van-tabs v-model:active="active" sticky @change="changeTab">
|
||||
<van-tab v-for="item in tabs" :key="item.title" :title="item.title">
|
||||
<div class="p-10" v-if="active === 0">
|
||||
<div class="base-info">
|
||||
<div class="d-flex base-item">
|
||||
<span class="base-title">机构名称</span>
|
||||
<span class="base-desc">{{ cusInfo.cusName }}</span>
|
||||
</div>
|
||||
<div class="d-flex base-item">
|
||||
<span class="base-title">机构类型</span>
|
||||
<span class="base-desc">{{ cusInfo.companyTypeName }}</span>
|
||||
</div>
|
||||
<div class="d-flex base-item">
|
||||
<span class="base-title">核心联系人</span>
|
||||
<span class="base-desc">{{ cusInfo.cusUserName }}</span>
|
||||
</div>
|
||||
<div class="d-flex base-item">
|
||||
<span class="base-title">跟进人</span>
|
||||
<span class="base-desc">{{ cusInfo.saleUserName }}</span>
|
||||
</div>
|
||||
|
||||
<div class="d-flex base-item">
|
||||
<span class="base-title">城市</span>
|
||||
<span class="base-desc">{{ cusInfo.cityName }}</span>
|
||||
</div>
|
||||
<div class="d-flex base-item">
|
||||
<span class="base-title">城市</span>
|
||||
<span class="base-desc">{{ cusInfo.cityName }}</span>
|
||||
</div>
|
||||
<div class="d-flex base-item">
|
||||
<span class="base-title">是否开户</span>
|
||||
<span class="base-desc">{{ cusInfo.accountStatusFlag ? '是' : '否' }}</span>
|
||||
</div>
|
||||
<div class="d-flex base-item">
|
||||
<span class="base-title">开户时间</span>
|
||||
<span class="base-desc">{{ cusInfo.accountStatusFlag && cusInfo.accountTime ? cusInfo.accountTime : ''
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="d-flex base-item">
|
||||
<span class="base-title">开户类型</span>
|
||||
<span class="base-desc">{{ cusInfo.accountStatusFlag && cusInfo.accountTypeName ? cusInfo.accountTypeName
|
||||
: '' }}</span>
|
||||
</div>
|
||||
<div class="d-flex base-item">
|
||||
<span class="base-title">是否交易</span>
|
||||
<span class="base-desc">{{ cusInfo.accountStatusFlag && cusInfo.isMoneyFlag ? '是' : '否' }}</span>
|
||||
</div>
|
||||
<div class="d-flex base-item">
|
||||
<span class="base-title">交易总金额</span>
|
||||
<span class="base-desc">{{ cusInfo.isMoneyFlag && cusInfo.accountStatusFlag && cusInfo.tradeMoney ?
|
||||
cusInfo.tradeMoney : '' }}</span>
|
||||
</div>
|
||||
<div class="d-flex base-item">
|
||||
<span class="base-title">资金规模</span>
|
||||
<span class="base-desc">{{ cusInfo.capitalScaleValue ? cusInfo.capitalScaleValue :
|
||||
cusInfo.capitalScaleName ? cusInfo.capitalScaleName : '' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="title">参会趋势</div>
|
||||
<div class="chart d-flex align-items-center justify-content-center">
|
||||
<van-loading v-show="chartLoading" type="spinner" color="#1989fa" class="loading" />
|
||||
<div id="meeting_chart" class="chartDiv"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="active === 1">
|
||||
<van-list v-if="cusUserList.length" finished-text="没有更多了" :immediate-check="false">
|
||||
<van-cell v-for="item in cusUserList" :key="item.cusId">
|
||||
<div class="name1 text-start stockNames balck-text-color">{{ item.cusUserName }}<span
|
||||
class="mx-10">|</span>{{
|
||||
item.positionName }}</div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="font-12">跟进人:{{ item.saleNames }}
|
||||
<span v-if="item.entryTime">({{ item.entryTime }}入职)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex font-12">
|
||||
<div>更新时间:</div>
|
||||
<div class="activeColor">{{ item.updateTime }}</div>
|
||||
</div>
|
||||
<div class="d-flex font-12 justify-content-between flex-wrap">
|
||||
<div class=" d-flex w-half">
|
||||
<div>参会次数:</div>
|
||||
<div class="activeColor">{{ item.meetingNum || 0 }}</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end w-half">
|
||||
<div>沟通次数:</div>
|
||||
<div class="activeColor">{{ item.linkNum || 0 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-cell>
|
||||
</van-list>
|
||||
<van-empty v-else />
|
||||
</div>
|
||||
<div v-if="active === 2">
|
||||
<van-cell v-if="workList?.length" v-for="(item, index) in workList" :key="index"
|
||||
@click="showDetail(item.linkNote)">
|
||||
<div class="f-b activeColor text-start mb-5 mt-5">{{ item.saleNames }} - {{ item.linkTypeMenuName }}
|
||||
</div>
|
||||
<div class="balck-text-color text-ellipsis">{{ item.linkNote }}</div>
|
||||
<div class=" d-flex font-12 flex-wrap">
|
||||
<div>相关研究人:{{ item.staffName }};</div>
|
||||
<div>联系人:{{ item.cusUserName }}</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<div class="activeColor font-12 text-start flex-1">{{ item.sysDate }}</div>
|
||||
<van-tag type="danger" class="ms-10" size="medium">{{ item.linkTypeName }}</van-tag>
|
||||
</div>
|
||||
</van-cell>
|
||||
<van-empty v-else />
|
||||
</div>
|
||||
<van-overlay :show="showOverLay" @click="showOverLay = false">
|
||||
<div class="wrapper">
|
||||
<div class="block" @click.stop>{{ note }}</div>
|
||||
</div>
|
||||
</van-overlay>
|
||||
<div v-if="active === 3">
|
||||
<van-cell v-if="meetingList?.length" v-for="item in meetingList" :key="item.meetingId"
|
||||
@click="$router.push({ name: 'meeting-detail', query: { meetingId: item.meetingId } })">
|
||||
<div class="f-b text-start balck-text-color mb-5 mt-5">{{ item.meetingTitle }}</div>
|
||||
<div class="d-flex">
|
||||
<div class="font-12 text-start activeColor flex-1">{{ item.meetingTime }}</div>
|
||||
<van-tag type="primary" size="medium">{{ item.meetingTypeName }}</van-tag>
|
||||
<van-tag type="danger" class="ms-10" size="medium">{{ item.meetingByName }}</van-tag>
|
||||
</div>
|
||||
</van-cell>
|
||||
<van-empty v-else description="暂无会议" />
|
||||
</div>
|
||||
<div v-if="active === 4">
|
||||
<communicate-item v-if="communicateList" class="pb-16" :list="communicateList" />
|
||||
<van-empty v-else />
|
||||
</div>
|
||||
<div v-if="active === 5">
|
||||
<van-cell v-if="serviceList?.length" v-for="item in serviceList" :key="item.id"
|
||||
@click="showDetail(item.content)">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="f-b activeColor text-ellipsis">{{ item.content }}</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="text-start font-12">开始时间:<span class="activeColor">{{ item.serviceBeginDate }}</span></div>
|
||||
<div class="text-start font-12">状态:<span class="activeColor">{{ item.serviceStatus }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</van-cell>
|
||||
<van-empty v-else description="暂无数据" />
|
||||
</div>
|
||||
<div v-if="active === 6">
|
||||
<van-cell v-if="yearList?.length" v-for="(item, index) in yearList" :key="index">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="f-b activeColor">{{ item.sysYear }}年</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="text-start font-12">目标销售额:<span class="activeColor">{{ item.targetMoney || 0 }}</span></div>
|
||||
<div class="text-start font-12">年度交易额:<span class="activeColor">{{ item.tradeMoneyYear || 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-start font-12 ">
|
||||
完成率:<span class="activeColor">{{ item.finishPer || 0 }}%</span>
|
||||
</div>
|
||||
</van-cell>
|
||||
<van-empty v-else description="暂无数据" />
|
||||
</div>
|
||||
<div v-if="active === 7">
|
||||
<van-cell v-if="accountList?.length" v-for="(item, index) in accountList" :key="index">
|
||||
<div class="f-b text-start balck-text-color mb-5">姓名:{{ item.accountName }}</div>
|
||||
<div class="text-start font-12">账号:{{ item.accountNo }}</div>
|
||||
</van-cell>
|
||||
<van-empty v-else description="暂无数据" />
|
||||
</div>
|
||||
</van-tab>
|
||||
</van-tabs>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { nextTick, ref } from 'vue'
|
||||
const showOverLay = ref(false)
|
||||
const note = ref()
|
||||
const showDetail = (value: string) => {
|
||||
note.value = value
|
||||
showOverLay.value = true
|
||||
}
|
||||
const active = ref(0)
|
||||
const tabs = [{
|
||||
name: 'base',
|
||||
title: '基本信息'
|
||||
}, {
|
||||
name: 'cusUser',
|
||||
title: '员工列表'
|
||||
}, {
|
||||
name: 'work',
|
||||
title: '工作日报'
|
||||
}, {
|
||||
name: 'meeting',
|
||||
title: '参会记录'
|
||||
}, {
|
||||
name: 'communicate',
|
||||
title: '沟通记录'
|
||||
}, {
|
||||
name: 'service',
|
||||
title: '服务目标'
|
||||
}, {
|
||||
name: 'year',
|
||||
title: '年度数据'
|
||||
}, {
|
||||
name: 'account',
|
||||
title: '账号列表'
|
||||
}, {
|
||||
name: '关注股票',
|
||||
title: '关注股票'
|
||||
}]
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
import emitter from '@/utils/mitt'
|
||||
const cusId = ref()
|
||||
const init = () => {
|
||||
if (route.query && route.query.cusId) {
|
||||
cusId.value = route.query.cusId
|
||||
getData()
|
||||
} else {
|
||||
router.push({ name: '404' })
|
||||
}
|
||||
}
|
||||
import {
|
||||
cusInfoInfo, cusUserListByCusId, cusLinkList, cusServiceGetByCusId,
|
||||
cusYearList, cusAccountList, cusMeetingList, cusInfoCountByMonth
|
||||
} from '@/utils/api'
|
||||
import { convertToUrl } from '@/utils'
|
||||
const getData = async () => {
|
||||
emitter.emit('showLoading', '')
|
||||
try {
|
||||
getCusInfo()
|
||||
getChartList()
|
||||
getCusUserList()
|
||||
getMeetingList()
|
||||
getCusLinkList()
|
||||
getWorkList()
|
||||
getServiceList()
|
||||
getYearList()
|
||||
getAccountList()
|
||||
} finally {
|
||||
emitter.emit('hiddenLoading', '')
|
||||
}
|
||||
}
|
||||
import * as echarts from 'echarts'
|
||||
const chartLoading = ref(false)
|
||||
const getChartList = async () => {
|
||||
chartLoading.value = false
|
||||
const { data } = await cusInfoCountByMonth({ cusId: cusId.value })
|
||||
const sysDate: any = []
|
||||
const seriesList: any = []
|
||||
const legendList = ['FSH电话会议+调研+其他', '周末会+精英会', '按需会议', '机构路演', '机构沟通', '交易量']
|
||||
const colorList = ['#f26522', '#45b97c', '#009ad6', '#8552a1', '#6c4c49', '#ed1941']
|
||||
legendList.forEach((ele, index) => {
|
||||
if (index < 5) {
|
||||
seriesList.push({
|
||||
type: 'line',
|
||||
name: ele,
|
||||
data: [],
|
||||
yAxisIndex: 0,
|
||||
xAxisIndex: 0,
|
||||
connectNulls: false,
|
||||
symbol: 'none',
|
||||
symbolSize: 0
|
||||
})
|
||||
} else {
|
||||
seriesList.push({
|
||||
type: 'bar',
|
||||
name: ele,
|
||||
data: [],
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
yAxisIndex: 1,
|
||||
xAxisIndex: 1,
|
||||
showEmptyData: false,
|
||||
barWidth: 10
|
||||
})
|
||||
}
|
||||
})
|
||||
data.forEach((ele: any) => {
|
||||
sysDate.push(ele.sysMonth)
|
||||
seriesList[0].data.push(ele.meetingNum34 + ele.meetingNum7)
|
||||
seriesList[1].data.push(ele.meetingNum5 + ele.meetingNum6)
|
||||
seriesList[2].data.push(ele.linkNum31)
|
||||
seriesList[3].data.push(ele.linkNum32)
|
||||
seriesList[4].data.push(ele.linkNum34)
|
||||
seriesList[5].data.push(ele.tradeMoney)
|
||||
})
|
||||
const xAxis = {
|
||||
type: 'category',
|
||||
name: '',
|
||||
data: [],
|
||||
show: true,
|
||||
axisLabel: {
|
||||
show: true,
|
||||
showMaxLabel: true,
|
||||
formatter: '{value}'
|
||||
},
|
||||
gridIndex: 0
|
||||
}
|
||||
const yAxis = {
|
||||
type: 'value',
|
||||
name: '',
|
||||
nameLocation: 'end',
|
||||
scale: true,
|
||||
show: true,
|
||||
axisLabel: {
|
||||
show: true,
|
||||
formatter: '{value}'
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: data.color || '#000'
|
||||
}
|
||||
},
|
||||
position: 'left',
|
||||
}
|
||||
const option = {
|
||||
yAxis: [{ ...yAxis, name: '次数', minInterval: 1 }, { ...yAxis, name: '交易数', minInterval: 1, nameLocation: 'start', gridIndex: 1 }],
|
||||
xAxis: [{ ...xAxis, data: sysDate, name: '时间' }, { ...xAxis, data: sysDate, showFlag: false, gridIndex: 1 }],
|
||||
series: seriesList,
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: 'axis',
|
||||
formatter: function (data: any) {
|
||||
let res = `${data[0].name}</br>`
|
||||
data.forEach((ele: any) => {
|
||||
res += `<span style="color: ${ele.color}">${ele.seriesName}:${ele.value}${ele.componentSubType === 'bar' ? '百万' : ''}</span></br>`
|
||||
})
|
||||
return res
|
||||
}
|
||||
},
|
||||
legend: { data: legendList, top: '10px' },
|
||||
color: colorList,
|
||||
grid: [{
|
||||
top: '84px',
|
||||
bottom: '35%',
|
||||
left: '40px',
|
||||
right: '40px'
|
||||
}, {
|
||||
top: '72%',
|
||||
bottom: '40px',
|
||||
left: '40px',
|
||||
right: '40px'
|
||||
}]
|
||||
}
|
||||
nextTick(() => {
|
||||
const chart = echarts.init(document.getElementById('meeting_chart'))
|
||||
chart.setOption(option)
|
||||
chartLoading.value = false
|
||||
})
|
||||
}
|
||||
const cusInfo = ref<any>({})
|
||||
const getCusInfo = async () => {
|
||||
const { data } = await cusInfoInfo(cusId.value)
|
||||
cusInfo.value = data
|
||||
emitter.emit('setTitle', { title: cusInfo.value.cusName, type: 'meeting' })
|
||||
}
|
||||
const cusUserList = ref<any[]>([])
|
||||
const getCusUserList = async () => {
|
||||
const { data } = await cusUserListByCusId({ cusId: cusId.value })
|
||||
cusUserList.value = data
|
||||
}
|
||||
const workList = ref<any[]>()
|
||||
const getWorkList = async () => {
|
||||
const url = convertToUrl({
|
||||
limit: 50,
|
||||
cusId: cusId.value,
|
||||
requestFrom: 'DETAIL_RB'
|
||||
})
|
||||
const data = await cusLinkList(url)
|
||||
workList.value = data
|
||||
}
|
||||
const communicateList = ref<any[]>()
|
||||
const getCusLinkList = async () => {
|
||||
const url = convertToUrl({
|
||||
limit: 100,
|
||||
cusId: cusId.value,
|
||||
requestFrom: 'DETAIL_GTJL',
|
||||
dataStatus: 1
|
||||
})
|
||||
const data = await cusLinkList(url)
|
||||
communicateList.value = data
|
||||
}
|
||||
const meetingList = ref<any[]>()
|
||||
const getMeetingList = async () => {
|
||||
const { data } = await cusMeetingList({ cusId: cusId.value, meetingType: '' })
|
||||
meetingList.value = data
|
||||
}
|
||||
const serviceList = ref<any[]>()
|
||||
const getServiceList = async () => {
|
||||
const { data } = await cusServiceGetByCusId({ cusId: cusId.value })
|
||||
data.forEach((ele: any) => {
|
||||
ele.selectId = ele.id
|
||||
ele.serviceStatus = ele.serviceStatus === 0 ? '历史' : '当前'
|
||||
})
|
||||
serviceList.value = data
|
||||
}
|
||||
const yearList = ref<any[]>()
|
||||
const getYearList = async () => {
|
||||
const { data } = await cusYearList({ cusId: cusId.value, curPage: 1, limit: 999 })
|
||||
yearList.value = data.list
|
||||
}
|
||||
const accountList = ref<any[]>()
|
||||
const getAccountList = async () => {
|
||||
const { data } = await cusAccountList({ cusId: cusId.value, curPage: 1, limit: 999 })
|
||||
accountList.value = data.list
|
||||
}
|
||||
init()
|
||||
const changeTab = (num: number) => {
|
||||
if (num === 0) {
|
||||
getChartList()
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$border-color: #e0e0e0;
|
||||
|
||||
.base-info {
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
border: 1px solid $border-color;
|
||||
|
||||
.base-item {
|
||||
font-size: 13px;
|
||||
border-bottom: 1px solid $border-color;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.base-title {
|
||||
width: 30%;
|
||||
border-right: 1px solid $border-color;
|
||||
padding: 6px 10px;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.base-desc {
|
||||
background-color: #f2f4f7;
|
||||
padding: 6px 10px;
|
||||
width: 70%;
|
||||
color: #1989fa;
|
||||
}
|
||||
}
|
||||
|
||||
.chartDiv {
|
||||
height: 50vh;
|
||||
width: 100vh;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-top: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:deep(.van-tab--active) {
|
||||
.van-tab__text {
|
||||
color: #1989fa;
|
||||
}
|
||||
}
|
||||
|
||||
.w-half {
|
||||
width: 50%;
|
||||
}
|
||||
.wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
.block {
|
||||
width: 80%;
|
||||
height: 50%;
|
||||
overflow: auto;
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
112
src/views/organization/list.vue
Normal file
112
src/views/organization/list.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<div class="page-content top-select-list" ref="pageContent">
|
||||
<div class="top-select border-bottom-1">
|
||||
<van-dropdown-menu>
|
||||
<van-dropdown-item :title="searchContent" ref="itemRef">
|
||||
<van-field v-model="search" placeholder="搜索" :right-icon="search ? 'clear' : ''"
|
||||
@click-right-icon.stop="clearSearch" />
|
||||
<div style="padding: 5px 16px;">
|
||||
<van-button type="primary" size="small" block round @click="onConfirm">
|
||||
确认
|
||||
</van-button>
|
||||
</div>
|
||||
</van-dropdown-item>
|
||||
<van-dropdown-item v-model="companyType" :options="companyTypeList" @change="onRefresh" />
|
||||
<van-dropdown-item v-model="cusLevel" :options="cusLevelList" @change="onRefresh" />
|
||||
</van-dropdown-menu>
|
||||
</div>
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh" v-if="list.length > 0">
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad"
|
||||
:immediate-check="false">
|
||||
<van-cell v-for="item in list" :key="item.cusId"
|
||||
@click="$router.push({ name: 'organization-detail', query: { cusId: item.cusId } })">
|
||||
<div class="name1 text-start stockNames balck-text-color">{{ item.cusName }}</div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="font-12">负责人:{{ item.saleUserName }}</div>
|
||||
<div class="name1 text-start flex1">
|
||||
<van-tag :style="{ backgroundColor: item.cusLevelColor}" size="medium">{{ item.companyTypeName }}</van-tag>
|
||||
<van-tag class="tag" size="medium">{{ item.cusLevelName }}</van-tag>
|
||||
</div>
|
||||
</div>
|
||||
</van-cell>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
<van-empty v-else />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { cusInfoList, sysDicListByType } from '@/utils/api'
|
||||
|
||||
let searchContent = ref('搜索')
|
||||
let itemRef = ref()
|
||||
const clearSearch = () => {
|
||||
search.value = ''
|
||||
searchContent.value = '搜索'
|
||||
}
|
||||
const onConfirm = () => {
|
||||
searchContent.value = search.value || '搜索'
|
||||
itemRef.value.toggle()
|
||||
onRefresh()
|
||||
}
|
||||
const getData = async () => {
|
||||
const { data } = await cusInfoList({
|
||||
companyType: companyType.value || null,
|
||||
cusLevel: cusLevel.value || null,
|
||||
cusName: search.value,
|
||||
curPage: curPage.value,
|
||||
limit: 20
|
||||
})
|
||||
console.log(data);
|
||||
|
||||
return data
|
||||
}
|
||||
const companyType = ref('')
|
||||
const cusLevel = ref('')
|
||||
const search = ref('')
|
||||
const companyTypeList = ref<any[]>([])
|
||||
const cusLevelList = ref<any[]>([])
|
||||
const getDicList = async () => {
|
||||
const { data } = await sysDicListByType({ dicType: 'sale_company_type' })
|
||||
companyTypeList.value = data.map((ele: any) => {
|
||||
ele.text = ele.dicValue
|
||||
ele.value = ele.dicKey
|
||||
return ele
|
||||
})
|
||||
companyTypeList.value.unshift({ text: '全部类型', value: '' })
|
||||
const { data: data1 } = await sysDicListByType({ dicType: 'sale_customer_type' })
|
||||
cusLevelList.value = data1.map((ele: any) => {
|
||||
ele.text = ele.dicValue
|
||||
ele.value = ele.dicKey
|
||||
return ele
|
||||
})
|
||||
cusLevelList.value.unshift({ text: '全部评级', value: '' })
|
||||
}
|
||||
getDicList()
|
||||
|
||||
import { listLoadAndRefresh } from '@/mixins/list-load-and-refresh'
|
||||
const { refreshing, finished, loading, curPage, list, onLoad, onRefresh } = listLoadAndRefresh(getData, 'organization')
|
||||
import { scrollList } from '@/mixins/scroll-list'
|
||||
const pageContent = ref()
|
||||
const scrollPosition = ref(0)
|
||||
const { setScrollTop, setScrollPositionAndRefreshFlag } = scrollList(pageContent, scrollPosition, onRefresh)
|
||||
setScrollTop()
|
||||
import { onBeforeRouteLeave } from 'vue-router'
|
||||
onBeforeRouteLeave((to, from) => {
|
||||
setScrollPositionAndRefreshFlag(to, 'organization-detail')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.top-select-list {
|
||||
padding-top: 60px;
|
||||
}
|
||||
.item {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
.tag {
|
||||
margin-left: 10px;
|
||||
background-color: #ff758f;
|
||||
}
|
||||
</style>
|
||||
267
src/views/report/list.vue
Normal file
267
src/views/report/list.vue
Normal file
@@ -0,0 +1,267 @@
|
||||
<template>
|
||||
<div class="page-content top-select-list">
|
||||
<div class="top-select border-bottom-1">
|
||||
<van-grid :border="false" :gutter="10" :column-num="3">
|
||||
<van-grid-item>
|
||||
<top-select1 :type="'report_type'" :initialName="'类型'" @refresh="refreshByReportType"
|
||||
:selectName="reportTypeName" :selectValue="reportType" />
|
||||
</van-grid-item>
|
||||
<van-grid-item>
|
||||
<top-date1 :initialName="'日期'" @refresh="refreshByDate" :selectDate="date" />
|
||||
</van-grid-item>
|
||||
<van-grid-item>
|
||||
<top-select2 :type="'stock'" :initialName="'股票'" @refresh="refreshByStock" :selectName="stockName"
|
||||
:selectValue="stockCode" />
|
||||
</van-grid-item>
|
||||
</van-grid>
|
||||
</div>
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh" v-if="list.length > 0">
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad"
|
||||
:immediate-check="false">
|
||||
<van-cell class="cell" v-for="(item, index) in list" :key="index" :id="item.reportId"
|
||||
@click="showDetail(item, index)">
|
||||
<div class="img-box">
|
||||
<van-image :src="item.url" class="img"></van-image>
|
||||
<div class="title">{{ item.reportName }}</div>
|
||||
</div>
|
||||
<div class="road-info flex-1 text-start d-flex justify-content-between">
|
||||
<div class="title">{{ item.reportName }}</div>
|
||||
<div class="flex-1">
|
||||
<div>
|
||||
<span>{{ item.stockName }}</span>
|
||||
<span class="line">|</span>
|
||||
<van-tag size="medium" :class="item.reportColor" class="white-text-color">
|
||||
{{ item.reportTypeName }}
|
||||
</van-tag>
|
||||
</div>
|
||||
<div class="report-desc">
|
||||
<div class="detail text-ellipsis" v-for="(row, idx) in item.reportDescList" :key="idx">{{ row }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="report-time">{{ item.reportDate }}</div>
|
||||
</div>
|
||||
</van-cell>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
<van-empty v-else />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, nextTick, onUnmounted } from 'vue'
|
||||
import TopSelect1 from '@/components/top-select1.vue'
|
||||
import TopSelect2 from '@/components/top-select2.vue'
|
||||
import TopDate1 from '@/components/top-date1.vue'
|
||||
import emitter from '@/utils/mitt'
|
||||
|
||||
import { stockReportList } from '@/utils/api'
|
||||
import { listLoadAndRefresh } from '@/mixins/list-load-and-refresh'
|
||||
|
||||
const getData = async () => {
|
||||
const { data } = await stockReportList(`${reportType.value ? '?reportType=' + reportType.value : ''}`, {
|
||||
curPage: curPage.value,
|
||||
limit: 20,
|
||||
stockCode: stockCode.value || null,
|
||||
reportDate: date.value !== '日期' ? date.value : null
|
||||
})
|
||||
data.list.map((ele: any) => {
|
||||
ele.url = `/img/${ele.reportType}.jpeg`
|
||||
ele.reportDescList = ele.reportTitle?.split(';') || []
|
||||
ele.reportName = ele.reportName
|
||||
? ele.reportName.substring(0, ele.reportName.length - 4)
|
||||
: ''
|
||||
switch (ele.reportType) {
|
||||
case 'SFBG':
|
||||
ele.reportColor = 'primary-background-color'
|
||||
break
|
||||
case 'GXBG':
|
||||
ele.reportColor = 'danger-background-color'
|
||||
break
|
||||
case 'XLL':
|
||||
ele.reportColor = 'success-background-color'
|
||||
break
|
||||
case 'CLBG':
|
||||
ele.reportColor = 'warning-background-color'
|
||||
break
|
||||
}
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
import reportInfoStore from '@/stores/reportInfo'
|
||||
const reportInfo = reportInfoStore()
|
||||
|
||||
const scrollPage = () => {
|
||||
nextTick(() => {
|
||||
if (reportInfo.reportId && reportInfo.index) {
|
||||
stockCode.value = reportInfo.stockCode
|
||||
stockName.value = reportInfo.stockName
|
||||
date.value = reportInfo.date
|
||||
reportTypeName.value = reportInfo.reportTypeName
|
||||
reportType.value = reportInfo.reportType
|
||||
let pageIndex1 = reportInfo.index / 20
|
||||
let pageIndex2 = reportInfo.index % 20
|
||||
if (pageIndex2 > 1) {
|
||||
pageIndex1++
|
||||
}
|
||||
if (pageIndex1 > curPage.value) {
|
||||
onLoad()
|
||||
} else {
|
||||
const el = document.getElementById(reportInfo.reportId)
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
reportInfo.reportId = ''
|
||||
reportInfo.index = 0
|
||||
emitter.emit('hiddenLoading', '')
|
||||
}
|
||||
} else {
|
||||
emitter.emit('hiddenLoading', '')
|
||||
}
|
||||
})
|
||||
}
|
||||
const { refreshing, finished, loading, list, onLoad, onRefresh, curPage } = listLoadAndRefresh(getData, 'report', scrollPage)
|
||||
|
||||
import { showFile } from '@/mixins/show-file'
|
||||
import userInfoStore from '@/stores/userInfo'
|
||||
const { loadingFile } = showFile()
|
||||
const showDetail = (item: any, index: number) => {
|
||||
reportInfo.index = index
|
||||
reportInfo.reportId = item.reportId
|
||||
reportInfo.stockCode = stockCode.value
|
||||
reportInfo.stockName = stockName.value
|
||||
reportInfo.date = date.value
|
||||
reportInfo.reportTypeName = reportTypeName.value
|
||||
reportInfo.reportType = reportType.value
|
||||
const userInfo = userInfoStore()
|
||||
loadingFile(`${import.meta.env.VITE_BASE_URL}/stock/report/${item.reportId}?token=${userInfo.token}`)
|
||||
}
|
||||
|
||||
let reportType = ref('')
|
||||
let reportTypeName = ref('类型')
|
||||
let date = ref('日期')
|
||||
let stockCode = ref('')
|
||||
let stockName = ref('股票')
|
||||
|
||||
const refreshByReportType = (e: any) => {
|
||||
reportType.value = e.value
|
||||
reportTypeName.value = e.name
|
||||
onRefresh()
|
||||
}
|
||||
|
||||
const refreshByDate = (e: any) => {
|
||||
date.value = e
|
||||
onRefresh()
|
||||
}
|
||||
|
||||
const refreshByStock = (e: any) => {
|
||||
stockCode.value = e.value
|
||||
stockName.value = e.name
|
||||
onRefresh()
|
||||
}
|
||||
|
||||
const onRefresh1 = () => {
|
||||
stockCode.value = reportInfo.stockCode
|
||||
stockName.value = reportInfo.stockName
|
||||
date.value = reportInfo.date
|
||||
reportTypeName.value = reportInfo.reportTypeName
|
||||
reportType.value = reportInfo.reportType
|
||||
onRefresh()
|
||||
|
||||
}
|
||||
onRefresh1()
|
||||
|
||||
onUnmounted(() => {
|
||||
reportInfo.stockCode = ''
|
||||
reportInfo.stockName = '股票'
|
||||
reportInfo.date = '日期'
|
||||
reportInfo.reportTypeName = '类型'
|
||||
reportInfo.reportType = ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.van-cell {
|
||||
width: 100%;
|
||||
max-width: 678px;
|
||||
margin: 0;
|
||||
box-shadow: 0 0;
|
||||
}
|
||||
|
||||
.cell {
|
||||
:deep(.van-cell__value) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.img-box {
|
||||
width: 30%;
|
||||
max-width: 100PX;
|
||||
max-height: 130PX;
|
||||
position: relative;
|
||||
|
||||
.img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.title {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
padding: 10px;
|
||||
zoom: 0.8;
|
||||
height: 40px;
|
||||
overflow: hidden;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.road-info {
|
||||
margin-left: 10px;
|
||||
flex-direction: column;
|
||||
width: 70%;
|
||||
|
||||
.report-time {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #000;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.report-desc {
|
||||
color: #999;
|
||||
height: 48px;
|
||||
margin-top: 6px;
|
||||
overflow: hidden;
|
||||
font-size: 12px;
|
||||
|
||||
.detail {
|
||||
line-height: 16px;
|
||||
height: 16px;
|
||||
padding-left: 8px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
background-color: #999;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.line {
|
||||
margin: 0 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
189
src/views/stock/detail.vue
Normal file
189
src/views/stock/detail.vue
Normal file
@@ -0,0 +1,189 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<div class="stock-detail" ref="pageContent">
|
||||
<van-steps direction="vertical" :active="-1" v-if="eventList.length > 0" @click-step="showInfo">
|
||||
<van-step v-for="item in eventList" :key="item.id" :id="item.id">
|
||||
<template v-slot:inactive-icon>
|
||||
<div style="background-color: #000" class="slotIcon"></div>
|
||||
</template>
|
||||
<div class="stepItem">
|
||||
<van-row class="mb-16">
|
||||
<van-col span="12">
|
||||
<van-tag size="medium" :stlye="{ backgroundColor: item.eventTypeColor }">
|
||||
{{ item.eventTypeName }}
|
||||
</van-tag>
|
||||
</van-col>
|
||||
<van-col span="12">
|
||||
<div class="text-end tip">{{ item.sysDate }}</div>
|
||||
</van-col>
|
||||
</van-row>
|
||||
<div v-if="item.eventType === 'CHDP'">
|
||||
<div v-if="item.eventNote">
|
||||
<div v-if="!item.playFlag" class="audioDiv d-flex align-items-center">
|
||||
<van-icon name="play-circle" @click="playAudio(item)" />
|
||||
</div>
|
||||
<audio v-else :id="`auto${item.id}`" :src="item.eventNote" controls @play="playAudio(item)"></audio>
|
||||
</div>
|
||||
</div>
|
||||
<div class="eventNote" v-else>{{ item.eventNote }}</div>
|
||||
</div>
|
||||
</van-step>
|
||||
</van-steps>
|
||||
<van-empty v-else />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, nextTick } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import emitter from '@/utils/mitt'
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
import stockInfoStore from '@/stores/stockInfo'
|
||||
const stockInfo = stockInfoStore()
|
||||
let stockCode: any = ref('')
|
||||
const init = () => {
|
||||
if (route.query && route.query.stockCode) {
|
||||
stockCode.value = route.query.stockCode
|
||||
emitter.emit('showLoading', '')
|
||||
getStockEventList()
|
||||
} else {
|
||||
router.push({ name: '404' })
|
||||
}
|
||||
}
|
||||
let eventList: any = ref([])
|
||||
import { stockStockinfoEventList } from '@/utils/api'
|
||||
const getStockEventList = async () => {
|
||||
const { data }: any = await stockStockinfoEventList({
|
||||
stockCode: stockCode.value
|
||||
})
|
||||
data.map((ele: any) => {
|
||||
ele.playFlag = false
|
||||
})
|
||||
eventList.value = data
|
||||
nextTick(() => {
|
||||
if (stockInfo.id) {
|
||||
const el = document.getElementById(stockInfo.id)
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
stockInfo.id = ''
|
||||
emitter.emit('hiddenLoading', '')
|
||||
} else {
|
||||
emitter.emit('hiddenLoading', '')
|
||||
}
|
||||
})
|
||||
}
|
||||
init()
|
||||
|
||||
import { showFile } from '@/mixins/show-file'
|
||||
const { loadingFile } = showFile()
|
||||
const showInfo = (e: any) => {
|
||||
if (eventList.value[e].eventType === 'DP') {
|
||||
router.push({ name: 'comment-detail', query: { inboxId: eventList.value[e].inboxId } })
|
||||
} else if (eventList.value[e].eventType === 'UPDATE' || eventList.value[e].eventType === 'FIRST') {
|
||||
stockInfo.id = eventList.value[e].id
|
||||
loadingFile(`${import.meta.env.VITE_BASE_URL}/api/file/${eventList.value[e].reportFileId}`)
|
||||
}
|
||||
}
|
||||
|
||||
import { scrollList } from '@/mixins/scroll-list'
|
||||
import { onBeforeRouteLeave } from 'vue-router'
|
||||
|
||||
let pageContent = ref(null)
|
||||
const scrollPosition = ref(0)
|
||||
|
||||
const refresh = () => {
|
||||
init()
|
||||
}
|
||||
|
||||
const { setScrollTop, setScrollPositionAndRefreshFlag } = scrollList(pageContent, scrollPosition, refresh)
|
||||
|
||||
setScrollTop()
|
||||
onBeforeRouteLeave((to, from) => {
|
||||
setScrollPositionAndRefreshFlag(to, 'comment-detail')
|
||||
})
|
||||
|
||||
const playAudio = (item: any) => {
|
||||
eventList.value.map((ele: any) => {
|
||||
if (ele.eventType === 'CHDP' && ele.eventNote) {
|
||||
if (item.id === ele.id) {
|
||||
ele.playFlag = true
|
||||
nextTick(() => {
|
||||
const el = document.getElementById(`auto${item.id}`)
|
||||
if (el) {
|
||||
el.play()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if (ele.playFlag) {
|
||||
const el = document.getElementById(`auto${item.id}`)
|
||||
if (el) {
|
||||
el.pause()
|
||||
}
|
||||
ele.playFlag = false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.stock-detail {
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
:deep(.van-step__circle-container) {
|
||||
top: 29px
|
||||
}
|
||||
|
||||
.van-steps--vertical {
|
||||
padding-left: 48px;
|
||||
}
|
||||
|
||||
.van-steps__items {
|
||||
padding-right: 32px
|
||||
}
|
||||
|
||||
.slotIcon {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.stepItem {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.van-step--vertical:not(:last-child):after {
|
||||
border-color: #b2b2b2;
|
||||
margin-right: 46px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.eventNote {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.audioDiv {
|
||||
height: 28px;
|
||||
width: 300px;
|
||||
border-radius: 32px;
|
||||
background-color: #f1f3f4;
|
||||
padding-left: 10px;
|
||||
margin: 4px 0;
|
||||
|
||||
.van-icon {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
audio {
|
||||
height: 28px;
|
||||
margin: 4px 0;
|
||||
}
|
||||
</style>
|
||||
121
src/views/stock/list.vue
Normal file
121
src/views/stock/list.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<div class="tabbar-list" ref="pageContent">
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh" v-if="list.length > 0">
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad"
|
||||
:immediate-check="false">
|
||||
<van-cell v-for="item in list" :key="item.stockCode"
|
||||
@click="$router.push({ name: 'stock-detail', query: { stockCode: item.stockCode } })">
|
||||
<van-row class="mb-1">
|
||||
<van-col span="12">
|
||||
<div class="text-start">
|
||||
<van-tag :class="item.eventTypeColor" size="medium">{{ item.eventTypeName }}</van-tag>
|
||||
</div>
|
||||
</van-col>
|
||||
<van-col span="12">
|
||||
<div class="text-end">
|
||||
<div class="sub-title balck-text-color">
|
||||
{{ item.stockCode }}{{ item.stockName ? '【' + item.stockName + '】' : '' }}
|
||||
</div>
|
||||
</div>
|
||||
</van-col>
|
||||
</van-row>
|
||||
<div v-if="item.eventType === 'CHDP'">
|
||||
<div v-if="item.eventNote">
|
||||
<div class="audioDiv d-flex align-items-center">
|
||||
<van-icon name="play-circle" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="eventNote text-start" v-else>{{ item.eventNote }}</div>
|
||||
<div class="text-end tip">{{ item.sysDate }}</div>
|
||||
</van-cell>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
<van-empty v-else />
|
||||
<tabbar />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { listLoadAndRefresh } from '@/mixins/list-load-and-refresh'
|
||||
import { scrollList } from '@/mixins/scroll-list'
|
||||
import { onBeforeRouteLeave } from 'vue-router'
|
||||
import Tabbar from '@/components/tabbar.vue'
|
||||
|
||||
let pageContent = ref(null)
|
||||
const scrollPosition = ref(0)
|
||||
|
||||
const refresh = () => {
|
||||
onRefresh()
|
||||
}
|
||||
|
||||
import { stockStockinfoEventNews } from '@/utils/api'
|
||||
|
||||
const getData = async () => {
|
||||
const { data } = await stockStockinfoEventNews({
|
||||
curPage: curPage.value,
|
||||
limit: 20
|
||||
})
|
||||
data.list.map((ele: any) => {
|
||||
switch (ele.eventType) {
|
||||
case 'DP':
|
||||
ele.eventTypeColor = 'primary-background-color'
|
||||
break
|
||||
case 'SFBG':
|
||||
ele.eventTypeColor = 'danger-background-color'
|
||||
break
|
||||
case 'GXBG':
|
||||
ele.eventTypeColor = 'warning-background-color'
|
||||
break
|
||||
case 'XLL':
|
||||
ele.eventTypeColor = 'pink-background-color'
|
||||
break
|
||||
case 'CLBG':
|
||||
ele.eventTypeColor = 'other-background-color'
|
||||
break
|
||||
case 'JOIN':
|
||||
ele.eventTypeColor = 'success-background-color'
|
||||
break
|
||||
case 'CHDP':
|
||||
ele.eventTypeColor = 'purple-background-color'
|
||||
break
|
||||
case 'DATA':
|
||||
ele.eventTypeColor = 'yellow-background-color'
|
||||
break
|
||||
}
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
const { refreshing, finished, loading, curPage, list, onLoad, onRefresh } = listLoadAndRefresh(getData, 'stock')
|
||||
const { setScrollTop, setScrollPositionAndRefreshFlag } = scrollList(pageContent, scrollPosition, refresh)
|
||||
|
||||
setScrollTop()
|
||||
onBeforeRouteLeave((to, from) => {
|
||||
setScrollPositionAndRefreshFlag(to, 'stock-detail')
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.eventNote {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.audioDiv {
|
||||
height: 28px;
|
||||
width: 300px;
|
||||
border-radius: 32px;
|
||||
background-color: #f1f3f4;
|
||||
padding-left: 10px;
|
||||
margin: 4px 0;
|
||||
|
||||
.van-icon {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
770
src/views/target/economy/economy-detail.vue
Normal file
770
src/views/target/economy/economy-detail.vue
Normal file
@@ -0,0 +1,770 @@
|
||||
<template>
|
||||
<div class="page-div">
|
||||
<back-button />
|
||||
<div class="header">
|
||||
<div class="header-content">
|
||||
<h1 class="title">{{ economyName }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-card" v-for="item in chartList" :key="item.id">
|
||||
<div class="card-title">{{ item.name }}</div>
|
||||
<div class="chart-container">
|
||||
<van-loading v-show="item.loading" type="spinner" color="#1989fa" class="loading" />
|
||||
<div :id="`${item.id}`" class="chartDiv"></div>
|
||||
</div>
|
||||
<div class="value-list-container">
|
||||
<div v-for="(res, index) in item.valueList" :key="index" class="value-item">
|
||||
<span class="value-name">{{ res.name }}:</span>
|
||||
<span class="value-value">{{ res.value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang='ts'>
|
||||
import BackButton from '@/components/back-button.vue'
|
||||
import moment from 'moment'
|
||||
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
|
||||
import { useRoute } from 'vue-router'
|
||||
const route = useRoute()
|
||||
const economyName = ref('')
|
||||
let chartList = ref<any[]>([])
|
||||
import { chartMixins } from '@/mixins/chart-mixins'
|
||||
const { charts, destroyedFlag } = chartMixins()
|
||||
|
||||
const init = async () => {
|
||||
const id = route.params.id + ''
|
||||
if (id === '1') {
|
||||
economyName.value = '储蓄专题'
|
||||
charts.value = [null, null, null, null, null, null, null]
|
||||
chartList.value = [{
|
||||
id: 'yoyReduce',
|
||||
loading: true,
|
||||
name: '储蓄增速VSM2 Spread(%)',
|
||||
valueList: []
|
||||
}, {
|
||||
id: 'deposit1',
|
||||
loading: true,
|
||||
name: '沪深市值/住户存款(%)',
|
||||
valueList: []
|
||||
}, {
|
||||
id: 'deposit2',
|
||||
loading: true,
|
||||
name: '(沪深京市值+港股通南向余额)/住户存款(%)',
|
||||
valueList: []
|
||||
}, {
|
||||
id: 'deposit',
|
||||
loading: true,
|
||||
name: '住户存款(万亿)',
|
||||
valueList: []
|
||||
}, {
|
||||
id: 'depositYoy',
|
||||
loading: true,
|
||||
name: '居民存储同比增速(%)',
|
||||
|
||||
valueList: []
|
||||
}, {
|
||||
id: 'm2',
|
||||
loading: true,
|
||||
name: '货币和准货币(M2)(万亿)',
|
||||
valueList: []
|
||||
}, {
|
||||
id: 'm2Yoy',
|
||||
loading: true,
|
||||
name: '货币和准货币(M2)增速Yoy(%)',
|
||||
valueList: []
|
||||
}]
|
||||
} else if (id === '2') {
|
||||
economyName.value = '两融专题'
|
||||
charts.value = [null, null, null]
|
||||
chartList.value = [{
|
||||
id: 'twoFinancingData',
|
||||
loading: true,
|
||||
name: '两融数据',
|
||||
valueList: []
|
||||
}, {
|
||||
id: 'twoFinancingRatiosHs',
|
||||
loading: true,
|
||||
name: '两融比沪深(%)',
|
||||
valueList: []
|
||||
}, {
|
||||
id: 'twoFinancingInflow',
|
||||
loading: true,
|
||||
name: '两融净流入(亿)',
|
||||
valueList: []
|
||||
}]
|
||||
} else if (id === '3') {
|
||||
economyName.value = 'A股专题'
|
||||
charts.value = [null, null, null, null, null, null]
|
||||
chartList.value = [{
|
||||
id: 'totalShares',
|
||||
loading: true,
|
||||
name: '发行总股本(万亿)',
|
||||
valueList: []
|
||||
}, {
|
||||
id: 'totalMarket',
|
||||
loading: true,
|
||||
name: '总市值(万亿)',
|
||||
valueList: []
|
||||
}, {
|
||||
id: 'dealAmount',
|
||||
loading: true,
|
||||
name: '月成交金额(万亿)',
|
||||
valueList: []
|
||||
}, {
|
||||
id: 'turnoverRate',
|
||||
loading: true,
|
||||
name: '换手率(%)',
|
||||
valueList: []
|
||||
}, {
|
||||
id: 'volume',
|
||||
loading: true,
|
||||
name: '月成交量(万亿)',
|
||||
valueList: []
|
||||
}, {
|
||||
id: 'szNum',
|
||||
loading: true,
|
||||
name: '月开户数量(万)',
|
||||
valueList: []
|
||||
}]
|
||||
} else if (id === '4') {
|
||||
economyName.value = '指数专题'
|
||||
charts.value = [null, null, null, null]
|
||||
chartList.value = [{
|
||||
id: 'hsIndex',
|
||||
loading: true,
|
||||
name: 'Hang Seng Index and Sub-indexes (HSI)(派息率)',
|
||||
valueList: []
|
||||
}, {
|
||||
id: 'hs300',
|
||||
loading: true,
|
||||
name: '沪深300',
|
||||
valueList: []
|
||||
}, {
|
||||
id: 'zz500',
|
||||
loading: true,
|
||||
name: '中证500',
|
||||
valueList: []
|
||||
}, {
|
||||
id: 'zz1000',
|
||||
loading: true,
|
||||
name: '中证1000',
|
||||
valueList: []
|
||||
}]
|
||||
}
|
||||
if (id === '1' || id === '2' || id === '3' || id === '4') {
|
||||
nextTick(() => {
|
||||
getChartData()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
import * as echarts from 'echarts'
|
||||
import { getLineSeries, getBarSeries, getBaseOption, getXAxis, getYAxis } from '@/utils/chart'
|
||||
import {
|
||||
reportEconomyMonthDepositVsM2, reportEconomyMonthMarketHSVsDeposit, reportEconomyMonthMarketHSJVsDeposit,
|
||||
reportEconomyMonthListByItem, stockStockrzrqQueryRzrq, stockStockrzrqQueryRzToTotalMarket, stockStockrzrqQueryRzjme,
|
||||
reportEconomyMonthStockShares, reportEconomyMonthStockMarket, reportEconomyMonthStockAmount,
|
||||
reportEconomyMonthStockTurnoverRate, reportEconomyMonthStockVolume, reportEconomyMonthNewAccountNum, stockStockHsList
|
||||
} from '@/utils/api'
|
||||
const getChartData = async () => {
|
||||
const id = route.params.id + ''
|
||||
if (id === '1') {
|
||||
const { data: data0 } = await reportEconomyMonthDepositVsM2()
|
||||
const sysDate0: any = []
|
||||
const seriesList0: any = [getLineSeries({ name: '储蓄增速VSM2 Spread' }), getLineSeries({ name: '储蓄增速VSM2 Spread前三月均值' }), getLineSeries({ name: '储蓄增速VSM2 Spread后三月均值' })]
|
||||
data0.map((ele: any) => {
|
||||
sysDate0.push(ele.sysDate)
|
||||
seriesList0[0].data.push({ value: ele.depositYoy && ele.m2Yoy ? Math.round((ele.depositYoy - ele.m2Yoy) * 10000) / 100 : null })
|
||||
seriesList0[1].data.push({ value: ele.before3 ? Math.round(ele.before3 * 100) / 100 : null })
|
||||
seriesList0[2].data.push({ value: ele.after3 ? Math.round(ele.after3 * 100) / 100 : null })
|
||||
})
|
||||
const tooltipFormatter0 = function (data: any) {
|
||||
let backText = `日期:${data[0].name}<br/>`
|
||||
data.forEach((ele: any) => {
|
||||
backText += `<span style='color: ${ele.color}'>${ele.seriesName}</span>:`
|
||||
if (ele.value > 0) {
|
||||
backText += `<span style='color: red'>${ele.value ? ele.value + '%' : ''}</span><br/>`
|
||||
} else if (ele.value < 0) {
|
||||
backText += `<span style='color: green'>${ele.value ? ele.value + '%' : ''}</span><br/>`
|
||||
} else {
|
||||
backText += `<span>${ele.value ? ele.value + '%' : ''}</span><br/>`
|
||||
}
|
||||
})
|
||||
return backText
|
||||
}
|
||||
chartList.value[0].valueList = [
|
||||
{ name: '日期', value: sysDate0[sysDate0.length - 1] },
|
||||
{ name: '储蓄增速VSM2 Spread', value: seriesList0[0].data[seriesList0[0].data.length - 1].value + '%' },
|
||||
{ name: '储蓄增速VSM2 Spread前三月均值', value: seriesList0[1].data[seriesList0[1].data.length - 1].value + '%' },
|
||||
{ name: '储蓄增速VSM2 Spread后三月均值', value: seriesList0[2].data[seriesList0[2].data.length - 1].value ? seriesList0[2].data[seriesList0[2].data.length - 1].value + '%' : '' }
|
||||
]
|
||||
setChartOption(
|
||||
0,
|
||||
[getYAxis({ })],
|
||||
sysDate0,
|
||||
seriesList0,
|
||||
tooltipFormatter0,
|
||||
{ data: ['储蓄增速VSM2 Spread', '储蓄增速VSM2 Spread前三月均值', '储蓄增速VSM2 Spread后三月均值'], top: '10px' },
|
||||
[{ top: '80px', bottom: '20px', left: '40px', right: '40px' }]
|
||||
)
|
||||
const { data: data1 } = await reportEconomyMonthMarketHSVsDeposit()
|
||||
const chartData1 = getChartData2(data1, 1)
|
||||
chartList.value[1].valueList = [
|
||||
{ name: '日期', value: chartData1.sysDate[chartData1.sysDate.length - 1] },
|
||||
{ name: '总市值', value: chartData1.seriesList[0].data[chartData1.seriesList[0].data.length - 1].totalMarket + '万亿' },
|
||||
{ name: '住房存款', value: chartData1.seriesList[0].data[chartData1.seriesList[0].data.length - 1].deposit + '万亿' },
|
||||
{ name: '比例', value: chartData1.seriesList[0].data[chartData1.seriesList[0].data.length - 1].value + '%' }
|
||||
]
|
||||
setChartOption(1, [getYAxis({ })], chartData1.sysDate, chartData1.seriesList, chartData1.tooltipFormatter)
|
||||
const { data: data2 } = await reportEconomyMonthMarketHSJVsDeposit()
|
||||
const chartData2 = getChartData2(data2, 2)
|
||||
chartList.value[2].valueList = [
|
||||
{ name: '日期', value: chartData2.sysDate[chartData2.sysDate.length - 1] },
|
||||
{ name: '总市值', value: chartData2.seriesList[0].data[chartData2.seriesList[0].data.length - 1].totalMarket + '万亿' },
|
||||
{ name: '住房存款', value: chartData2.seriesList[0].data[chartData2.seriesList[0].data.length - 1].deposit + '万亿' },
|
||||
{ name: '南方余额', value: chartData2.seriesList[0].data[chartData2.seriesList[0].data.length - 1].southBalance + '亿' },
|
||||
{ name: '比例', value: chartData2.seriesList[0].data[chartData2.seriesList[0].data.length - 1].value + '%' }
|
||||
]
|
||||
setChartOption(2, [getYAxis({ })], chartData2.sysDate, chartData2.seriesList, chartData2.tooltipFormatter)
|
||||
const chartData3: any = await getChartData3('deposit', '万亿', 10000)
|
||||
chartList.value[3].valueList = [
|
||||
{ name: '日期', value: chartData3.sysDate[chartData3.sysDate.length - 1] },
|
||||
{ name: '住户存款', value: chartData3.seriesList[0].data[chartData3.seriesList[0].data.length - 1].value + '万亿' }
|
||||
]
|
||||
setChartOption(3, [getYAxis({ })], chartData3.sysDate, chartData3.seriesList, chartData3.tooltipFormatter)
|
||||
const chartData4: any = await getChartData3('deposit_yoy', '%', 100)
|
||||
chartList.value[4].valueList = [
|
||||
{ name: '日期', value: chartData4.sysDate[chartData4.sysDate.length - 1] },
|
||||
{ name: '居民存储同比增速', value: chartData4.seriesList[0].data[chartData4.seriesList[0].data.length - 1].value + '%' }
|
||||
]
|
||||
setChartOption(4, [getYAxis({ })], chartData4.sysDate, chartData4.seriesList, chartData4.tooltipFormatter)
|
||||
const chartData5: any = await getChartData3('m2', '万亿', 10000)
|
||||
chartList.value[5].valueList = [
|
||||
{ name: '日期', value: chartData5.sysDate[chartData5.sysDate.length - 1] },
|
||||
{ name: '货币和准货币(M2)', value: chartData5.seriesList[0].data[chartData5.seriesList[0].data.length - 1].value + '万亿' }
|
||||
]
|
||||
setChartOption(5, [getYAxis({ })], chartData5.sysDate, chartData5.seriesList, chartData5.tooltipFormatter)
|
||||
const chartData6: any = await getChartData3('m2_yoy', '%', 100)
|
||||
chartList.value[6].valueList = [
|
||||
{ name: '日期', value: chartData6.sysDate[chartData6.sysDate.length - 1] },
|
||||
{ name: '货币和准货币(M2)增速Yoy', value: chartData6.seriesList[0].data[chartData6.seriesList[0].data.length - 1].value + '%' }
|
||||
]
|
||||
setChartOption(6, [getYAxis({ })], chartData6.sysDate, chartData6.seriesList, chartData6.tooltipFormatter)
|
||||
} else if (id === '2') {
|
||||
const { data: data0 } = await stockStockrzrqQueryRzrq()
|
||||
const sysDate0: any = []
|
||||
const seriesList0: any = [
|
||||
getLineSeries({ name: '沪深300', yAxisIndex: 2 }),
|
||||
getLineSeries({ name: '中证100', yAxisIndex: 2 }),
|
||||
getLineSeries({ name: '中证500', yAxisIndex: 2 }),
|
||||
getLineSeries({ name: '中证1000', yAxisIndex: 2 }),
|
||||
getLineSeries({ name: '沪深股票数', yAxisIndex: 2 }),
|
||||
getLineSeries({ name: '沪深总市值', yAxisIndex: 1 }),
|
||||
getLineSeries({ name: '沪深总股数', yAxisIndex: 1 }),
|
||||
getLineSeries({ name: '融资余额' }),
|
||||
getLineSeries({ name: '较去年融资增长率', yAxisIndex: 3 }),
|
||||
getLineSeries({ name: '去年平均融资额' })
|
||||
]
|
||||
data0.map((ele: any) => {
|
||||
sysDate0.push(ele.sysDate)
|
||||
seriesList0[0].data.push({ value: ele.hs300 ? Math.round(ele.hs300 * 100) / 100 : null })
|
||||
seriesList0[1].data.push({ value: ele.zz100 ? Math.round(ele.zz100 * 100) / 100 : null })
|
||||
seriesList0[2].data.push({ value: ele.zz500 ? Math.round(ele.zz500 * 100) / 100 : null })
|
||||
seriesList0[3].data.push({ value: ele.zz1000 ? Math.round(ele.zz1000 * 100) / 100 : null })
|
||||
seriesList0[4].data.push({ value: ele.totalStock ? Math.round(ele.totalStock * 100) / 100 : null })
|
||||
seriesList0[5].data.push({ value: ele.totalMarket ? Math.round(ele.totalMarket / 1000000) / 100 : null })
|
||||
seriesList0[6].data.push({ value: ele.totalShares ? Math.round(ele.totalShares / 100000) / 100 : null })
|
||||
seriesList0[7].data.push({ value: ele.rzye ? Math.round(ele.rzye / 100) / 100 : null })
|
||||
seriesList0[8].data.push({ value: ele.rzyeLastYearAvg ? Math.round((ele.rzye - ele.rzyeLastYearAvg) / ele.rzyeLastYearAvg * 10000) / 100 : null })
|
||||
seriesList0[9].data.push({ value: ele.rzyeLastYearAvg ? Math.round(ele.rzyeLastYearAvg / 100) / 100 : null })
|
||||
})
|
||||
const tooltipFormatter0 = function (data: any) {
|
||||
let backText = `日期:${data[0].name}<br/>`
|
||||
data.forEach((ele: any) => {
|
||||
let value = ele.value
|
||||
if (ele.seriesName === '沪深总股数') {
|
||||
value = ele.value ? Math.round(ele.value * 10) / 100 + '万亿' : ''
|
||||
} else if (ele.seriesName === '沪深总市值' || ele.seriesName === '融资余额' || ele.seriesName === '去年平均融资额') {
|
||||
value = ele.value ? ele.value + '万亿' : ''
|
||||
} else if (ele.seriesName === '较去年融资增长率') {
|
||||
value = ele.value ? ele.value + '%' : ''
|
||||
}
|
||||
backText += `<div><span style="color: ${ele.color}">${ele.seriesName}</span>:${value}</div>`
|
||||
})
|
||||
return backText
|
||||
}
|
||||
chartList.value[0].valueList = [
|
||||
{ name: '日期', value: sysDate0[sysDate0.length - 1] },
|
||||
{ name: '沪深300', value: seriesList0[0].data[seriesList0[0].data.length - 1].value },
|
||||
{ name: '中证100', value: seriesList0[1].data[seriesList0[1].data.length - 1].value },
|
||||
{ name: '中证500', value: seriesList0[2].data[seriesList0[2].data.length - 1].value },
|
||||
{ name: '中证1000', value: seriesList0[3].data[seriesList0[3].data.length - 1].value },
|
||||
{ name: '沪深股票数', value: seriesList0[4].data[seriesList0[4].data.length - 1].value },
|
||||
{ name: '沪深总市值', value: seriesList0[5].data[seriesList0[5].data.length - 1].value + '万亿' },
|
||||
{ name: '沪深总股数', value: seriesList0[6].data[seriesList0[6].data.length - 1].value ? Math.round(seriesList0[6].data[seriesList0[6].data.length - 1].value * 10) / 100 + '万亿' : '' },
|
||||
{ name: '融资余额', value: seriesList0[7].data[seriesList0[7].data.length - 1].value + '万亿' },
|
||||
{ name: '较去年融资增长率', value: seriesList0[8].data[seriesList0[8].data.length - 1].value + '%' },
|
||||
{ name: '去年平均融资额', value: seriesList0[9].data[seriesList0[9].data.length - 1].value + '万亿' }
|
||||
]
|
||||
setChartOption(
|
||||
0,
|
||||
[getYAxis({ }), getYAxis({ showFlag: false, position: 'right' }), getYAxis({ showFlag: false, position: 'right' }), getYAxis({ showFlag: false, position: 'right' })],
|
||||
sysDate0,
|
||||
seriesList0,
|
||||
tooltipFormatter0,
|
||||
{
|
||||
data: ['沪深300', '中证100', '中证500', '中证1000', '沪深股票数', '沪深总市值', '沪深总股数', '融资余额', '较去年融资增长率', '去年平均融资额'],
|
||||
top: '10px',
|
||||
selected: {
|
||||
沪深300: false,
|
||||
中证100: false,
|
||||
中证500: false,
|
||||
中证1000: false,
|
||||
沪深股票数: false,
|
||||
沪深总市值: false,
|
||||
沪深总股数: false,
|
||||
融资余额: true,
|
||||
较去年融资增长率: true,
|
||||
去年平均融资额: true
|
||||
}
|
||||
},
|
||||
[{ top: '100px', bottom: '20px', left: '40px', right: '40px' }]
|
||||
)
|
||||
const { data: data1 } = await stockStockrzrqQueryRzToTotalMarket()
|
||||
const sysDate1: any = []
|
||||
const seriesList1: any = [
|
||||
getLineSeries({ name: '比重' }),
|
||||
getLineSeries({ name: '创业板指数', yAxisIndex: 1 }),
|
||||
getLineSeries({ name: '沪深300', yAxisIndex: 1 }),
|
||||
getLineSeries({ name: '融资融券余额', yAxisIndex: 2 })
|
||||
]
|
||||
data1.map((ele: any) => {
|
||||
sysDate1.push(ele.sysDate)
|
||||
seriesList1[0].data.push({
|
||||
value: Math.round(ele.per * 100) / 100,
|
||||
totalMarket: Math.round(ele.totalMarket / 1000000) / 100
|
||||
})
|
||||
seriesList1[1].data.push({ value: Math.round(ele.cybNav * 100) / 100 })
|
||||
seriesList1[2].data.push({ value: Math.round(ele.hs300Nav * 100) / 100 })
|
||||
seriesList1[3].data.push({ value: Math.round(ele.rzrqye / 100) / 100 })
|
||||
})
|
||||
const tooltipFormatter1 = function (data: any) {
|
||||
let backText = `日期:${data[0].name}<br/>`
|
||||
data.forEach((ele: any) => {
|
||||
if (ele.value) {
|
||||
if (ele.seriesName === '比重') {
|
||||
backText += `<br/><span style="color: blue">比重</span>:<span style="color: red">${ele.value}%</span><br/>`
|
||||
backText += `<span style="color: blue">沪深市值</span>:<span style="color: red">${ele.data.totalMarket ? ele.data.totalMarket + '万亿' : ''}</span><br/>`
|
||||
} else if (ele.seriesName === '融资融券余额') {
|
||||
backText += `<span style="color: blue">${ele.seriesName}</span>:<span style="color: red">${ele.value + '万亿'}</span><br/>`
|
||||
} else {
|
||||
backText += `<span style="color: blue">${ele.seriesName}</span>:<span style="color: red">${ele.value}</span><br/>`
|
||||
}
|
||||
}
|
||||
})
|
||||
return backText
|
||||
}
|
||||
chartList.value[1].valueList = [
|
||||
{ name: '日期', value: sysDate1[sysDate1.length - 1] },
|
||||
{ name: '沪深市值', value: seriesList1[0].data[seriesList1[0].data.length - 1].totalMarket + '万亿' },
|
||||
{ name: '比重', value: seriesList1[0].data[seriesList1[0].data.length - 1].value + '%' },
|
||||
{ name: '创业板指数', value: seriesList1[1].data[seriesList1[1].data.length - 1].value },
|
||||
{ name: '沪深300', value: seriesList1[2].data[seriesList1[2].data.length - 1].value },
|
||||
{ name: '融资融券余额', value: seriesList1[3].data[seriesList1[3].data.length - 1].value + '万亿' }
|
||||
]
|
||||
setChartOption(
|
||||
1,
|
||||
[getYAxis({ }), getYAxis({ position: 'right', showFlag: false }), getYAxis({ position: 'right', showFlag: false })],
|
||||
sysDate1,
|
||||
seriesList1,
|
||||
tooltipFormatter1
|
||||
)
|
||||
const { data: data2 } = await stockStockrzrqQueryRzjme({
|
||||
beginDate: moment().subtract(1, 'years').format('YYYY-MM-DD'),
|
||||
endDate: moment().format('YYYY-MM-DD')
|
||||
})
|
||||
const sysDate2: any = []
|
||||
const seriesList2: any = [
|
||||
getBarSeries({ name: '两融净流入', labelFlag: false }),
|
||||
getLineSeries({ name: '沪深300', yAxisIndex: 1 }),
|
||||
]
|
||||
let yMin1 = 0
|
||||
let yMax1 = 0
|
||||
let yMin2 = 0
|
||||
let yMax2 = 0
|
||||
let min = null
|
||||
data2.map((ele: any, index: number) => {
|
||||
sysDate2.push(ele.sys_date)
|
||||
seriesList2[0].data.push({ value: ele.rzjme })
|
||||
seriesList2[1].data.push({ value: ele.hs300 })
|
||||
if (index === 0) {
|
||||
yMin1 = ele.rzjme
|
||||
yMax1 = ele.rzjme
|
||||
} else {
|
||||
if (ele.rzjme < yMin1) {
|
||||
yMin1 = ele.rzjme
|
||||
}
|
||||
if (ele.rzjme > yMax1) {
|
||||
yMax1 = ele.rzjme
|
||||
}
|
||||
}
|
||||
if (index === 0) {
|
||||
yMin2 = ele.hs300
|
||||
yMax2 = ele.hs300
|
||||
} else {
|
||||
if (ele.hs300 < yMin2) {
|
||||
yMin2 = ele.hs300
|
||||
}
|
||||
if (ele.hs300 > yMax2) {
|
||||
yMax2 = ele.hs300
|
||||
}
|
||||
}
|
||||
})
|
||||
if (yMin1 < 0) {
|
||||
yMax1 = Math.ceil(yMax1 / 100) * 100
|
||||
yMax2 = Math.ceil(yMax2 / 100) * 100
|
||||
yMin1 = Math.floor(yMin1 / 100) * 100
|
||||
yMin2 = Math.floor(yMin2 / 100) * 100
|
||||
min = yMin2 - (yMax2 - yMin2) * ((0 - yMin1) / (yMax1 - yMin1) + 0.1)
|
||||
min = Math.floor(min / 100) * 100
|
||||
}
|
||||
const tooltipFormatter2 = function (data: any) {
|
||||
let backText = `日期:${data[0].name}<br/>`
|
||||
data.forEach((ele: any) => {
|
||||
if (ele.value) {
|
||||
backText += `<span style="color: blue">${ele.seriesName}:</span><span style="color: red">${ele.value}${ele.seriesName === '两融净流入' ? '亿' : ''}</span><br/>`
|
||||
}
|
||||
})
|
||||
return backText
|
||||
}
|
||||
chartList.value[2].valueList = [
|
||||
{ name: '日期', value: sysDate2[sysDate2.length - 1] },
|
||||
{ name: '两融净流入', value: seriesList2[0].data[seriesList2[0].data.length - 1].value + '亿' },
|
||||
{ name: '沪深300', value: seriesList2[1].data[seriesList2[1].data.length - 1].value },
|
||||
]
|
||||
setChartOption(
|
||||
2,
|
||||
yMin1 < 0 ?
|
||||
[getYAxis({ }), getYAxis({ position: 'right', showFlag: false, min: min, max: yMax2 })]:
|
||||
[getYAxis({ }), getYAxis({ position: 'right', showFlag: false })],
|
||||
sysDate2,
|
||||
seriesList2,
|
||||
tooltipFormatter2,
|
||||
{ data: ['两融净流入', '沪深300'], top: '10px' },
|
||||
[{ top: '40px', bottom: '20px', left: '40px', right: '40px' }]
|
||||
)
|
||||
} else if (id === '3') {
|
||||
const { data: data0 } = await reportEconomyMonthStockShares()
|
||||
const chartData0: any = getChartData4(data0, '万亿', ['totalShares'], 10000)
|
||||
chartList.value[0].valueList = [
|
||||
{ name: '日期', value: chartData0.sysDate[chartData0.sysDate.length - 1] },
|
||||
{ name: '发行总股本', value: chartData0.seriesList[0].data[chartData0.seriesList[0].data.length - 1].value + '万亿' }
|
||||
]
|
||||
setChartOption(0, [getYAxis({ })], chartData0.sysDate, chartData0.seriesList, chartData0.tooltipFormatter)
|
||||
const { data: data1 } = await reportEconomyMonthStockMarket()
|
||||
const chartData1: any = getChartData4(data1, '万亿', ['totalMarket'], 10000)
|
||||
chartList.value[1].valueList = [
|
||||
{ name: '日期', value: chartData1.sysDate[chartData1.sysDate.length - 1] },
|
||||
{ name: '总市值', value: chartData1.seriesList[0].data[chartData1.seriesList[0].data.length - 1].value + '万亿' }
|
||||
]
|
||||
setChartOption(1, [getYAxis({ })], chartData1.sysDate, chartData1.seriesList, chartData1.tooltipFormatter)
|
||||
const { data: data2 } = await reportEconomyMonthStockAmount()
|
||||
const chartData2: any = getChartData4(data2, '', ['dealAmount'], 10000)
|
||||
chartList.value[2].valueList = [
|
||||
{ name: '日期', value: chartData2.sysDate[chartData2.sysDate.length - 1] },
|
||||
{ name: '月成交金额', value: chartData2.seriesList[0].data[chartData2.seriesList[0].data.length - 1].value + '万亿' }
|
||||
]
|
||||
setChartOption(2, [getYAxis({ })], chartData2.sysDate, chartData2.seriesList, chartData2.tooltipFormatter)
|
||||
const { data: data3 } = await reportEconomyMonthStockTurnoverRate()
|
||||
const chartData3: any = getChartData4(data3, '%', ['turnoverRate'])
|
||||
chartList.value[3].valueList = [
|
||||
{ name: '日期', value: chartData3.sysDate[chartData3.sysDate.length - 1] },
|
||||
{ name: '换手率', value: chartData3.seriesList[0].data[chartData3.seriesList[0].data.length - 1].value + '%' }
|
||||
]
|
||||
setChartOption(3, [getYAxis({ })], chartData3.sysDate, chartData3.seriesList, chartData3.tooltipFormatter)
|
||||
const { data: data4 } = await reportEconomyMonthStockVolume()
|
||||
const chartData4: any = getChartData4(data4, '万亿', ['volume'], 10000)
|
||||
chartList.value[4].valueList = [
|
||||
{ name: '日期', value: chartData4.sysDate[chartData4.sysDate.length - 1] },
|
||||
{ name: '月成交量', value: chartData4.seriesList[0].data[chartData4.seriesList[0].data.length - 1].value + '万亿' }
|
||||
]
|
||||
setChartOption(4, [getYAxis({ })], chartData4.sysDate, chartData4.seriesList, chartData4.tooltipFormatter)
|
||||
const { data: data5 } = await reportEconomyMonthNewAccountNum()
|
||||
const chartData5: any = getChartData4(data5, '万', ['totalNewAccountNum', 'shNewAccountNum'], 10000)
|
||||
chartList.value[5].valueList = [
|
||||
{ name: '日期', value: chartData5.sysDate[chartData5.sysDate.length - 1] },
|
||||
{ name: '总开户数', value: chartData5.seriesList[0].data[chartData5.seriesList[0].data.length - 1].value + '万' },
|
||||
{ name: '上证开户数', value: chartData5.seriesList[1].data[chartData5.seriesList[1].data.length - 1].value + '万' }
|
||||
]
|
||||
setChartOption(
|
||||
5,
|
||||
[getYAxis({ })],
|
||||
chartData5.sysDate,
|
||||
chartData5.seriesList,
|
||||
chartData5.tooltipFormatter,
|
||||
{ data: ['总开户数', '上证开户数'], top: '10px' },
|
||||
[{ top: '40px', bottom: '20px', left: '40px', right: '40px' }]
|
||||
)
|
||||
} else if (id === '4') {
|
||||
const { data: data0 } = await stockStockHsList()
|
||||
const sysDate0: any = []
|
||||
const seriesList0: any = [
|
||||
getLineSeries({ name: 'Hang Seng Index(%)' }),
|
||||
getLineSeries({ name: 'HSI-Finance' }),
|
||||
getLineSeries({ name: 'HSI-Utilities' }),
|
||||
getLineSeries({ name: 'HSI-Properties' }),
|
||||
getLineSeries({ name: 'HSI-Com & Ind' }),
|
||||
getLineSeries({ name: 'Hang Seng Index', yAxisIndex: 1 })
|
||||
]
|
||||
data0.monthList.forEach((ele: any) => {
|
||||
sysDate0.push(ele.hsMonth)
|
||||
seriesList0[0].data.push({ value: ele.hsi })
|
||||
seriesList0[1].data.push({ value: ele.hsiFinance })
|
||||
seriesList0[2].data.push({ value: ele.hsiUtilities })
|
||||
seriesList0[3].data.push({ value: ele.hsiProperties })
|
||||
seriesList0[4].data.push({ value: ele.hsiComInd })
|
||||
seriesList0[5].data.push({ value: ele.hsNav })
|
||||
})
|
||||
data0.dayList.forEach((ele: any) => {
|
||||
sysDate0.push(ele.sysDate)
|
||||
seriesList0[0].data.push({ value: ele.hsi })
|
||||
seriesList0[1].data.push({ value: ele.hsiFinance })
|
||||
seriesList0[2].data.push({ value: ele.hsiUtilities })
|
||||
seriesList0[3].data.push({ value: ele.hsiProperties })
|
||||
seriesList0[4].data.push({ value: ele.hsiComInd })
|
||||
seriesList0[5].data.push({ value: ele.hsNav })
|
||||
})
|
||||
if (data0.monthList.length > 0 && data0.dayList.length > 0) {
|
||||
seriesList0[5].markArea = {
|
||||
itemStyle: {
|
||||
color: '#afdfe4'
|
||||
},
|
||||
data: [
|
||||
[
|
||||
{ name: '本月', xAxis: data0.monthList[data0.monthList.length - 1].hsMonth },
|
||||
{ xAxis: data0.dayList[data0.dayList.length - 1].sysDate }
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
const tooltipFormatter0 = function (data: any) {
|
||||
let backText = `日期:${data[0].name}<br/>`
|
||||
data.forEach((ele: any) => {
|
||||
backText += `<span style="color: ${ele.color}">${ele.seriesName}</span>:${ele.value ? ele.value : '暂无数据'}<br/>`
|
||||
})
|
||||
return backText
|
||||
}
|
||||
chartList.value[0].valueList = [
|
||||
{ name: '日期', value: sysDate0[sysDate0.length - 1] },
|
||||
{ name: 'Hang Seng Index(%)', value: seriesList0[0].data[seriesList0[0].data.length - 1].value },
|
||||
{ name: 'HSI-Finance', value: seriesList0[1].data[seriesList0[1].data.length - 1].value },
|
||||
{ name: 'HSI-Utilities', value: seriesList0[2].data[seriesList0[2].data.length - 1].value },
|
||||
{ name: 'HSI-Properties', value: seriesList0[3].data[seriesList0[3].data.length - 1].value },
|
||||
{ name: 'HSI-Com & Ind', value: seriesList0[4].data[seriesList0[4].data.length - 1].value },
|
||||
{ name: 'Hang Seng Index', value: seriesList0[5].data[seriesList0[5].data.length - 1].value }
|
||||
]
|
||||
setChartOption(
|
||||
0,
|
||||
[getYAxis({ }), getYAxis({ position: 'right', showFlag: false })],
|
||||
sysDate0,
|
||||
seriesList0,
|
||||
tooltipFormatter0,
|
||||
{ data: ['Hang Seng Index(%)', 'HSI-Finance', 'HSI-Utilities', 'HSI-Properties', 'HSI-Com & Ind', 'Hang Seng Index'], top: '10px' },
|
||||
[{ top: '100px', bottom: '20px', left: '40px', right: '40px' }]
|
||||
)
|
||||
const chartData1: any = await getChartData3('hs300', '')
|
||||
chartList.value[1].valueList = [
|
||||
{ name: '日期', value: chartData1.sysDate[chartData1.sysDate.length - 1] },
|
||||
{ name: '中证300', value: chartData1.seriesList[0].data[chartData1.seriesList[0].data.length - 1].value }
|
||||
]
|
||||
setChartOption(1, [getYAxis({ })], chartData1.sysDate, chartData1.seriesList, chartData1.tooltipFormatter)
|
||||
const chartData2: any = await getChartData3('zz500', '')
|
||||
chartList.value[2].valueList = [
|
||||
{ name: '日期', value: chartData2.sysDate[chartData2.sysDate.length - 1] },
|
||||
{ name: '中证500', value: chartData2.seriesList[0].data[chartData2.seriesList[0].data.length - 1].value }
|
||||
]
|
||||
setChartOption(2, [getYAxis({ })], chartData2.sysDate, chartData2.seriesList, chartData2.tooltipFormatter)
|
||||
const chartData3: any = await getChartData3('zz1000', '')
|
||||
chartList.value[3].valueList = [
|
||||
{ name: '日期', value: chartData3.sysDate[chartData3.sysDate.length - 1] },
|
||||
{ name: '中证1000', value: chartData3.seriesList[0].data[chartData3.seriesList[0].data.length - 1].value }
|
||||
]
|
||||
setChartOption(3, [getYAxis({ })], chartData3.sysDate, chartData3.seriesList, chartData3.tooltipFormatter)
|
||||
}
|
||||
}
|
||||
|
||||
const getChartData2 = (data: any, index: number) => {
|
||||
const sysDate: any = []
|
||||
const seriesList: any = [getLineSeries({ })]
|
||||
data.map((ele: any) => {
|
||||
sysDate.push(ele.sysDate)
|
||||
const item: any = {
|
||||
value: Math.round(ele.rateValue * 100) / 100,
|
||||
deposit: Math.round(ele.deposit * 100) / 100,
|
||||
totalMarket: Math.round(ele.totalMarket * 100) / 100
|
||||
}
|
||||
if (index === 2) {
|
||||
item.southBalance = Math.round(ele.southBalance * 100) / 100
|
||||
}
|
||||
seriesList[0].data.push(item)
|
||||
})
|
||||
const tooltipFormatter = function (data: any) {
|
||||
let backText = `日期:${data[0].name}<br/>`
|
||||
data.forEach((ele: any) => {
|
||||
backText += `<span style="color: blue">总市值:</span><span style="color: red">${ele.data.totalMarket}万亿</span><br/>`
|
||||
backText += `<span style="color: blue">住房存款:</span><span style="color: red">${ele.data.deposit}万亿</span><br/>`
|
||||
if (index === 2 && ele.data.southBalance) {
|
||||
backText += `<span style="color: blue">南方余额:</span><span style="color: red">${ele.data.southBalance}亿</span><br/>`
|
||||
}
|
||||
backText += `<span style="color: blue">比例:</span><span style="color: red">${ele.data.value}%</span>`
|
||||
})
|
||||
return backText
|
||||
}
|
||||
return { sysDate: sysDate, seriesList: seriesList, tooltipFormatter: tooltipFormatter }
|
||||
}
|
||||
|
||||
const getChartData3 = async(type: string, unit: string, scale?: number) => {
|
||||
const { data } = await reportEconomyMonthListByItem({ items: type })
|
||||
const sysDate: any = []
|
||||
const seriesList: any = [getLineSeries({ })]
|
||||
data.map((ele: any) => {
|
||||
sysDate.push(ele.sysDate)
|
||||
if (scale === 10000) {
|
||||
seriesList[0].data.push({ value: Math.round(ele[type] / 100) / 100 })
|
||||
} else if (scale === 100) {
|
||||
seriesList[0].data.push({ value: Math.round(ele[type] * 10000) / 100 })
|
||||
} else {
|
||||
seriesList[0].data.push({ value: Math.round(ele[type] * 100) / 100 })
|
||||
}
|
||||
})
|
||||
const tooltipFormatter = function (data: any) {
|
||||
let backText = `日期:${data[0].name}<br/>`
|
||||
data.forEach((ele: any) => {
|
||||
backText += `<span style='color: ${ele.color}'>${ele.value ? ele.value + unit : '暂无数据'}</span>`
|
||||
})
|
||||
return backText
|
||||
}
|
||||
return { sysDate: sysDate, seriesList: seriesList, tooltipFormatter: tooltipFormatter }
|
||||
}
|
||||
|
||||
const getChartData4 = (data: any, unit: string, type: string[], scale?: number) => {
|
||||
const sysDate: any = []
|
||||
let seriesList: any = []
|
||||
if (type.length === 2) {
|
||||
seriesList = [getLineSeries({ name: '总开户数' }), getLineSeries({ name: '上证开户数' })]
|
||||
} else {
|
||||
seriesList = [getLineSeries({ })]
|
||||
}
|
||||
data.map((ele: any) => {
|
||||
sysDate.push(ele.sysDate)
|
||||
type.forEach((res, index) => {
|
||||
if (scale === 10000) {
|
||||
seriesList[index].data.push({ value: ele[res] ? Math.round(ele[res] / 100) / 100 : null })
|
||||
} else {
|
||||
seriesList[index].data.push({ value: Math.round(ele[res] * 100) / 100 })
|
||||
}
|
||||
})
|
||||
})
|
||||
const tooltipFormatter = function (data: any) {
|
||||
let backText = `日期:${data[0].name}<br/>`
|
||||
data.forEach((ele: any) => {
|
||||
if (type.length === 2) {
|
||||
backText += `<span style="color: ${ele.color}">${ele.seriesName}</span>:${ele.value ? ele.value + unit : '暂无数据'}<br/>`
|
||||
} else {
|
||||
backText += `<span style='color: ${ele.color}'>${ele.value ? ele.value + unit : '暂无数据'}</span><br/>`
|
||||
}
|
||||
})
|
||||
return backText
|
||||
}
|
||||
return { sysDate: sysDate, seriesList: seriesList, tooltipFormatter: tooltipFormatter }
|
||||
}
|
||||
|
||||
const setChartOption = (index: number, yAxis: any, sysDate: any, series: any, tooltipFormatter: any, legend?: any, grid?: any) => {
|
||||
const option = getBaseOption({
|
||||
yAxis: yAxis,
|
||||
xAxis: [getXAxis({ data: sysDate })],
|
||||
series: series,
|
||||
legend: legend,
|
||||
tooltipFormatter: tooltipFormatter,
|
||||
grid: grid ? grid : [{ top: '20px', bottom: '20px', left: '40px', right: '40px' }]
|
||||
})
|
||||
if (!destroyedFlag.value && document.getElementById(chartList.value[index].id)) {
|
||||
const chart = echarts.init(document.getElementById(chartList.value[index].id))
|
||||
chart.setOption(option)
|
||||
charts.value[index] = chart
|
||||
chartList.value[index].loading = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted (() =>{
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.page-div {
|
||||
padding-top: 86px;
|
||||
height: auto;
|
||||
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
|
||||
@media screen and (min-width: 992px) {
|
||||
left: calc(50vw - 496px);
|
||||
right: calc(50vw - 496px);
|
||||
}
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
height: 30vh;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
height: 40vh;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.value-list-container {
|
||||
padding: 12px 15px;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.value-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px dashed #eee;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.value-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.value-item:hover {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.value-name {
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.value-value {
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
min-width: 80px;
|
||||
}
|
||||
</style>
|
||||
52
src/views/target/economy/economy-list.vue
Normal file
52
src/views/target/economy/economy-list.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<div class="page-div">
|
||||
<back-button />
|
||||
<div class="header">
|
||||
<h1 class="title">经济专题</h1>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<van-grid :column-num="1" :gutter="16" :border="false">
|
||||
<van-grid-item v-for="item in list" :key="item.id">
|
||||
<div @touchstart="onTouchStart(item.economyName)" @touchend="onTouchEnd(item.economyName, 'economy-detail', item.id)" class="item"
|
||||
@touchcancel="onTouchCancel(item.economyName)" :class="{ 'tapped': tappedItem === item.economyName }">
|
||||
<div class="card-content" :style="{ backgroundColor: item.bgColor, borderLeftColor: item.color }">
|
||||
<div class="card-code" :style="{ color: item.color }">{{ item.economyName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-grid-item>
|
||||
</van-grid>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BackButton from '@/components/back-button.vue'
|
||||
|
||||
import { tapMixins } from '@/mixins/tap-mixins'
|
||||
const { tappedItem, onTouchStart, onTouchEnd, onTouchCancel } = tapMixins()
|
||||
|
||||
import { ref, onMounted } from 'vue'
|
||||
let list = ref<any[]>([])
|
||||
import { colorList } from '@/utils/colorList'
|
||||
|
||||
const init = async () => {
|
||||
list.value = [
|
||||
{ economyName: '储蓄专题', id: 1 },
|
||||
{ economyName: '两融专题', id: 2 },
|
||||
{ economyName: 'A股专题', id: 3 },
|
||||
{ economyName: '指数专题', id: 4 }
|
||||
]
|
||||
|
||||
list.value.forEach((ele, index) => {
|
||||
ele.color = colorList[index % colorList.length].color
|
||||
ele.bgColor = colorList[index % colorList.length].bgColor
|
||||
})
|
||||
}
|
||||
|
||||
onMounted (() =>{
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
</style>
|
||||
663
src/views/target/etf/etf-detail.vue
Normal file
663
src/views/target/etf/etf-detail.vue
Normal file
@@ -0,0 +1,663 @@
|
||||
<template>
|
||||
<div class="page-div">
|
||||
<back-button />
|
||||
<div class="header">
|
||||
<div class="header-content">
|
||||
<h1 class="title">{{ fundData?.etfCode }}</h1>
|
||||
<p class="subtitle">{{ fundData?.etfName || '指数详情' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-card" v-if="chartList.length > 0">
|
||||
<div class="chart-container">
|
||||
<van-loading v-show="chartList[0].loading" type="spinner" color="#1989fa" class="loading" />
|
||||
<div :id="`${chartList[0].id}`" class="chartDiv"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-card" v-if="!virtualCurrency && chartList.length > 0">
|
||||
<div class="chart-container">
|
||||
<van-loading v-show="chartList[1].loading" type="spinner" color="#1989fa" class="loading" />
|
||||
<div :id="`${chartList[1].id}`" class="chartDiv"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-card" v-if="!virtualCurrency && chartList.length > 0">
|
||||
<div class="chart-container">
|
||||
<van-loading v-show="chartList[2].loading" type="spinner" color="#1989fa" class="loading" />
|
||||
<div :id="`${chartList[2].id}`" class="chartDiv"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-card" v-if="virtualCurrency">
|
||||
<div class="tableDiv">
|
||||
<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="td--column--1">时间</th>
|
||||
<th>股本(变化)</th>
|
||||
<th>{{ route.params.id === 'IBIT' ? '比特币数量' : '以太坊数量' }}(数量变化)</th>
|
||||
<th>股本/{{ route.params.id === 'IBIT' ? '比特币' : '以太坊' }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(coin, index) in coinList" :key="index">
|
||||
<td class="td--column--1">{{ coin.sysDate }}</td>
|
||||
<td>
|
||||
{{ coin.shares }}<br/>
|
||||
{{ coin.sharesChange }}<br/>
|
||||
<span :class="coin.sharesChangePer > 0 ? 'red-color' : coin.sharesChangePer < 0 ? 'green-color' : ''"
|
||||
v-if="coin.sharesChangePer">({{ coin.sharesChangePer }}%)</span>
|
||||
</td>
|
||||
<td>
|
||||
{{ coin.coinNum }}<br/>
|
||||
{{ coin.coinNumChange }}<br/>
|
||||
<span :class="coin.coinNumChangePer > 0 ? 'red-color' : coin.coinNumChangePer < 0 ? 'green-color' : ''"
|
||||
v-if="coin.coinNumChangePer">({{ coin.coinNumChangePer }}%)</span>
|
||||
</td>
|
||||
<td>{{ coin.per }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-card" v-if="!virtualCurrency">
|
||||
<h3 class="card-title">持仓列表</h3>
|
||||
<div> 最新日期:<span class="blue-color">{{ holdList.length > 0 ? holdList[0].sysDate : '暂无'}}</span>,
|
||||
本季开始日期:<span class="red-color">{{ holdList.length > 0 ? holdList[0].lastQuarterDate : '暂无'}}</span></div>
|
||||
<div class="tableDiv">
|
||||
<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="td--column--1">名称</th>
|
||||
<th>上期权重</th>
|
||||
<th>当前权重</th>
|
||||
<th>相对变化</th>
|
||||
<th>市值</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(hold, index) in holdList" :key="index" @click="showStockChart(hold)">
|
||||
<td class="td--column--1">
|
||||
{{ hold.ticker }}
|
||||
<div class="van-ellipsis firstTd">{{ hold.tickerName }}</div>
|
||||
</td>
|
||||
<td class="blue-color">{{ hold.lastQuarterWeight }}%</td>
|
||||
<td>
|
||||
<div :class="hold.weight > hold.lastQuarterWeight ? 'red-color' : hold.weight < hold.lastQuarterWeight ? 'green-color' : ''">{{ hold.weight }}%</div>
|
||||
</td>
|
||||
<td>
|
||||
<div :style="{color: hold.weightChangePer > 0 ? 'red' : hold.weightChangePer < 0 ? 'green' : ''}">
|
||||
{{ hold.weightChangePer > 0 ? '+' + hold.weightChangePer : hold.weightChangePer }}%
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ hold.marketValue }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<van-popup v-model:show="showStockChartPopup" position="bottom" :style="{ height: '50%' }">
|
||||
<div class="p-16">
|
||||
<div class="d-flex justify-content-between items-center mb-16">
|
||||
<div class="sub-title f-b">{{ stockCode }}【{{ stockName }}】权重趋势</div>
|
||||
<van-icon name="close" @click="closeStockChart" />
|
||||
</div>
|
||||
<div class="popup-card">
|
||||
<div class="chart-container">
|
||||
<van-loading v-show="chartList[3].loading" type="spinner" color="#1989fa" class="loading" />
|
||||
<div :id="`${chartList[3].id}`" class="chartDiv"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-popup>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang='ts'>
|
||||
import BackButton from '@/components/back-button.vue'
|
||||
|
||||
import { ref, onMounted, computed, onUnmounted, nextTick } from 'vue'
|
||||
|
||||
import { useRoute } from 'vue-router'
|
||||
const route = useRoute()
|
||||
const virtualCurrency = computed(() => {
|
||||
return route.params.id === 'ETHA' || route.params.id === 'IBIT'
|
||||
})
|
||||
import { indexFundGetByEtfCode, indexFundHisQueryEtfShareHis, indexFundDetailDayCoinList, indexFundDetailDayList, indexFundDetailQuarterCircleByExchange, indexFundDetailQuarterHisByExchange, indexFundDetailQuarterGetChart } from '@/utils/api'
|
||||
const fundData: any = ref(null)
|
||||
import { addOrSubtractTime, formatNumberByUnit, formatNumber } from '@/utils'
|
||||
|
||||
import * as echarts from 'echarts'
|
||||
import { getLineSeries, getBaseOption, getXAxis, getYAxis } from '@/utils/chart'
|
||||
const init = async () => {
|
||||
try {
|
||||
const { data } = await indexFundGetByEtfCode({ etfCode: route.params.id + '' })
|
||||
fundData.value = data
|
||||
const { data: navList } = await indexFundHisQueryEtfShareHis({
|
||||
timeE: new Date().toISOString().split('T')[0],
|
||||
timeB: addOrSubtractTime(new Date(), -10, 'year'),
|
||||
etfCode: route.params.id + ''
|
||||
})
|
||||
setLineChart(navList.dataList)
|
||||
if (virtualCurrency.value) {
|
||||
getCoinList()
|
||||
} else {
|
||||
getChartData()
|
||||
getHoldList()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('初始化数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const setLineChart = (data: any) => {
|
||||
const sysDate: any = []
|
||||
const unit = ['万', '亿', '']
|
||||
let seriesList: any = []
|
||||
let yAxis: any = []
|
||||
let xAxis: any = []
|
||||
let grid: any = []
|
||||
seriesList.push(getLineSeries({ name: '股本' }))
|
||||
seriesList.push(getLineSeries({ name: '市值', yAxisIndex: 1 }))
|
||||
seriesList.push(getLineSeries({ name: 'NAV', yAxisIndex: 2 }))
|
||||
if (virtualCurrency.value) {
|
||||
seriesList.push(getLineSeries({ name: '股本回撤', xAxisIndex: 1, yAxisIndex: 3 }))
|
||||
seriesList.push(getLineSeries({ name: 'NAV回撤', xAxisIndex: 1, yAxisIndex: 3 }))
|
||||
grid = [{ bottom: '54%', left: '0', right: '0', top: '40px' }, { top: '62%', bottom: '20px', left: '0', right: '0' }]
|
||||
yAxis = [
|
||||
getYAxis({ axisLabel: '{value} 万', color: '#FF0000', showFlag: false }),
|
||||
getYAxis({ axisLabel: '{value} 亿', position: 'right', color: '#3E8EF7', showFlag: false }),
|
||||
getYAxis({ position: 'left', offset: 90, color: '#000000', showFlag: false }),
|
||||
getYAxis({ gridIndex: 1, axisLabel: '{value}%', showFlag: false })
|
||||
]
|
||||
const drawdowns1: any = []
|
||||
const drawdowns2: any = []
|
||||
let maxDrawdown1 = 0
|
||||
let maxDrawdown2 = 0
|
||||
for (let j = 0; j < data.length; j++) {
|
||||
let drawdown1 = 0
|
||||
const sharesOutstanding = Number(data[j].sharesOutstanding)
|
||||
if (sharesOutstanding > maxDrawdown1) {
|
||||
maxDrawdown1 = sharesOutstanding
|
||||
drawdown1 = 0
|
||||
} else {
|
||||
drawdown1 = Math.round((sharesOutstanding - maxDrawdown1) / maxDrawdown1 * 10000) / 100
|
||||
}
|
||||
drawdowns1.push(drawdown1)
|
||||
let drawdown2 = 0
|
||||
const NAV = Number(data[j].NAV)
|
||||
if (NAV > maxDrawdown2) {
|
||||
maxDrawdown2 = NAV
|
||||
drawdown2 = 0
|
||||
} else {
|
||||
drawdown2 = Math.round((NAV - maxDrawdown2) / maxDrawdown2 * 10000) / 100
|
||||
}
|
||||
drawdowns2.push(drawdown2)
|
||||
}
|
||||
data.forEach((ele: any, index: number) => {
|
||||
sysDate.push(ele.sysDate)
|
||||
seriesList[0].data.push({ value: ele.sharesOutstanding, sysDate: ele.sysDate, drawdowns: drawdowns1[index] })
|
||||
seriesList[1].data.push({ value: ele.market, sysDate: ele.sysDate })
|
||||
seriesList[2].data.push({ value: ele.NAV, sysDate: ele.sysDate, drawdowns: drawdowns2[index] })
|
||||
seriesList[3].data.push({ value: drawdowns1[index], sysDate: ele.sysDate, nav: ele.sharesOutstanding })
|
||||
seriesList[4].data.push({ value: drawdowns2[index], sysDate: ele.sysDate, nav: ele.NAV })
|
||||
})
|
||||
xAxis = [getXAxis({ data: sysDate }), getXAxis({ data: sysDate, gridIndex: 1, showFlag: false })]
|
||||
} else{
|
||||
grid = [{ left: '40px', right: '40px', bottom: '20px', top: '20px' }]
|
||||
yAxis = [
|
||||
getYAxis({ axisLabel: '{value} 万', color: '#FF0000', showFlag: false }),
|
||||
getYAxis({ axisLabel: '{value} 亿', position: 'right', color: '#3E8EF7', showFlag: false }),
|
||||
getYAxis({ position: 'left', offset: 90, color: '#000000', showFlag: false })
|
||||
]
|
||||
data.forEach((ele: any) => {
|
||||
sysDate.push(ele.sysDate)
|
||||
seriesList[0].data.push({ value: ele.sharesOutstanding })
|
||||
seriesList[1].data.push({ value: ele.market })
|
||||
seriesList[2].data.push({ value: ele.NAV })
|
||||
})
|
||||
xAxis = [getXAxis({ data: sysDate })]
|
||||
}
|
||||
|
||||
if (!destroyedFlag.value) {
|
||||
let chart = echarts.init(document.getElementById(`${chartList.value[0].id}`))
|
||||
const option = getBaseOption({
|
||||
series: seriesList,
|
||||
yAxis: yAxis,
|
||||
xAxis: xAxis,
|
||||
tooltipFormatter: function (params: any) {
|
||||
const data = Array.isArray(params) ? params : [params]
|
||||
let backText = `日期:${data[0].name}<br/>`
|
||||
if (virtualCurrency.value) {
|
||||
if (data[0].seriesName.includes('回撤')) {
|
||||
data.forEach((ele, index) => {
|
||||
backText +=
|
||||
`<div style="color: ${ele.color}">${ele.seriesName.slice(0, -2)}:${ele.data.nav || 0}${ele.data.nav ? unit[index] : ''}</div>`
|
||||
backText +=
|
||||
`<div style="color: ${ele.color}">${ele.seriesName}:${ele.value || 0}</div>`
|
||||
})
|
||||
} else {
|
||||
data.forEach((ele, index) => {
|
||||
backText +=
|
||||
`<div style="color: ${ele.color}">${ele.seriesName}:${ele.value || '0'}${ele.value ? unit[index] : ''}</div>`
|
||||
if (ele.seriesName !== '市值') {
|
||||
backText +=
|
||||
`<div style="color: ${ele.color}">${ele.seriesName + '回撤'}:${ele.data.drawdowns || '0'}</div>`
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
data.forEach((ele) => {
|
||||
backText += `<span style="color: ${ele.color}">${ele.seriesName}</span>:<span class="f-b">${formatNumberByUnit(ele.value)}</span><br/>`
|
||||
})
|
||||
}
|
||||
return backText
|
||||
},
|
||||
grid: grid,
|
||||
color: ['red', 'blue', 'black'],
|
||||
legend: {
|
||||
data: ['股本', '市值', 'NAV'],
|
||||
top: '0px',
|
||||
show: true
|
||||
}
|
||||
})
|
||||
chart.setOption(option)
|
||||
charts.value[0] = chart
|
||||
chartList.value[0].loading = false
|
||||
}
|
||||
}
|
||||
|
||||
const coinList = ref<any[]>([])
|
||||
const getCoinList = async () => {
|
||||
const { data } = await indexFundDetailDayCoinList({ etfCode: route.params.id + '' })
|
||||
data.map((ele: any, index: number) => {
|
||||
if (index < data.length - 1) {
|
||||
const sharesChange = ele.shares - data[index + 1].shares
|
||||
ele.sharesChangePer = Math.round(sharesChange / ele.shares * 10000) / 100
|
||||
ele.sharesChange = formatNumber(sharesChange)
|
||||
const coinNumChange = Math.round((ele.coinNum - data[index + 1].coinNum) * 100) / 100
|
||||
ele.coinNumChangePer = Math.round(coinNumChange / ele.coinNum * 10000) / 100
|
||||
ele.coinNumChange = formatNumber(coinNumChange)
|
||||
} else {
|
||||
ele.sharesChange = 0
|
||||
ele.sharesChangePer = 0
|
||||
ele.coinNumChange = 0
|
||||
ele.coinNumChangePer = 0
|
||||
}
|
||||
ele.per = Math.round(ele.shares / ele.coinNum * 100) / 100
|
||||
ele.shares = formatNumber(ele.shares)
|
||||
ele.coinNum = formatNumber(ele.coinNum)
|
||||
})
|
||||
coinList.value = data
|
||||
}
|
||||
|
||||
const colors = ['#5673cc', '#9fe080', '#fdd85e', '#ee6666', '#73c0de', '#3ba272', '#ff915a']
|
||||
const getChartData = async () => {
|
||||
const { data } = await indexFundDetailQuarterCircleByExchange({ etfCode: route.params.id + '' })
|
||||
data.sort(function (a: any, b: any) {
|
||||
return b.marketValue - a.marketValue
|
||||
})
|
||||
const series: any = []
|
||||
let weight = 0
|
||||
let stockNum = 0
|
||||
data.forEach((ele: any, index: number) => {
|
||||
if (index < 6) {
|
||||
series.push({ value: ele.weight, name: ele.exchange, stockNum: ele.stockNum })
|
||||
} else {
|
||||
weight = Math.round((weight + ele.weight) * 100) / 100
|
||||
stockNum = Math.round(stockNum + ele.stockNum)
|
||||
if (index === data.length - 1) {
|
||||
series.push({ value: weight, name: '其他', stockNum: stockNum })
|
||||
}
|
||||
}
|
||||
})
|
||||
const color = colors.slice(0, series.length)
|
||||
const option = {
|
||||
color: color,
|
||||
tooltip: { trigger: 'item' },
|
||||
legend: { orient: 'vertical', left: 'left' },
|
||||
series: [{
|
||||
type: 'pie',
|
||||
radius: '50%',
|
||||
data: series,
|
||||
label: {
|
||||
formatter: function (data: any) {
|
||||
return `{b|${data.name}}:\n{hr|}\n权重{per|${data.value}%}股票數量{per|${data.data.stockNum}}`
|
||||
},
|
||||
backgroundColor: '#F6F8FC',
|
||||
borderColor: '#8C8D8E',
|
||||
borderWidth: 1,
|
||||
borderRadius: 4,
|
||||
rich: {
|
||||
a: { color: '#6E7079', lineHeight: 22, align: 'center' },
|
||||
hr: { borderColor: '#8C8D8E', width: '100%', borderWidth: 1, height: 0 },
|
||||
b: { color: '#4C5058', fontSize: 14, fontWeight: 'bold', lineHeight: 33 },
|
||||
per: { color: '#fff', backgroundColor: '#4C5058', padding: [3, 4], borderRadius: 4, lineHeight: 33 }
|
||||
}
|
||||
},
|
||||
emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' } }
|
||||
}]
|
||||
}
|
||||
if (!destroyedFlag.value && document.getElementById(chartList.value[1].id)) {
|
||||
let chart = echarts.init(document.getElementById(`${chartList.value[1].id}`))
|
||||
chart.setOption(option)
|
||||
charts.value[1] = chart
|
||||
chartList.value[1].loading = false
|
||||
setMarketLineChart1(data, color)
|
||||
chart.on('legendselectchanged', (params: any) => {
|
||||
setMarketLineChart2(color, params.selected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const minTime = ref('')
|
||||
const maxTime = ref('')
|
||||
const seriesList = ref<any[]>([])
|
||||
const legendList = ref<any[]>([])
|
||||
const setMarketLineChart1 = async (res: any, color: any) => {
|
||||
const series: any = []
|
||||
const legends: any = []
|
||||
let min = ''
|
||||
let max = ''
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
if (i < 6) {
|
||||
legends.push(res[i].exchange)
|
||||
series.push(getLineSeries({ name: res[i].exchange }))
|
||||
const { data } = await indexFundDetailQuarterHisByExchange({ etfCode: route.params.id + '', exchange: res[i].exchange })
|
||||
data.forEach((ele: any) => {
|
||||
series[i].data.push([ele.sysDate, ele.weight, ele.marketValue, ele.stockNum])
|
||||
})
|
||||
if (data.length > 0) {
|
||||
if (i === 0) {
|
||||
min = data[0].sysDate
|
||||
max = data[data.length - 1].sysDate
|
||||
} else {
|
||||
if (new Date(data[0].sysDate).getTime() < new Date(min).getTime()) {
|
||||
min = data[0].sysDate
|
||||
}
|
||||
if (new Date(data[data.length - 1].sysDate).getTime() > new Date(max).getTime()) {
|
||||
max = data[data.length - 1].sysDate
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (i === 6) {
|
||||
legends.push('其他')
|
||||
series.push(getLineSeries({ name: '其他' }))
|
||||
}
|
||||
const { data } = await indexFundDetailQuarterHisByExchange({ etfCode: route.params.id + '', exchange: res[i].exchange })
|
||||
if (data.length > 0) {
|
||||
if (new Date(data[0].sysDate).getTime() < new Date(min).getTime()) {
|
||||
min = data[0].sysDate
|
||||
}
|
||||
if (new Date(data[data.length - 1].sysDate).getTime() > new Date(max).getTime()) {
|
||||
max = data[data.length - 1].sysDate
|
||||
}
|
||||
}
|
||||
data.forEach((ele: any) => {
|
||||
const item = series[6].data.find((res: any) => {
|
||||
return res[0] === ele.sysDate
|
||||
})
|
||||
if (item) {
|
||||
item[1] += ele.weight
|
||||
item[2] += ele.marketValue
|
||||
item[3] += ele.stockNum
|
||||
} else {
|
||||
series[6].data.push([ele.sysDate, ele.weight, ele.marketValue, ele.stockNum])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
minTime.value = min
|
||||
maxTime.value = max
|
||||
seriesList.value = series
|
||||
legendList.value = legends
|
||||
nextTick(() => {
|
||||
setMarketLineChart(color, { data: legends, top: '30px', show: false })
|
||||
})
|
||||
}
|
||||
|
||||
const setMarketLineChart2 = (color: any, selected: any) => {
|
||||
if (charts.value[2]) {
|
||||
charts.value[2].dispose()
|
||||
}
|
||||
nextTick(() => {
|
||||
setMarketLineChart(color,
|
||||
{
|
||||
data: legendList.value,
|
||||
top: '30px',
|
||||
show: false,
|
||||
selected
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const setMarketLineChart = (color: any, legend: any) => {
|
||||
const option = getBaseOption({
|
||||
color: color,
|
||||
yAxis: [getYAxis({ })],
|
||||
xAxis: [getXAxis({
|
||||
type: 'time',
|
||||
min: minTime.value,
|
||||
max: maxTime.value,
|
||||
axisLabel: function (value: any) {
|
||||
var date = new Date(value)
|
||||
var texts = `${date.getFullYear()}\n${(date.getMonth() + 1)}-${date.getDate()}`
|
||||
return texts
|
||||
}
|
||||
})],
|
||||
series: JSON.parse(JSON.stringify(seriesList.value)),
|
||||
tooltipFormatter: function (params: any) {
|
||||
const data = Array.isArray(params) ? params : [params]
|
||||
let res = data[0].data[0] + '</br>'
|
||||
data.forEach((ele: any) => {
|
||||
res += `${ele.seriesName}:</br>权重:${Math.round(ele.data[1] * 100) / 100}%</br>股票:${ele.data[3]}支</br>市值:${Math.round(ele.data[2] * 100) / 100}</br>`
|
||||
})
|
||||
return res
|
||||
},
|
||||
grid: [{
|
||||
left: '60px',
|
||||
right: '40px'
|
||||
}],
|
||||
legend: legend
|
||||
})
|
||||
if (!destroyedFlag.value && document.getElementById(chartList.value[2].id)) {
|
||||
let chart = echarts.init(document.getElementById(`${chartList.value[2].id}`))
|
||||
chart.setOption(option)
|
||||
charts.value[2] = chart
|
||||
chartList.value[2].loading = false
|
||||
}
|
||||
}
|
||||
|
||||
const curPage = ref(1)
|
||||
const holdList = ref<any[]>([])
|
||||
const totalPages = ref(1)
|
||||
const hasMore = ref(true)
|
||||
const isLoading = ref(false)
|
||||
const getHoldList = async () => {
|
||||
if (!hasMore.value || isLoading.value) return
|
||||
isLoading.value = true
|
||||
try {
|
||||
const { data } = await indexFundDetailDayList({ etfCode: route.params.id + '', curPage: curPage.value, limit: 50 })
|
||||
totalPages.value = data.totalPage || 1
|
||||
hasMore.value = curPage.value < totalPages.value
|
||||
data.list.forEach((ele: any) => {
|
||||
ele.shares = formatNumberByUnit(ele.shares)
|
||||
ele.marketValue = formatNumberByUnit(ele.marketValue)
|
||||
})
|
||||
holdList.value = [...holdList.value, ...data.list]
|
||||
} catch (error) {
|
||||
console.error('加载失败:', error)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadNextPage = () => {
|
||||
if (hasMore.value) {
|
||||
curPage.value++
|
||||
getHoldList()
|
||||
}
|
||||
}
|
||||
|
||||
const handleScroll = () => {
|
||||
if (!virtualCurrency.value) {
|
||||
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop
|
||||
const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight
|
||||
const clientHeight = document.documentElement.clientHeight || window.innerHeight
|
||||
|
||||
if (scrollTop + clientHeight >= scrollHeight - 100 && hasMore.value && !isLoading.value) {
|
||||
loadNextPage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('scroll', handleScroll)
|
||||
})
|
||||
|
||||
const showStockChartPopup = ref(false)
|
||||
const stockCode = ref('')
|
||||
const stockName = ref('')
|
||||
const showStockChart = async (item: any) => {
|
||||
showStockChartPopup.value = true
|
||||
stockCode.value = item.stockCode
|
||||
stockName.value = item.tickerName
|
||||
const { data } = await indexFundDetailQuarterGetChart({ stockCode: item.stockCode, etfCode: route.params.id + '', exchange: item.exchange })
|
||||
const sysDate: any = []
|
||||
const seriesList = [getLineSeries({ name: '权重' }), getLineSeries({ name: '股本', yAxisIndex: 1, lineType: 'dashed' })]
|
||||
let gt = 0
|
||||
const lte = data.length
|
||||
data.forEach((ele: any, index: number) => {
|
||||
sysDate.push(ele.sysDate)
|
||||
seriesList[0].data.push({ value: ele.weight })
|
||||
seriesList[1].data.push({ value: ele.shares })
|
||||
if (index > 0 && ele.dataType === 'D' && data[index - 1].dataType === 'Q') {
|
||||
gt = index
|
||||
}
|
||||
})
|
||||
nextTick(() => {
|
||||
if (!destroyedFlag.value) {
|
||||
let chart = echarts.init(document.getElementById(`${chartList.value[3].id}`))
|
||||
const option = getBaseOption({
|
||||
series: seriesList,
|
||||
yAxis: [getYAxis({ axisLabel: '{value} %' }), getYAxis({ position: 'right' })],
|
||||
xAxis: [getXAxis({ data: sysDate })],
|
||||
tooltipFormatter: function (params: any) {
|
||||
const data = Array.isArray(params) ? params : [params]
|
||||
let backText = `${data[0].name}:`
|
||||
data.forEach((ele) => {
|
||||
backText += `<span class="f-b">${ele.value}${ele.seriesName === '权重' ? '%' : ''}</span><br/>`
|
||||
})
|
||||
return backText
|
||||
},
|
||||
grid: [{ top: '20px', left: '50px', right: '50px', bottom: '20px' }],
|
||||
legend: {
|
||||
data: [{
|
||||
name: '权重',
|
||||
itemStyle: {
|
||||
color: '#ff0000'
|
||||
},
|
||||
icon: 'path://M802.59 532.76H221.4c-11.47 0-20.76-9.3-20.76-20.76s9.29-20.76 20.76-20.76h581.19c11.47 0 20.76 9.3 20.76 20.76s-9.29 20.76-20.76 20.76z'
|
||||
}, {
|
||||
name: '股本',
|
||||
itemStyle: {
|
||||
color: '#ff0000'
|
||||
},
|
||||
icon: 'path://M234.666667 490.666667h-153.6a25.6 25.6 0 1 0 0 51.2h153.6a25.6 25.6 0 1 0 0-51.2zM473.6 490.666667h-153.6a25.6 25.6 0 1 0 0 51.2h153.6a25.6 25.6 0 1 0 0-51.2zM934.4 490.666667h-136.533333a25.6 25.6 0 1 0 0 51.2h136.533333a25.6 25.6 0 1 0 0-51.2zM712.533333 490.666667h-153.6a25.6 25.6 0 1 0 0 51.2h153.6a25.6 25.6 0 1 0 0-51.2z'
|
||||
}],
|
||||
top: '30px'
|
||||
},
|
||||
gt: gt,
|
||||
lte: lte
|
||||
})
|
||||
chart.setOption(option)
|
||||
charts.value[3] = chart
|
||||
chartList.value[3].loading = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const closeStockChart = () =>{
|
||||
if (charts.value[3]) {
|
||||
charts.value[3].dispose()
|
||||
charts.value[3] = null
|
||||
}
|
||||
showStockChartPopup.value = false
|
||||
}
|
||||
|
||||
import { chartMixins } from '@/mixins/chart-mixins'
|
||||
const { charts, destroyedFlag } = chartMixins()
|
||||
|
||||
let chartList = ref<any[]>([])
|
||||
onMounted (() =>{
|
||||
charts.value = virtualCurrency.value ? [null] : [null, null, null, null]
|
||||
chartList.value = virtualCurrency.value ?
|
||||
[{ id: 'fund_chart', loading: true }] :
|
||||
[{ id: 'fund_chart', loading: true }, { id: 'fund_pie_chart', loading: true }, { id: 'fund_line_chart', loading: true }, { id: 'stock_chart', loading: true }]
|
||||
init()
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.page-div {
|
||||
padding-top: 105px;
|
||||
height: auto;
|
||||
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
|
||||
@media screen and (min-width: 992px) {
|
||||
left: calc(50vw - 496px);
|
||||
right: calc(50vw - 496px);
|
||||
}
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
height: 26vh;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
height: calc(50vh - 226px);
|
||||
}
|
||||
}
|
||||
.tableDiv {
|
||||
th {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.firstTd {
|
||||
min-width: 80px;
|
||||
max-width: 120px;
|
||||
width: auto;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
min-width: 150px;
|
||||
max-width: 400px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popup-card {
|
||||
.chart-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 30vh;
|
||||
@media screen and (min-width: 768px) {
|
||||
height: 40vh;
|
||||
}
|
||||
|
||||
.loading {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.chartDiv {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
362
src/views/target/fred/fred-detail.vue
Normal file
362
src/views/target/fred/fred-detail.vue
Normal file
@@ -0,0 +1,362 @@
|
||||
<template>
|
||||
<div class="page-div">
|
||||
<back-button />
|
||||
<div class="header">
|
||||
<h1 class="title">{{ fredCode }}</h1>
|
||||
<p class="subtitle">{{ fredName }}</p>
|
||||
</div>
|
||||
<div class="chart-header">
|
||||
<div class="time-range-buttons">
|
||||
<van-button
|
||||
v-for="range in timeRanges"
|
||||
:key="range.value"
|
||||
:plain="selectedTimeRange !== range.value"
|
||||
:hairline="selectedTimeRange !== range.value"
|
||||
:type="selectedTimeRange === range.value ? 'primary' : 'default'"
|
||||
@click="setTimeRange(range.value)"
|
||||
style="margin-left: 8px;"
|
||||
>{{ range.label }}</van-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="chart-container">
|
||||
<van-loading v-show="loading" type="spinner" color="#1989fa" class="loading" />
|
||||
<div :id="`${fredCode}_chart`" class="chartDiv"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chartItem d-flex align-items-center justify-content-center">
|
||||
</div>
|
||||
<div class="listDiv">
|
||||
<div class="data-list-container" v-if="!loading">
|
||||
<div v-if="list.length > 0">
|
||||
<div class="list-header">
|
||||
<div class="list-item">
|
||||
<div class="list-col date-col">日期</div>
|
||||
<template v-if="route.params.id">
|
||||
<div class="list-col value-col">值(%)</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="list-col aaa-col">AAA(%)</div>
|
||||
<div class="list-col bbb-col">BBB(%)</div>
|
||||
<div class="list-col spread-col">BBB-AAA(%)</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<RecycleScroller
|
||||
class="list-body"
|
||||
:items="list"
|
||||
:item-size="1"
|
||||
key-field="sysDate"
|
||||
>
|
||||
<template v-slot="{ item }">
|
||||
<div class="list-item">
|
||||
<div class="list-col date-col">{{ item.sysDate }}</div>
|
||||
<template v-if="route.params.id">
|
||||
<div class="list-col value-col">{{ item.fredValue }}</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="list-col aaa-col">{{ item.AaaFredValue }}</div>
|
||||
<div class="list-col bbb-col">{{ item.BbbFredValue }}</div>
|
||||
<div class="list-col spread-col">{{ (item.BbbFredValue - item.AaaFredValue).toFixed(2) }}</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</RecycleScroller>
|
||||
</div>
|
||||
<div class="no-data" v-else>暂无数据</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang='ts'>
|
||||
import BackButton from '@/components/back-button.vue'
|
||||
|
||||
import { ref, nextTick, onMounted } from 'vue'
|
||||
import { RecycleScroller } from 'vue-virtual-scroller'
|
||||
|
||||
import { useRoute } from 'vue-router'
|
||||
import { fredInfoInfo, fredDetailQueryFred, fredDetailQueryAaaAndBbb } from '@/utils/api'
|
||||
let fredCode = ref('')
|
||||
let fredName = ref('')
|
||||
let beginTime = ref('')
|
||||
let endTime = ref('')
|
||||
let selectedTimeRange = ref('3Y')
|
||||
|
||||
const timeRanges = [
|
||||
{ label: '1M', value: '1M', unit: 'month', offset: -1 },
|
||||
{ label: '3M', value: '3M', unit: 'month', offset: -3 },
|
||||
{ label: '1Y', value: '1Y', unit: 'year', offset: -1 },
|
||||
{ label: '3Y', value: '3Y', unit: 'year', offset: -3 },
|
||||
]
|
||||
const route = useRoute()
|
||||
const init = async () => {
|
||||
const id = route.params.id
|
||||
if (id) {
|
||||
const { data } = await fredInfoInfo(id + '')
|
||||
fredCode.value = data.fredCode
|
||||
fredName.value = data.fredName
|
||||
} else {
|
||||
fredCode.value = 'BBB-AAA'
|
||||
fredName.value = 'Option-Adjusted Spread'
|
||||
}
|
||||
setTimeRange('3Y')
|
||||
charts.value = [null]
|
||||
}
|
||||
|
||||
import { addOrSubtractTime } from '@/utils'
|
||||
const setTimeRange = (range: string) => {
|
||||
selectedTimeRange.value = range
|
||||
endTime.value = new Date().toISOString().split('T')[0]
|
||||
const rangeConfig = timeRanges.find(item => item.value === range)
|
||||
if (rangeConfig) {
|
||||
beginTime.value = addOrSubtractTime(new Date(), rangeConfig.offset, rangeConfig.unit as 'month' | 'year')
|
||||
}
|
||||
|
||||
disposeAll()
|
||||
nextTick(() => {
|
||||
const id = route.params.id
|
||||
getData(id ? 1 : 0)
|
||||
})
|
||||
}
|
||||
|
||||
let loading = ref(true)
|
||||
let list = ref<any[]>([])
|
||||
import * as echarts from 'echarts'
|
||||
import { chartMixins } from '@/mixins/chart-mixins'
|
||||
const { charts, destroyedFlag, disposeAll } = chartMixins()
|
||||
import { getLineSeries, getBaseOption, getXAxis, getYAxis } from '@/utils/chart'
|
||||
const getData = async (index: number) => {
|
||||
loading.value = true
|
||||
const sysDate: any = []
|
||||
let seriesList: any = []
|
||||
let color = []
|
||||
const legend = {
|
||||
data: [] as string[],
|
||||
top: '30px',
|
||||
show: false
|
||||
}
|
||||
if (index === 0) {
|
||||
const { data } = await fredDetailQueryAaaAndBbb({
|
||||
begin: beginTime.value,
|
||||
end: endTime.value
|
||||
})
|
||||
color = ['green', 'blue', 'red']
|
||||
legend.show = true
|
||||
legend.data = ['AAA', 'BBB', 'BBB-AAA']
|
||||
seriesList = [getLineSeries({ name: 'AAA' }), getLineSeries({ name: 'BBB' }), getLineSeries({ name: 'BBB-AAA' })]
|
||||
data.forEach((ele: any) => {
|
||||
sysDate.push(ele.sysDate)
|
||||
seriesList[0].data.push({ value: ele.AaaFredValue })
|
||||
seriesList[1].data.push({ value: ele.BbbFredValue })
|
||||
seriesList[2].data.push({ value: Math.round((ele.BbbFredValue - ele.AaaFredValue) * 100) / 100 })
|
||||
list.value.unshift(ele)
|
||||
})
|
||||
} else{
|
||||
const { data } = await fredDetailQueryFred({
|
||||
fredCode: fredCode.value,
|
||||
begin: beginTime.value,
|
||||
end: endTime.value
|
||||
})
|
||||
color = ['blue']
|
||||
seriesList = [getLineSeries({ name: fredCode.value })]
|
||||
data.forEach((ele: any) => {
|
||||
sysDate.push(ele.sysDate)
|
||||
seriesList[0].data.push({ value: ele.fredValue })
|
||||
list.value.unshift(ele)
|
||||
})
|
||||
}
|
||||
|
||||
if (!destroyedFlag.value) {
|
||||
let chart = echarts.init(document.getElementById(`${fredCode.value}_chart`))
|
||||
const option = getBaseOption({
|
||||
series: seriesList,
|
||||
yAxis: [getYAxis({ axisLabel: '{value} %' })],
|
||||
xAxis: [getXAxis({ data: sysDate })],
|
||||
tooltipFormatter: function (params: any) {
|
||||
const data = Array.isArray(params) ? params : [params]
|
||||
let backText = `日期:${data[0].name}<br/>`
|
||||
data.forEach((ele) => {
|
||||
backText += `<span style="color: ${ele.color}">${ele.seriesName}</span>:<span class="f-b">${ele.value}%</span><br/>`
|
||||
})
|
||||
return backText
|
||||
},
|
||||
grid: [{ top: '10px', left: '40px', right: '40px', bottom: '20px' }],
|
||||
legend: legend,
|
||||
color: color
|
||||
})
|
||||
chart.setOption(option)
|
||||
charts.value[0] = chart
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted (() =>{
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
@import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';
|
||||
.page-div {
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
scroll-behavior: smooth;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
padding-top: 102px;
|
||||
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
|
||||
@media screen and (min-width: 992px) {
|
||||
left: calc(50vw - 496px);
|
||||
right: calc(50vw - 496px);
|
||||
}
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
width: 100%;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.chart-title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.time-range-buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 0 16px;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
height: 30vh;
|
||||
@media screen and (min-width: 768px) {
|
||||
height: 40vh;
|
||||
}
|
||||
}
|
||||
|
||||
.listDiv {
|
||||
padding: 0 16px;
|
||||
|
||||
.data-list-container {
|
||||
width: 100%;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
background-color: #fff;
|
||||
max-height: none !important;
|
||||
overflow: visible !important;
|
||||
height: auto !important;
|
||||
-webkit-overflow-scrolling: auto;
|
||||
|
||||
.list-header {
|
||||
background-color: #f5f7fa;
|
||||
border-bottom: 1px solid #e8ebed;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
border-bottom: 1px solid #e8ebed;
|
||||
height: 40px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.list-body {
|
||||
overflow: visible !important;
|
||||
max-height: none !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.list-col {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding: 0 8px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
|
||||
&.date-col {
|
||||
flex: 0 0 30%;
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&.value-col,
|
||||
&.aaa-col,
|
||||
&.bbb-col,
|
||||
&.spread-col {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
&.spread-col {
|
||||
color: #1989fa;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.no-data {
|
||||
padding: 30px 0;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.chart-header {
|
||||
margin-bottom: 12px;
|
||||
|
||||
.time-range-buttons {
|
||||
padding: 0 12px;
|
||||
|
||||
button {
|
||||
font-size: 12px !important;
|
||||
padding: 4px 8px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
height: 25vh;
|
||||
}
|
||||
|
||||
.listDiv {
|
||||
padding: 0 12px;
|
||||
|
||||
.data-list-container {
|
||||
.list-item {
|
||||
padding: 10px 12px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.list-col {
|
||||
font-size: 12px;
|
||||
padding: 0 4px;
|
||||
|
||||
&.date-col {
|
||||
flex: 0 0 35%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
58
src/views/target/fred/fred-list.vue
Normal file
58
src/views/target/fred/fred-list.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="page-div">
|
||||
<back-button />
|
||||
<div class="header">
|
||||
<h1 class="title">FRED 经济指标</h1>
|
||||
<p class="subtitle">美国联邦储备经济数据</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p class="info-text">
|
||||
FRED (Federal Reserve Economic Data) 提供了美国经济的关键指标数据,帮助您做出更明智的投资决策。
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<van-grid :column-num="1" :gutter="16" :border="false">
|
||||
<van-grid-item v-for="item in list" :key="item.fredCode">
|
||||
<div @touchstart="onTouchStart(item.fredName)" @touchend="onTouchEnd(item.fredName, 'fred-detail', item.id)" class="item"
|
||||
@touchcancel="onTouchCancel(item.fredName)" :class="{ 'tapped': tappedItem === item.fredName }">
|
||||
<div class="card-content" :style="{ backgroundColor: item.bgColor, borderLeftColor: item.color }">
|
||||
<div class="card-code" :style="{ color: item.color }">{{ item.fredCode }}</div>
|
||||
<div class="card-name">{{ item.fredName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-grid-item>
|
||||
</van-grid>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BackButton from '@/components/back-button.vue'
|
||||
|
||||
import { tapMixins } from '@/mixins/tap-mixins'
|
||||
const { tappedItem, onTouchStart, onTouchEnd, onTouchCancel } = tapMixins()
|
||||
|
||||
import { ref, onMounted } from 'vue'
|
||||
let list = ref<any[]>([])
|
||||
import { fredInfoList } from '@/utils/api'
|
||||
import { colorList } from '@/utils/colorList'
|
||||
|
||||
const init = async () => {
|
||||
const { data } = await fredInfoList()
|
||||
list.value = [...[
|
||||
{ fredCode: 'BBB-AAA', fredName: 'Option-Adjusted Spread', id: '' }
|
||||
], ...data]
|
||||
|
||||
list.value.forEach((ele, index) => {
|
||||
ele.color = colorList[index % colorList.length].color
|
||||
ele.bgColor = colorList[index % colorList.length].bgColor
|
||||
})
|
||||
}
|
||||
|
||||
onMounted (() =>{
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
</style>
|
||||
769
src/views/target/fund/fund-detail.vue
Normal file
769
src/views/target/fund/fund-detail.vue
Normal file
@@ -0,0 +1,769 @@
|
||||
<template>
|
||||
<div class="page-div">
|
||||
<back-button />
|
||||
<div class="header">
|
||||
<div class="header-content">
|
||||
<h1 class="title">{{ fundData?.fundName }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="f-b">相关文件</div>
|
||||
<div v-if="fileList.length > 0" @click="showFileList" class="blue-color">
|
||||
查看详情
|
||||
</div>
|
||||
<div v-else class="red-color">暂无数据</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-card" v-if="chartList.length > 0">
|
||||
<div class="chart-container">
|
||||
<van-loading v-show="chartList[0].loading" type="spinner" color="#1989fa" class="loading" />
|
||||
<div :id="`${chartList[0].id}`" class="chartDiv"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-card" v-if="chartList.length > 1">
|
||||
<div class="chart-container">
|
||||
<van-loading v-show="chartList[1].loading" type="spinner" color="#1989fa" class="loading" />
|
||||
<div :id="`${chartList[1].id}`" class="chartDiv"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="listDiv">
|
||||
<div class="data-list-container">
|
||||
<div v-if="tableList.length > 0">
|
||||
<div class="list-header">
|
||||
<div class="list-item">
|
||||
<div class="list-col date-col">日期</div>
|
||||
<div class="list-col value-col">净值</div>
|
||||
<div class="list-col value-col">月涨幅</div>
|
||||
<div class="list-col value-col">今年以来收益</div>
|
||||
</div>
|
||||
</div>
|
||||
<RecycleScroller
|
||||
class="list-body"
|
||||
:items="tableList"
|
||||
:item-size="1"
|
||||
key-field="realSysDate"
|
||||
>
|
||||
<template v-slot="{ item }">
|
||||
<div class="list-item">
|
||||
<div class="list-col date-col" :class="{ 'f-b' : item.boldFlag }">{{ item.realSysDate }}</div>
|
||||
<div class="list-col value-col">
|
||||
<div v-if="item.budget" class="tag-div"><van-tag type="success" plain>预测</van-tag></div>
|
||||
<span :class="{ 'f-b' : item.boldFlag, 'budgetHeight': item.budget }">{{ item.nav }}</span>
|
||||
</div>
|
||||
<div class="list-col value-col">
|
||||
<div v-if="item.budget" class="tag-div"><van-tag type="success" plain>预测</van-tag></div>
|
||||
<span :class="{
|
||||
'f-b' : item.boldFlag,
|
||||
'budgetHeight': item.budget,
|
||||
'red-color': item.navPercent > 0,
|
||||
'green-color' : item.navPercent < 0
|
||||
}">{{ item.navPercent || 0 }}%</span>
|
||||
</div>
|
||||
<div class="list-col value-col">
|
||||
<div v-if="item.budget" class="tag-div"><van-tag type="success" plain>预测</van-tag></div>
|
||||
<span :class="{
|
||||
'f-b' : item.boldFlag,
|
||||
'budgetHeight': item.budget,
|
||||
'red-color': item.navPercent > 0,
|
||||
'green-color' : item.navPercent < 0
|
||||
}">{{ item.yearIncomeRate || 0 }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</RecycleScroller>
|
||||
</div>
|
||||
<div class="no-data" v-else>暂无数据</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<van-popup v-model:show="showFileListPopup" position="bottom" :style="{ height: '70%' }">
|
||||
<div class="p-16">
|
||||
<div class="d-flex justify-content-between items-center mb-16">
|
||||
<div class="sub-title f-b">文件列表</div>
|
||||
<van-icon name="close" @click="showFileListPopup = false" />
|
||||
</div>
|
||||
<div class="file-list">
|
||||
<div v-for="(file, index) in fileList" :key="index" class="file-item">
|
||||
<a :href="file.fileUrl" target="_blank" class="file-link">
|
||||
<i class="iconfont" :class="`icon-${file.type}`"></i>
|
||||
<span class="file-title van-ellipsis">{{ file.fileName }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-popup>
|
||||
</template>
|
||||
<script setup lang='ts'>
|
||||
import BackButton from '@/components/back-button.vue'
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
import { RecycleScroller } from 'vue-virtual-scroller'
|
||||
|
||||
import { useRoute } from 'vue-router'
|
||||
const route = useRoute()
|
||||
import { stockAFundInfo, stockAFundFileList, stockAFundHisFundHisList, stockAFundListFund } from '@/utils/api'
|
||||
const fundData: any = ref(null)
|
||||
const fileList = ref<any[]>([])
|
||||
import { padZeroAfterDecimal } from '@/utils'
|
||||
|
||||
const init = async () => {
|
||||
try {
|
||||
const data: any = await stockAFundInfo(route.params.id + '')
|
||||
fundData.value = data.data
|
||||
const { data: file } = await stockAFundFileList({ fundId: route.params.id + '', curPage: 1, limit: 999 })
|
||||
const reg1 = /(\.xls|\.xlsx)$/
|
||||
const reg2 = /(\.pdf)$/
|
||||
const reg3 = /(\.doc|\.docx)$/
|
||||
const reg4 = /(\.ppt|\.pptx)$/
|
||||
file.list.map((ele: any) => {
|
||||
if (ele.fileUrl) {
|
||||
ele.type = 'lianjie'
|
||||
} else {
|
||||
if (ele.fileId) {
|
||||
if (reg1.test(ele.fileName)) {
|
||||
ele.type = 'excel'
|
||||
}
|
||||
if (reg2.test(ele.fileName)) {
|
||||
ele.type = 'pdf'
|
||||
}
|
||||
if (reg3.test(ele.fileName)) {
|
||||
ele.type = 'word'
|
||||
}
|
||||
if (reg4.test(ele.fileName)) {
|
||||
ele.type = 'ppt'
|
||||
}
|
||||
}
|
||||
ele.fileUrl = `${import.meta.env.VITE_BASE_URL}/file/${ele.fileId}`
|
||||
}
|
||||
})
|
||||
fileList.value = file.list
|
||||
const { data: nav } = await stockAFundHisFundHisList({ fundId: route.params.id + '' })
|
||||
nav.map((ele: any) => {
|
||||
ele.selectId = ele.id
|
||||
ele.nav = ele.nav ? padZeroAfterDecimal(ele.nav) : ''
|
||||
const month = ele.sysDate.slice(5, 7)
|
||||
ele.boldFlag = false
|
||||
if (month === '12') {
|
||||
ele.boldFlag = true
|
||||
}
|
||||
ele.budget = false
|
||||
ele.realSysDate = ele.sysDate
|
||||
ele.holdingList = []
|
||||
})
|
||||
const info2 = data.preData && data.preData.sysDate ? data.preData : null
|
||||
if (info2 && nav.length > 0) {
|
||||
let sysDate = ''
|
||||
let fundId = ''
|
||||
nav.forEach((ele: any) => {
|
||||
if (ele.holdingNum > 0) {
|
||||
sysDate = ele.sysDate
|
||||
fundId = ele.fundId
|
||||
}
|
||||
})
|
||||
const month = info2.sysDate.slice(5, 7)
|
||||
nav.push({
|
||||
sysDate: sysDate,
|
||||
fundId: fundId,
|
||||
realSysDate: info2.sysDate,
|
||||
nav: info2.price ? padZeroAfterDecimal(info2.price) : '',
|
||||
navPercent: info2.percent,
|
||||
yearIncomeRate: info2.yearIncomeRate,
|
||||
holdingNum: info2.holdingList.length,
|
||||
boldFlag: month === '12',
|
||||
budget: true,
|
||||
holdingList: info2.holdingList
|
||||
})
|
||||
}
|
||||
setLineChart(nav, data.fundConfig)
|
||||
} catch (error) {
|
||||
console.error('初始化数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
let chartList = ref<any[]>([])
|
||||
import { chartMixins } from '@/mixins/chart-mixins'
|
||||
const { charts, destroyedFlag } = chartMixins()
|
||||
import * as echarts from 'echarts'
|
||||
import { getLineSeries, getBaseOption, getXAxis, getYAxis } from '@/utils/chart'
|
||||
const tableList = ref<any[]>([])
|
||||
const setLineChart = (nav: any, fundConfig: any) => {
|
||||
if (fundData.value.configFlag === 1 && fundData.value.fundType === 'fund' && fundConfig.length > 0) {
|
||||
charts.value = [null, null]
|
||||
chartList.value = [{
|
||||
id: 'nav_chart',
|
||||
loading: true,
|
||||
}, {
|
||||
id: 'nav_list_chart',
|
||||
loading: true,
|
||||
}]
|
||||
} else {
|
||||
charts.value = [null]
|
||||
chartList.value = [{
|
||||
id: 'nav_chart',
|
||||
loading: true,
|
||||
}]
|
||||
}
|
||||
const list = nav.sort(function (a: any, b: any) {
|
||||
return new Date(a.realSysDate).getTime() - new Date(b.realSysDate).getTime()
|
||||
})
|
||||
const drawdowns: any = []
|
||||
let maxDrawdown = 0
|
||||
const tabledata: any = []
|
||||
for (let j = 0; j < list.length; j++) {
|
||||
tabledata.unshift(list[j])
|
||||
let drawdown = 0
|
||||
const nav = Number(list[j].nav)
|
||||
if (nav > maxDrawdown) {
|
||||
maxDrawdown = nav
|
||||
drawdown = 0
|
||||
} else {
|
||||
drawdown = Math.round((nav - maxDrawdown) / maxDrawdown * 10000) / 100
|
||||
}
|
||||
drawdowns.push(drawdown)
|
||||
}
|
||||
const sysDate: any = []
|
||||
const seriesData: any = []
|
||||
const drawdownsData: any = []
|
||||
list.forEach((ele: any, index: number) => {
|
||||
sysDate.push(ele.realSysDate)
|
||||
seriesData.push({ value: ele.nav, drawdown: drawdowns[index] })
|
||||
drawdownsData.push({ value: drawdowns[index], nav: ele.nav, sysDate: ele.realSysDate })
|
||||
})
|
||||
const series = [
|
||||
getLineSeries({ data: seriesData, name: '历史净值', areaStyle: { color: '#FAE5E3' }, color: '#FF3732' }),
|
||||
getLineSeries({ data: drawdownsData, yAxisIndex: 1, xAxisIndex: 1, name: '回撤走势', areaStyle: { color: '#FAE5E3' }, color: '#FF3732' })
|
||||
]
|
||||
if (drawdownsData.length > 3) {
|
||||
const minTime = new Date(drawdownsData[0].sysDate).getTime()
|
||||
const maxTime = new Date(drawdownsData[drawdownsData.length - 1].sysDate).getTime()
|
||||
const range = Math.round((maxTime - minTime) / 6)
|
||||
const min1 = getPoint(drawdownsData)
|
||||
const left = []
|
||||
const right = []
|
||||
for (let i = 0; i < drawdownsData.length; i++) {
|
||||
if (new Date(min1.sysDate).getTime() - new Date(drawdownsData[i].sysDate).getTime() - range > 0) {
|
||||
left.push(drawdownsData[i])
|
||||
}
|
||||
if (new Date(drawdownsData[i].sysDate).getTime() - range - new Date(min1.sysDate).getTime() > 0) {
|
||||
right.push(drawdownsData[i])
|
||||
}
|
||||
}
|
||||
let min2: any = {}
|
||||
let min3: any = {}
|
||||
if (left.length > 0 && right.length > 0) {
|
||||
min2 = getPoint(left)
|
||||
min3 = getPoint(right)
|
||||
} else if (left.length > 0 && right.length === 0) {
|
||||
min2 = getPoint(left)
|
||||
const left1 = []
|
||||
const right1 = []
|
||||
for (let i = 0; i < left.length; i++) {
|
||||
if (new Date(min2.sysDate).getTime() - new Date(left[i].sysDate).getTime() - range > 0) {
|
||||
left1.push(left[i])
|
||||
}
|
||||
if (new Date(left[i].sysDate).getTime() - range - new Date(min2.sysDate).getTime() > 0) {
|
||||
right1.push(left[i])
|
||||
}
|
||||
}
|
||||
min3 = getPoint([...left1, ...right1])
|
||||
} else if (left.length === 0 && right.length > 0) {
|
||||
min2 = getPoint(right)
|
||||
const left2 = []
|
||||
const right2 = []
|
||||
for (let i = 0; i < right.length; i++) {
|
||||
if (new Date(min2.sysDate).getTime() - new Date(right[i].sysDate).getTime() - range > 0) {
|
||||
left2.push(right[i])
|
||||
}
|
||||
if (new Date(right[i].sysDate).getTime() - range - new Date(min2.sysDate).getTime() > 0) {
|
||||
right2.push(right[i])
|
||||
}
|
||||
}
|
||||
min3 = getPoint([...left2, ...right2])
|
||||
}
|
||||
series[1].markPoint = {
|
||||
data: [{
|
||||
name: 1,
|
||||
value: min1.num,
|
||||
coord: [min1.sysDate, min1.num],
|
||||
itemStyle: {
|
||||
color: 'rgba(0,0,0,0)'
|
||||
},
|
||||
label: {
|
||||
position: 'bottom',
|
||||
color: '#f58220'
|
||||
}
|
||||
}, {
|
||||
name: 2,
|
||||
value: min2.num,
|
||||
coord: [min2.sysDate, min2.num],
|
||||
itemStyle: {
|
||||
color: 'rgba(0,0,0,0)'
|
||||
},
|
||||
label: {
|
||||
position: 'bottom',
|
||||
color: '#0000ff'
|
||||
}
|
||||
}, {
|
||||
name: 3,
|
||||
value: min3.num,
|
||||
coord: [min3.sysDate, min3.num],
|
||||
itemStyle: {
|
||||
color: 'rgba(0,0,0,0)'
|
||||
},
|
||||
label: {
|
||||
position: 'bottom',
|
||||
color: '#65c294'
|
||||
}
|
||||
}]
|
||||
}
|
||||
} else {
|
||||
series[1].markPoint = {
|
||||
data: drawdownsData.map((ele: any, i: number) => ({
|
||||
name: `${i + 1}`,
|
||||
value: ele.value,
|
||||
coord: [sysDate[i], ele.value],
|
||||
itemStyle: {
|
||||
color: 'rgba(0,0,0,0)'
|
||||
},
|
||||
label: {
|
||||
position: 'bottom',
|
||||
color: i === 0 ? '#f58220' : i === 1 ? '#0000ff' : '#65c294'
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
const option = getBaseOption({
|
||||
yAxis: [getYAxis({ }), getYAxis({ gridIndex: 1, axisLabel: '{value}%' })],
|
||||
xAxis: [getXAxis({ data: sysDate }), getXAxis({ data: sysDate, gridIndex: 1, showFlag: false })],
|
||||
series: series,
|
||||
title: [{ text: '历史净值' }, { text: '回撤情况', top: '54%' }],
|
||||
grid: [{ bottom: '54%', left: '40px', right: '40px', top: '40px' }, { top: '66%', bottom: '20px', left: '40px', right: '40px' }],
|
||||
formatter: function (data: any) {
|
||||
let res = `${data[0].name}<br/>`
|
||||
data.forEach((ele: any) => {
|
||||
res += `历史净值:${ele.seriesName === '历史净值' ? ele.data.value : ele.data.nav}<br/>`
|
||||
res += `回撤:${ele.seriesName === '历史净值' ? ele.data.drawdown : ele.data.value}%<br/>`
|
||||
})
|
||||
return res
|
||||
}
|
||||
})
|
||||
nextTick(() =>{
|
||||
if (!destroyedFlag.value && document.getElementById(chartList.value[0].id)) {
|
||||
const chart = echarts.init(document.getElementById(chartList.value[0].id))
|
||||
chart.setOption(option)
|
||||
charts.value[0] = chart
|
||||
chartList.value[0].loading = false
|
||||
}
|
||||
if (fundData.value.configFlag === 1 && fundData.value.fundType === 'fund' && fundConfig.length > 0) {
|
||||
getFundList(fundConfig)
|
||||
tableList.value = tabledata
|
||||
} else {
|
||||
tableList.value = tabledata
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getPoint = (drawdownsData: any) => {
|
||||
const minValue = {
|
||||
num: drawdownsData[0].value,
|
||||
sysDate: drawdownsData[0].sysDate
|
||||
}
|
||||
for (let i = 1; i < drawdownsData.length; i++) {
|
||||
if (drawdownsData[i].value < minValue.num) {
|
||||
minValue.num = drawdownsData[i].value
|
||||
minValue.sysDate = drawdownsData[i].sysDate
|
||||
}
|
||||
}
|
||||
return minValue
|
||||
}
|
||||
|
||||
const getFundList = async(fundConfig: any) => {
|
||||
const { data } = await stockAFundListFund()
|
||||
const multipleSelection: any = []
|
||||
data.fundList.forEach((ele: any) => {
|
||||
const fund = fundConfig.find((res: any) => {
|
||||
return ele.fundId === res.configFundId
|
||||
})
|
||||
if (fund && fund.sysDate && fund.weight) {
|
||||
multipleSelection.push({
|
||||
weight: fund.weight,
|
||||
sysDate: fund.sysDate,
|
||||
stockPriceList: ele.stockPriceList,
|
||||
fundName: ele.fundName,
|
||||
fundId: ele.fundId
|
||||
})
|
||||
}
|
||||
})
|
||||
getChart(multipleSelection)
|
||||
}
|
||||
|
||||
const getChart = (multipleSelection: any) => {
|
||||
const colorPalette = generateColors(multipleSelection.length + 1)
|
||||
const legend: any = []
|
||||
const seriesList: any = []
|
||||
const sysDate: any = []
|
||||
multipleSelection.sort((a: any, b: any) => new Date(a.sysDate).getTime() - new Date(b.sysDate).getTime())
|
||||
multipleSelection.forEach((ele: any, index: number) => {
|
||||
legend.push(ele.fundName)
|
||||
seriesList.push(getLineSeries({ name: ele.fundName, color: colorPalette[index] }))
|
||||
seriesList.push(getLineSeries({ name: ele.fundName, yAxisIndex: 1, xAxisIndex: 1, color: colorPalette[index] }))
|
||||
const priceIndex = ele.stockPriceList.findIndex((res: any) => {
|
||||
return new Date(res.sysDate).getTime() >= new Date(ele.sysDate).getTime()
|
||||
})
|
||||
const stockPriceList = ele.stockPriceList.slice(priceIndex)
|
||||
const per = stockPriceList.length > 0 ? stockPriceList[0].nav : 1
|
||||
let maxDrawdown = 0
|
||||
stockPriceList.forEach((res: any) => {
|
||||
if (!sysDate.includes(res.sysDate)) {
|
||||
sysDate.push(res.sysDate)
|
||||
}
|
||||
const nav = res.nav ? Math.round(res.nav / per * 10000) / 10000 : 0
|
||||
let drawdown = 0
|
||||
if (nav > maxDrawdown) {
|
||||
maxDrawdown = nav
|
||||
drawdown = 0
|
||||
} else {
|
||||
drawdown = Math.round((nav - maxDrawdown) / maxDrawdown * 10000) / 100
|
||||
}
|
||||
seriesList[index * 2].data.push([res.sysDate, res.nav, drawdown])
|
||||
seriesList[index * 2 + 1].data.push([res.sysDate, drawdown, res.nav])
|
||||
})
|
||||
})
|
||||
sysDate.sort((a: any, b: any) => new Date(a).getTime() - new Date(b).getTime())
|
||||
const moneyList: any = []
|
||||
const navList: any = []
|
||||
sysDate.forEach((ele: any) => {
|
||||
const countList: any = []
|
||||
seriesList.forEach((res: any, index: number) => {
|
||||
if (index % 2 === 0) {
|
||||
if (new Date(multipleSelection[index / 2].sysDate).getTime() <= new Date(ele).getTime()) {
|
||||
const navItem = res.data.find((item: any) => {
|
||||
return item[0] === ele
|
||||
})
|
||||
if (navItem) {
|
||||
countList.push({
|
||||
weight: Number(multipleSelection[index / 2].weight),
|
||||
nav: navItem[1],
|
||||
marketValue: 0,
|
||||
fundId: multipleSelection[index / 2].fundId
|
||||
})
|
||||
} else {
|
||||
countList.push({
|
||||
weight: Number(multipleSelection[index / 2].weight),
|
||||
nav: 0,
|
||||
marketValue: 0,
|
||||
fundId: multipleSelection[index / 2].fundId
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
let weights = 0
|
||||
countList.forEach((ele: any) => {
|
||||
weights += ele.weight
|
||||
})
|
||||
let marketValue = 0
|
||||
if (moneyList.length === 0) {
|
||||
countList.map((res: any) => {
|
||||
res.marketValue = Math.round(Number(fundData.value.initAmount) * res.weight / weights * 100) / 100
|
||||
marketValue += res.marketValue
|
||||
})
|
||||
} else {
|
||||
if (countList.length > moneyList[moneyList.length - 1].length) {
|
||||
countList.forEach((res: any, index: number) => {
|
||||
if (index < moneyList[moneyList.length - 1].length) {
|
||||
if (res.nav) {
|
||||
marketValue += Math.round(moneyList[moneyList.length - 1][index].marketValue / moneyList[moneyList.length - 1][index].nav * res.nav * 100) / 100
|
||||
} else {
|
||||
marketValue += moneyList[moneyList.length - 1][index].marketValue
|
||||
}
|
||||
}
|
||||
})
|
||||
countList.map((res: any, index: number) => {
|
||||
if (index >= moneyList[moneyList.length - 1].length) {
|
||||
res.marketValue = Math.round(marketValue * res.weight / weights * 100) / 100
|
||||
} else {
|
||||
res.marketValue = Math.round(marketValue * res.weight / weights * 100) / 100
|
||||
}
|
||||
})
|
||||
} else {
|
||||
countList.map((res: any, index: number) => {
|
||||
if (res.nav) {
|
||||
res.marketValue = Math.round(moneyList[moneyList.length - 1][index].marketValue / moneyList[moneyList.length - 1][index].nav * res.nav * 100) / 100
|
||||
} else {
|
||||
res.marketValue = moneyList[moneyList.length - 1][index].marketValue
|
||||
}
|
||||
marketValue += res.marketValue
|
||||
})
|
||||
}
|
||||
}
|
||||
moneyList.push(countList)
|
||||
navList.push({ sysDate: sysDate[0], nav: Math.round(marketValue / Number(fundData.value.initAmount) * 10000) / 10000 })
|
||||
})
|
||||
legend.push('本基金')
|
||||
seriesList.push(getLineSeries({ name: '本基金', color: colorPalette[colorPalette.length - 1] }))
|
||||
seriesList.push(getLineSeries({ name: '本基金', yAxisIndex: 1, xAxisIndex: 1, color: colorPalette[colorPalette.length - 1] }))
|
||||
let maxDrawdown = 0
|
||||
navList.forEach((ele: any) => {
|
||||
let drawdown = 0
|
||||
const nav = Number(ele.nav)
|
||||
if (nav > maxDrawdown) {
|
||||
maxDrawdown = nav
|
||||
drawdown = 0
|
||||
} else {
|
||||
drawdown = Math.round((nav - maxDrawdown) / maxDrawdown * 10000) / 100
|
||||
}
|
||||
seriesList[seriesList.length - 2].data.push([ele.sysDate, nav, drawdown])
|
||||
seriesList[seriesList.length - 1].data.push([ele.sysDate, drawdown, nav])
|
||||
})
|
||||
const option = getBaseOption({
|
||||
yAxis: [getYAxis({ }), getYAxis({ gridIndex: 1, axisLabel: '{value}%' })],
|
||||
xAxis: [getXAxis({ data: sysDate }), getXAxis({ data: sysDate, gridIndex: 1, showFlag: false })],
|
||||
series: seriesList,
|
||||
formatter: function (data: any) {
|
||||
let res = `${data[0].value[0]}<br/>`
|
||||
data.forEach((ele: any) => {
|
||||
res += `<span style="color: ${ele.color}">${ele.seriesName}:历史净值${ele.axisIndex % 2 === 0 ? ele.value[1] : ele.value[2]},回撤${ele.axisIndex % 2 === 0 ? ele.value[2] : ele.value[1]}%</span><br/>`
|
||||
})
|
||||
return res
|
||||
},
|
||||
legend: { data: legend, top: '40px' },
|
||||
title: [{ text: '历史净值' }, { text: '回撤情况', top: '54%' }],
|
||||
grid: [{ bottom: '54%', left: '40px', right: '40px', top: '90px' }, { top: '66%', bottom: '20px', left: '40px', right: '40px' }],
|
||||
color: colorPalette
|
||||
})
|
||||
if (!destroyedFlag.value && document.getElementById(chartList.value[1].id)) {
|
||||
const chart = echarts.init(document.getElementById(chartList.value[1].id))
|
||||
chart.setOption(option)
|
||||
charts.value[1] = chart
|
||||
chartList.value[1].loading = false
|
||||
}
|
||||
}
|
||||
|
||||
const generateColors = (numLines: number) => {
|
||||
const colors = []
|
||||
const m = numLines - 1
|
||||
if (m > 0) {
|
||||
const startHue = 30
|
||||
const endHue = 330
|
||||
const step = m === 1 ? 0 : (endHue - startHue) / (m - 1)
|
||||
for (let i = 0; i < m; i++) {
|
||||
const h = startHue + (step * i)
|
||||
const s = 70
|
||||
const l = 70
|
||||
colors.push(hslToHex(h, s, l))
|
||||
}
|
||||
}
|
||||
colors.push('#ff0000')
|
||||
return colors
|
||||
}
|
||||
|
||||
const hslToHex = (h : number, s: number, l: number) => {
|
||||
h /= 360
|
||||
s /= 100
|
||||
l /= 100
|
||||
const c = (1 - Math.abs(2 * l - 1)) * s
|
||||
const x = c * (1 - Math.abs((h * 6) % 2 - 1))
|
||||
const m = l - c / 2
|
||||
let r, g, b
|
||||
const i = Math.floor(h * 6)
|
||||
switch (i) {
|
||||
case 0: r = c; g = x; b = 0; break
|
||||
case 1: r = x; g = c; b = 0; break
|
||||
case 2: r = 0; g = c; b = x; break
|
||||
case 3: r = 0; g = x; b = c; break
|
||||
case 4: r = x; g = 0; b = c; break
|
||||
default: r = c; g = 0; b = x; break
|
||||
}
|
||||
r = Math.round((r + m) * 255)
|
||||
g = Math.round((g + m) * 255)
|
||||
b = Math.round((b + m) * 255)
|
||||
return '#' + [r, g, b].map(v => v.toString(16).padStart(2, '0')).join('')
|
||||
}
|
||||
|
||||
const showFileListPopup = ref(false)
|
||||
const showFileList = () => {
|
||||
showFileListPopup.value = true
|
||||
}
|
||||
onMounted (() =>{
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
@import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';
|
||||
.page-div {
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
scroll-behavior: smooth;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
padding-top: 86px;
|
||||
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
|
||||
@media screen and (min-width: 992px) {
|
||||
left: calc(50vw - 496px);
|
||||
right: calc(50vw - 496px);
|
||||
}
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
height: 40vh;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
height: 60vh;
|
||||
}
|
||||
}
|
||||
|
||||
.listDiv {
|
||||
padding: 0 16px;
|
||||
|
||||
.data-list-container {
|
||||
width: 100%;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
background-color: #fff;
|
||||
max-height: none !important;
|
||||
overflow: visible !important;
|
||||
height: auto !important;
|
||||
-webkit-overflow-scrolling: auto;
|
||||
|
||||
.list-header {
|
||||
background-color: #f5f7fa;
|
||||
border-bottom: 1px solid #e8ebed;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #e8ebed;
|
||||
height: 48px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.list-body {
|
||||
overflow: visible !important;
|
||||
max-height: none !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.list-col {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding: 0 8px;
|
||||
font-size: 14px;
|
||||
position: relative;
|
||||
color: #333;
|
||||
|
||||
&.date-col {
|
||||
flex: 0 0 30%;
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.budgetHeight {
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
}
|
||||
|
||||
.tag-div {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.no-data {
|
||||
padding: 30px 0;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
|
||||
.listDiv {
|
||||
padding: 0 12px;
|
||||
|
||||
.data-list-container {
|
||||
.list-item {
|
||||
padding: 10px 12px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.list-col {
|
||||
font-size: 12px;
|
||||
padding: 0 4px;
|
||||
color: #333;
|
||||
|
||||
&.date-col {
|
||||
flex: 0 0 25%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.file-list {
|
||||
max-height: calc(100% - 60px);
|
||||
overflow-y: auto;
|
||||
padding-bottom: 20px;
|
||||
|
||||
.file-item {
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
}
|
||||
|
||||
.file-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
|
||||
i {
|
||||
font-size: 20px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.file-title {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
padding: 0 16px 20px;
|
||||
|
||||
.file-item {
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.file-link {
|
||||
i {
|
||||
font-size: 24px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.file-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
260
src/views/target/fund/fund-list.vue
Normal file
260
src/views/target/fund/fund-list.vue
Normal file
@@ -0,0 +1,260 @@
|
||||
<template>
|
||||
<div class="page-div">
|
||||
<back-button />
|
||||
<div class="header">
|
||||
<h1 class="title">基金列表</h1>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="tab-container">
|
||||
<button v-for="tab in tabList" :key="tab.dicKey" @click="activeTab = tab.dicKey"
|
||||
:class="{ 'active': activeTab === tab.dicKey }" class="tab-button">
|
||||
{{ tab.dicValue }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<van-grid :column-num="1" :gutter="16" :border="false">
|
||||
<van-grid-item v-for="item in fundList" :key="item.indexId">
|
||||
<div @touchstart="onTouchStart(item.fundName)" @touchend="onTouchEnd(item.fundName, 'fund-detail', item.fundId)" class="item"
|
||||
@touchcancel="onTouchCancel(item.fundName)" :class="{ 'tapped': tappedItem === item.fundName }">
|
||||
<div class="card-content" :style="{ backgroundColor: item.bgColor, borderLeftColor: item.color }">
|
||||
<div class="card-code" :style="{ color: item.color }">{{ item.fundName }}</div>
|
||||
<div class="d-flex">
|
||||
<div class="leftValue d-flex justify-center align-center pe-16">
|
||||
<div class="text-center">
|
||||
<div class="blue-color cagr">{{ item.cagr ? item.cagr + '%' : '暂无' }}</div>
|
||||
<div class="tip">复合年增长率</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rightChart flex-1 d-flex justify-center align-center">
|
||||
<van-loading v-show="item.loading" type="spinner" color="#1989fa" class="loading" />
|
||||
<div style="width: 100%;">
|
||||
<div class="chart-div" :id="item.fundId + ''"></div>
|
||||
<div class="d-flex justify-between">
|
||||
<div class="tip">{{ item.beginDay }}</div>
|
||||
<div class="tip">{{ item.endDay }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-grid-item>
|
||||
</van-grid>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BackButton from '@/components/back-button.vue'
|
||||
|
||||
import { tapMixins } from '@/mixins/tap-mixins'
|
||||
const { tappedItem, onTouchStart, onTouchEnd, onTouchCancel } = tapMixins()
|
||||
|
||||
import { ref, onMounted, watch, nextTick } from 'vue'
|
||||
let tabList = ref<any[]>([])
|
||||
import { sysDicListByType, stockAFundList, stockAFundInfo, stockAFundHisFundHisChart } from '@/utils/api'
|
||||
|
||||
const activeTab = ref('')
|
||||
const init = async () => {
|
||||
const { data } = await sysDicListByType({ dicType: 'fund_type' })
|
||||
tabList.value = data
|
||||
activeTab.value = data[0].dicKey
|
||||
}
|
||||
|
||||
watch(activeTab, () => {
|
||||
disposeAll()
|
||||
charts.value = []
|
||||
fundList.value = []
|
||||
handleTabChange()
|
||||
})
|
||||
|
||||
import { colorList } from '@/utils/colorList'
|
||||
let fundList = ref<any[]>([])
|
||||
const handleTabChange = async () => {
|
||||
const { data } = await stockAFundList({ curPage: 1, limit: 999, fundType: activeTab.value })
|
||||
data.list.map((ele: any, index: number) => {
|
||||
ele.loading = true
|
||||
const colorIndex = index % colorList.length
|
||||
ele.color = colorList[colorIndex].color
|
||||
ele.bgColor = colorList[colorIndex].bgColor
|
||||
ele.cagr = ''
|
||||
ele.beginDay = ''
|
||||
ele.endDay = ''
|
||||
})
|
||||
charts.value = new Array(data.list.length).fill(null)
|
||||
fundList.value = data.list
|
||||
nextTick(() =>{
|
||||
setData()
|
||||
})
|
||||
}
|
||||
|
||||
import { chartMixins } from '@/mixins/chart-mixins'
|
||||
const { charts, destroyedFlag, disposeAll } = chartMixins()
|
||||
import * as echarts from 'echarts'
|
||||
import { getLineSeries, getBaseOption, getXAxis, getYAxis } from '@/utils/chart'
|
||||
const setData = async() => {
|
||||
for (let i = 0 ; i < fundList.value.length ; i++) {
|
||||
if (fundList.value && fundList.value.length > i && !fundList.value[i].cagr) {
|
||||
const { data: info } = await stockAFundInfo(fundList.value[i].fundId)
|
||||
if (info && fundList.value[i]) {
|
||||
fundList.value[i].cagr = info.cagr
|
||||
}
|
||||
}
|
||||
if (fundList.value && fundList.value.length > i && fundList.value[i].loading) {
|
||||
const { data: chartData } = await stockAFundHisFundHisChart({ fundId: fundList.value[i].fundId })
|
||||
const sysDate: any = []
|
||||
const seriesData: any = []
|
||||
chartData.forEach((ele: any) => {
|
||||
sysDate.push(ele.sysDate)
|
||||
seriesData.push({ value: ele.nav })
|
||||
})
|
||||
const markPoint = []
|
||||
if (seriesData.length > 0) {
|
||||
if (fundList.value && fundList.value.length > i) {
|
||||
fundList.value[i].beginDay = sysDate[0]
|
||||
fundList.value[i].endDay = sysDate[sysDate.length - 1]
|
||||
}
|
||||
markPoint.push({
|
||||
symbolSize: 30,
|
||||
coord: [0, seriesData[0].value],
|
||||
label: {
|
||||
formatter: function () {
|
||||
return seriesData[0].value
|
||||
},
|
||||
color: '#ff0000',
|
||||
},
|
||||
itemStyle: {
|
||||
color: 'rgba(0,0,0,0)'
|
||||
}
|
||||
})
|
||||
markPoint.push({
|
||||
symbolSize: 30,
|
||||
coord: [sysDate.length - 1, seriesData[seriesData.length - 1].value],
|
||||
label: {
|
||||
formatter: function () {
|
||||
return seriesData[seriesData.length - 1].value
|
||||
},
|
||||
color: '#ff0000',
|
||||
},
|
||||
itemStyle: {
|
||||
color: 'rgba(0,0,0,0)'
|
||||
}
|
||||
})
|
||||
}
|
||||
const series = [
|
||||
getLineSeries({ data: seriesData, name: '历史净值', markPoint: { data : markPoint } }),
|
||||
]
|
||||
const option = getBaseOption({
|
||||
yAxis: [getYAxis({ showFlag: false })],
|
||||
xAxis: [getXAxis({ data: sysDate, showFlag: false })],
|
||||
series: series,
|
||||
grid: [{ top: '20px', bottom: '1px', left: '20px', right: '20px' }]
|
||||
})
|
||||
if (!destroyedFlag.value && fundList.value && fundList.value.length > i && document.getElementById(fundList.value[i].fundId)) {
|
||||
if (charts.value[i]) {
|
||||
charts.value[i]?.dispose()
|
||||
charts.value[i] = null
|
||||
}
|
||||
const chart = echarts.init(document.getElementById(fundList.value[i].fundId))
|
||||
chart.setOption(option)
|
||||
charts.value[i] = chart
|
||||
fundList.value[i].loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
import { useRoute } from 'vue-router'
|
||||
const route = useRoute()
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
(newPath, oldPath) => {
|
||||
if (oldPath?.includes('target-table')) {
|
||||
nextTick(() => {
|
||||
handleTabChange()
|
||||
})
|
||||
} else if (oldPath?.includes('fund-detail')) {
|
||||
nextTick(() => {
|
||||
setData()
|
||||
})
|
||||
}
|
||||
if (newPath?.includes('target-table')) {
|
||||
disposeAll()
|
||||
charts.value = []
|
||||
fundList.value = []
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
onMounted (() =>{
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.card-code {
|
||||
margin-bottom: 16px !important;
|
||||
}
|
||||
.leftValue {
|
||||
width: 96px;
|
||||
height: 100px;
|
||||
|
||||
.cagr {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.tip {
|
||||
color: #666;
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.rightChart {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
position: relative;
|
||||
|
||||
.loading {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.chart-div {
|
||||
height: 80px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.grid {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.card-code {
|
||||
margin-bottom: 24px !important;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.leftValue {
|
||||
width: 240px;
|
||||
.cagr {
|
||||
font-size: 24px;
|
||||
}
|
||||
.tip {
|
||||
margin-top: 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-div {
|
||||
height: 80px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
356
src/views/target/group/group-detail.vue
Normal file
356
src/views/target/group/group-detail.vue
Normal file
@@ -0,0 +1,356 @@
|
||||
<template>
|
||||
<div class="page-div">
|
||||
<back-button />
|
||||
<div class="header">
|
||||
<div class="header-content">
|
||||
<h1 class="title">{{ groupData?.stockGroupName }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-card financial-data">
|
||||
<div class="data-item">
|
||||
<div class="data-label">总资产</div>
|
||||
<div class="data-value">{{ groupData?.totalAmount }}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">总盈亏</div>
|
||||
<div class="data-value">
|
||||
<span :class="groupData?.totalIncome >= 0 ? 'red-color' : 'green-color'">{{ groupData?.totalIncome }}</span>
|
||||
<span :class="groupData?.totalIncomeRate >= 0 ? 'red-color' : 'green-color'">({{ groupData?.totalIncomeRate }}%)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">当日盈亏</div>
|
||||
<div class="data-value">
|
||||
<span :class="groupData?.todayIncome >= 0 ? 'red-color' : 'green-color'">{{ groupData?.todayIncome }}</span>
|
||||
<span :class="groupData?.todayIncomeRate >= 0 ? 'red-color' : 'green-color'">({{ groupData?.todayIncomeRate }}%)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">总市值</div>
|
||||
<div class="data-value">{{ groupData?.hkTotalMarket }}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">可用</div>
|
||||
<div class="data-value">{{ groupData?.totalCash }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-card" v-if="chartList.length > 0">
|
||||
<div class="chart-container">
|
||||
<van-loading v-show="chartList[0].loading" type="spinner" color="#1989fa" class="loading" />
|
||||
<div :id="`${chartList[0].id}`" class="chartDiv"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="tableDiv">
|
||||
<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="td--column--1">股票/市值</th>
|
||||
<th>盈亏</th>
|
||||
<th>持仓</th>
|
||||
<th>成本/现价</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(hold, index) in holdList" :key="index" @click="showTradingRecord(hold)">
|
||||
<td class="td--column--1">
|
||||
<div>{{ hold.stockName }}</div>
|
||||
<div>{{ hold.hkStockMarket }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div>{{ hold.income }}</div>
|
||||
<div>{{ hold.incomeRate }}%</div>
|
||||
</td>
|
||||
<td>
|
||||
{{ hold.shares }}<br/>
|
||||
</td>
|
||||
<td>
|
||||
<div>{{ hold.costPrice }}</div>
|
||||
<div>{{ hold.hkStockPrice }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<van-popup v-model:show="showTradingRecordPopup" position="bottom" :style="{ height: '70%' }">
|
||||
<div class="p-16">
|
||||
<div class="d-flex justify-content-between items-center mb-16">
|
||||
<div class="sub-title f-b">{{ stockCode }}【{{ stockName }}】交易记录</div>
|
||||
<van-icon name="close" @click="showTradingRecordPopup = false" />
|
||||
</div>
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad"
|
||||
:immediate-check="false">
|
||||
<van-cell v-for="(item, index) in tradeList" :key="index" :class="['trade-item', `trade-type-${item.tradeType}`]">
|
||||
<div class="trade-header d-flex justify-content-between items-center f-b">
|
||||
<div class="trade-date">{{ item.tradeDate }}</div>
|
||||
<div class="trade-type-label f-b">{{ item.tradeType === 'dividend' ? '分红' : item.tradeType === 'cash' ? '派息' : item.tradeType === 'buy' ? '买入' : '卖出' }}</div>
|
||||
</div>
|
||||
<div class="trade-content">
|
||||
<div v-if="item.tradeType === 'dividend'" class="dividend-content">
|
||||
<div class="trade-amount">送股数量: {{ item.sharesChange }}</div>
|
||||
</div>
|
||||
<div v-else-if="item.tradeType === 'cash'" class="cash-content">
|
||||
<div class="trade-amount">每股派息: {{ item.dps }}{{ item.dividendUnit }}</div>
|
||||
</div>
|
||||
<div v-else-if="item.tradeType === 'buy' || item.tradeType === 'sell'" class="trade-content-main">
|
||||
<div class="trade-stock-info">
|
||||
<span class="stock-price">价格: {{ item.stockPrice }}</span>
|
||||
</div>
|
||||
<div class="trade-shares">数量: {{ item.sharesChange }}股</div>
|
||||
<div class="trade-market">金额: {{ item.tradeMarket }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-cell>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
</div>
|
||||
</van-popup>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang='ts'>
|
||||
import BackButton from '@/components/back-button.vue'
|
||||
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
import { useRoute } from 'vue-router'
|
||||
const route = useRoute()
|
||||
import { groupInfo, groupHisChartIncomeRate, groupHoldList, groupTradeList } from '@/utils/api'
|
||||
const groupData: any = ref(null)
|
||||
import { formatNumber } from '@/utils'
|
||||
|
||||
import * as echarts from 'echarts'
|
||||
import { getLineSeries, getBaseOption, getXAxis, getYAxis } from '@/utils/chart'
|
||||
const init = async () => {
|
||||
try {
|
||||
const {data } = await groupInfo(route.params.id + '')
|
||||
groupData.value = {
|
||||
stockGroupName: data.stockGroup && data.stockGroup.stockGroupName ? data.stockGroup.stockGroupName : '',
|
||||
totalAmount: data.holdInfo && data.holdInfo.totalAmount ? formatNumber(data.holdInfo.totalAmount) : '',
|
||||
totalIncome: data.holdInfo && data.holdInfo.totalIncome ? formatNumber(data.holdInfo.totalIncome) : '',
|
||||
totalIncomeRate: data.holdInfo && data.holdInfo.totalIncomeRate ? data.holdInfo.totalIncomeRate : '',
|
||||
todayIncome: data.holdInfo && data.holdInfo.todayIncome ? formatNumber(data.holdInfo.todayIncome) : '',
|
||||
todayIncomeRate: data.holdInfo && data.holdInfo.todayIncomeRate ? data.holdInfo.todayIncomeRate : '',
|
||||
hkTotalMarket: data.holdInfo && data.holdInfo.hkTotalMarket ? formatNumber(data.holdInfo.hkTotalMarket) : '',
|
||||
totalCash: data.holdInfo && data.holdInfo.totalCash ? formatNumber(data.holdInfo.totalCash) : ''
|
||||
}
|
||||
getChartData()
|
||||
getTableData()
|
||||
} catch (error) {
|
||||
console.error('初始化数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const getChartData = async () => {
|
||||
const { data } = await groupHisChartIncomeRate({ groupId: route.params.id + '' })
|
||||
const sysDate: any = []
|
||||
const seriesData: any = []
|
||||
data.forEach((ele: any) => {
|
||||
sysDate.push(ele.sysDate)
|
||||
seriesData.push({ value: ele.nav })
|
||||
})
|
||||
const option = getBaseOption({
|
||||
yAxis: [getYAxis({ })],
|
||||
xAxis: [getXAxis({ data: sysDate })],
|
||||
series: [getLineSeries({ data: seriesData, name: '累计收益率' })],
|
||||
grid: [{ top: '20px', bottom: '20px', left: '40px', right: '40px' }]
|
||||
})
|
||||
if (!destroyedFlag.value && document.getElementById(chartList.value[0].id)) {
|
||||
let chart = echarts.init(document.getElementById(`${chartList.value[0].id}`))
|
||||
chart.setOption(option)
|
||||
charts.value[0] = chart
|
||||
chartList.value[0].loading = false
|
||||
}
|
||||
}
|
||||
|
||||
const holdList = ref<any[]>([])
|
||||
const getTableData = async () => {
|
||||
const { data } = await groupHoldList({ curPage: 1, limit: 999, groupId: route.params.id + '' })
|
||||
let hkStockMarket = 0
|
||||
let shares = 0
|
||||
let incomeAll = 0
|
||||
let holdCostALL = 0
|
||||
data.list.map((ele: any) => {
|
||||
ele.stockPrice = formatNumber(ele.stockPrice)
|
||||
if (ele.stockCode !== 'CASH-HKD') {
|
||||
const income = Math.round((ele.hkStockMarket - ele.holdCost) * 100) / 100
|
||||
incomeAll += income
|
||||
holdCostALL += ele.holdCost
|
||||
ele.incomeRate = ele.holdCost ? Math.round(income / ele.holdCost * 10000) / 100 : 0
|
||||
ele.income = formatNumber(income)
|
||||
hkStockMarket += ele.hkStockMarket
|
||||
shares += ele.shares
|
||||
}
|
||||
ele.hkStockMarket = formatNumber(ele.hkStockMarket)
|
||||
ele.shares = formatNumber(ele.shares)
|
||||
})
|
||||
incomeAll = Math.round(incomeAll * 100) / 100
|
||||
data.list.push({
|
||||
stockName: '总计',
|
||||
hkStockMarket: formatNumber(Math.round(hkStockMarket * 100) / 100),
|
||||
shares: formatNumber(Math.round(shares * 100) / 100),
|
||||
income: formatNumber(incomeAll),
|
||||
incomeRate: holdCostALL ? Math.round(incomeAll / holdCostALL * 10000) / 100 : 0
|
||||
})
|
||||
holdList.value = data.list
|
||||
}
|
||||
|
||||
|
||||
const showTradingRecordPopup = ref(false)
|
||||
const stockCode = ref('')
|
||||
const stockName = ref('')
|
||||
const showTradingRecord = (item: any) => {
|
||||
showTradingRecordPopup.value = true
|
||||
stockCode.value = item.stockCode
|
||||
stockName.value = item.stockName
|
||||
onRefresh()
|
||||
}
|
||||
|
||||
const curPage = ref(1)
|
||||
const tradeList = ref<any[]>([])
|
||||
const onRefresh = () => {
|
||||
curPage.value = 1
|
||||
tradeList.value = []
|
||||
onLoad()
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
const totalCount = ref(0)
|
||||
const refreshing = ref(false)
|
||||
const finished = ref(false)
|
||||
const onLoad = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const { data } = await groupTradeList({ groupId: route.params.id + '', stockCode: stockCode.value, curPage: curPage.value, limit: 20 })
|
||||
totalCount.value = data.totalCount
|
||||
if (refreshing.value) {
|
||||
tradeList.value = data.list
|
||||
refreshing.value = false
|
||||
} else {
|
||||
tradeList.value = tradeList.value.concat(data.list)
|
||||
}
|
||||
if (tradeList.value.length >= data.totalCount) {
|
||||
finished.value = true
|
||||
} else {
|
||||
curPage.value++
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
import { chartMixins } from '@/mixins/chart-mixins'
|
||||
const { charts, destroyedFlag } = chartMixins()
|
||||
|
||||
let chartList = ref([{ id: 'group_chart', loading: true }])
|
||||
onMounted (() =>{
|
||||
charts.value = [null]
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.page-div {
|
||||
padding-top: 86px;
|
||||
height: auto;
|
||||
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
|
||||
@media screen and (min-width: 992px) {
|
||||
left: calc(50vw - 496px);
|
||||
right: calc(50vw - 496px);
|
||||
}
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
height: 26vh;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
height: calc(50vh - 226px);
|
||||
}
|
||||
}
|
||||
.tableDiv {
|
||||
th {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.firstTd {
|
||||
min-width: 80px;
|
||||
max-width: 120px;
|
||||
width: auto;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
min-width: 150px;
|
||||
max-width: 400px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.trade-item {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.trade-header {
|
||||
margin-bottom: 8px;
|
||||
|
||||
.trade-date {
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.trade-type-label {
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.trade-type-buy .trade-type-label {
|
||||
background-color: #e1f3d8;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.trade-type-sell .trade-type-label {
|
||||
background-color: #fff1f0;
|
||||
color: #f5222d;
|
||||
}
|
||||
|
||||
.trade-type-dividend .trade-type-label,
|
||||
.trade-type-cash .trade-type-label {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.trade-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
.trade-stock-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.trade-shares,
|
||||
.trade-market,
|
||||
.trade-amount {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.trade-price {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
213
src/views/target/group/group-list.vue
Normal file
213
src/views/target/group/group-list.vue
Normal file
@@ -0,0 +1,213 @@
|
||||
<template>
|
||||
<div class="page-div">
|
||||
<back-button />
|
||||
<div class="header">
|
||||
<h1 class="title">组合列表</h1>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<van-grid :column-num="1" :gutter="16" :border="false">
|
||||
<van-grid-item v-for="item in list" :key="item.id">
|
||||
<div @touchstart="onTouchStart(item.stockGroupName)" @touchend="onTouchEnd(item.stockGroupName, 'group-detail', item.id)" class="item"
|
||||
@touchcancel="onTouchCancel(item.stockGroupName)" :class="{ 'tapped': tappedItem === item.stockGroupName }">
|
||||
<div class="card-content" :style="{ backgroundColor: item.bgColor, borderLeftColor: item.color }">
|
||||
<div class="card-code" :style="{ color: item.color }">{{ item.stockGroupName }}</div>
|
||||
<div class="d-flex">
|
||||
<div class="leftValue d-flex justify-center align-center pe-16">
|
||||
<div>
|
||||
<div class="card-name">累计收益率:
|
||||
<span :class="item.totalIncomeRate > 0 ? 'red-color' : item.totalIncomeRate < 0 ? 'green-color' : ''">{{ item.totalIncomeRate }}%</span></div>
|
||||
<div class="card-note">建仓日期:{{ item.createDate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rightChart flex-1 d-flex justify-center align-center">
|
||||
<van-loading v-show="item.loading" type="spinner" color="#1989fa" class="loading" />
|
||||
<div style="width: 100%;">
|
||||
<div class="chart-div" :id="item.id + ''"></div>
|
||||
<div class="d-flex justify-between">
|
||||
<div class="tip">{{ item.beginDay }}</div>
|
||||
<div class="tip">{{ item.endDay }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-grid-item>
|
||||
</van-grid>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BackButton from '@/components/back-button.vue'
|
||||
|
||||
import { tapMixins } from '@/mixins/tap-mixins'
|
||||
const { tappedItem, onTouchStart, onTouchEnd, onTouchCancel } = tapMixins()
|
||||
|
||||
import { ref, onMounted, nextTick, watch } from 'vue'
|
||||
let list = ref<any[]>([])
|
||||
import { groupList, groupHisChartIncomeRate } from '@/utils/api'
|
||||
import { colorList } from '@/utils/colorList'
|
||||
|
||||
const init = async () => {
|
||||
const { data } = await groupList('curPage=1&limit=999&isPublic=1,2')
|
||||
data.list.map((ele: any, index: number) => {
|
||||
const colorIndex = index % colorList.length
|
||||
ele.color = colorList[colorIndex].color
|
||||
ele.bgColor = colorList[colorIndex].bgColor
|
||||
ele.totalIncomeRate = ele.holdInfo && ele.holdInfo.totalIncomeRate ? ele.holdInfo.totalIncomeRate : ''
|
||||
ele.loading = true
|
||||
ele.beginDay = ''
|
||||
ele.endDay = ''
|
||||
})
|
||||
charts.value = new Array(data.list.length).fill(null)
|
||||
list.value = data.list
|
||||
nextTick(() =>{
|
||||
setData()
|
||||
})
|
||||
}
|
||||
|
||||
import { chartMixins } from '@/mixins/chart-mixins'
|
||||
const { charts, destroyedFlag, disposeAll } = chartMixins()
|
||||
import * as echarts from 'echarts'
|
||||
import { getLineSeries, getBaseOption, getXAxis, getYAxis } from '@/utils/chart'
|
||||
const setData = async() => {
|
||||
for (let i = 0 ; i < list.value.length ; i++) {
|
||||
if (list.value && list.value.length > i && list.value[i].loading) {
|
||||
const { data: chartData } = await groupHisChartIncomeRate({ groupId: list.value[i].id })
|
||||
const sysDate: any = []
|
||||
const seriesData: any = []
|
||||
chartData.forEach((ele: any) => {
|
||||
sysDate.push(ele.sysDate)
|
||||
seriesData.push({ value: ele.nav })
|
||||
})
|
||||
const markPoint = []
|
||||
if (seriesData.length > 0) {
|
||||
if (list.value && list.value.length > i) {
|
||||
list.value[i].beginDay = sysDate[0]
|
||||
list.value[i].endDay = sysDate[sysDate.length - 1]
|
||||
}
|
||||
markPoint.push({
|
||||
symbolSize: 30,
|
||||
coord: [0, seriesData[0].value],
|
||||
label: {
|
||||
formatter: function () {
|
||||
return seriesData[0].value
|
||||
},
|
||||
color: '#ff0000',
|
||||
},
|
||||
itemStyle: {
|
||||
color: 'rgba(0,0,0,0)'
|
||||
}
|
||||
})
|
||||
markPoint.push({
|
||||
symbolSize: 30,
|
||||
coord: [sysDate.length - 1, seriesData[seriesData.length - 1].value],
|
||||
label: {
|
||||
formatter: function () {
|
||||
return seriesData[seriesData.length - 1].value
|
||||
},
|
||||
color: '#ff0000',
|
||||
},
|
||||
itemStyle: {
|
||||
color: 'rgba(0,0,0,0)'
|
||||
}
|
||||
})
|
||||
}
|
||||
const series = [
|
||||
getLineSeries({ data: seriesData, name: '累计收益率', markPoint: { data : markPoint } }),
|
||||
]
|
||||
const option = getBaseOption({
|
||||
yAxis: [getYAxis({ showFlag: false })],
|
||||
xAxis: [getXAxis({ data: sysDate, showFlag: false })],
|
||||
series: series,
|
||||
grid: [{ top: '20px', bottom: '1px', left: '20px', right: '20px' }]
|
||||
})
|
||||
if (!destroyedFlag.value && list.value && list.value.length > i && document.getElementById(list.value[i].id)) {
|
||||
if (charts.value[i]) {
|
||||
charts.value[i]?.dispose()
|
||||
charts.value[i] = null
|
||||
}
|
||||
const chart = echarts.init(document.getElementById(list.value[i].id))
|
||||
chart.setOption(option)
|
||||
charts.value[i] = chart
|
||||
list.value[i].loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
import { useRoute } from 'vue-router'
|
||||
const route = useRoute()
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
(newPath, oldPath) => {
|
||||
if (oldPath?.includes('target-table')) {
|
||||
nextTick(() => {
|
||||
init()
|
||||
})
|
||||
} else if (oldPath?.includes('group-detail')) {
|
||||
nextTick(() => {
|
||||
setData()
|
||||
})
|
||||
}
|
||||
if (newPath?.includes('target-table')) {
|
||||
disposeAll()
|
||||
charts.value = []
|
||||
list.value = []
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
onMounted (() =>{
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.leftValue {
|
||||
width: 160px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.rightChart {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
position: relative;
|
||||
|
||||
.loading {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.chart-div {
|
||||
height: 80px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.grid {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.card-code {
|
||||
margin-bottom: 24px !important;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.leftValue {
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
.chart-div {
|
||||
height: 80px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
473
src/views/target/index/index-detail.vue
Normal file
473
src/views/target/index/index-detail.vue
Normal file
@@ -0,0 +1,473 @@
|
||||
<template>
|
||||
<div class="page-div">
|
||||
<back-button />
|
||||
<div class="header">
|
||||
<div class="header-content">
|
||||
<h1 class="title">{{ indexData?.indexCode }}</h1>
|
||||
<p class="subtitle">{{ indexData?.indexName || '指数详情' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="f-b">相关文件</div>
|
||||
<div v-if="fileList.length > 0" @click="handleViewDetails" class="blue-color">
|
||||
查看详情
|
||||
</div>
|
||||
<div v-else class="red-color">暂无数据</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<h3 class="card-title">指数走势图</h3>
|
||||
<div class="chart-container">
|
||||
<van-loading v-show="chartList[0].loading" type="spinner" color="#1989fa" class="loading" />
|
||||
<div :id="`${chartList[0].id}`" class="chartDiv"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<h3 class="card-title">追踪汇总图</h3>
|
||||
<div class="chart-container">
|
||||
<van-loading v-show="chartList[1].loading" type="spinner" color="#1989fa" class="loading" />
|
||||
<div :id="`${chartList[1].id}`" class="chartDiv"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="tab-container">
|
||||
<button @click="activeTab = 0" :class="{ 'active': activeTab === 0 }" class="tab-button">行业</button>
|
||||
<button @click="activeTab = 1" :class="{ 'active': activeTab === 1 }" class="tab-button">指数持仓</button>
|
||||
<button @click="activeTab = 2" :class="{ 'active': activeTab === 2 }" class="tab-button">跟踪基金</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="activeTab === 0">
|
||||
<div class="info-card">
|
||||
<h3 class="card-title">一级行业</h3>
|
||||
<div class="chart-container">
|
||||
<van-loading v-show="chartList[2].loading" type="spinner" color="#1989fa" class="loading" />
|
||||
<div :id="`${chartList[2].id}`" class="chartDiv"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<h3 class="card-title">二级行业</h3>
|
||||
<div class="chart-container">
|
||||
<van-loading v-show="chartList[3].loading" type="spinner" color="#1989fa" class="loading" />
|
||||
<div :id="`${chartList[3].id}`" class="chartDiv"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="activeTab === 1">
|
||||
<div class="info-card">
|
||||
<div class="tableDiv">
|
||||
<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="td--column--1">名称</th>
|
||||
<th>上期权重</th>
|
||||
<th>本期权重</th>
|
||||
<th>权重变化</th>
|
||||
<th>排名</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="index in indexDetailList" :key="index.id">
|
||||
<td class="td--column--1">
|
||||
{{ index.ticker }}
|
||||
<div class="van-ellipsis firstTd">{{ index.tickerName }}</div>
|
||||
</td>
|
||||
<td>{{ index.lastQuarterWeight ? index.lastQuarterWeight + '%' : '' }}</td>
|
||||
<td>{{ index.weight ? index.weight + '%' : '' }}</td>
|
||||
<td :class="index.weightChange > 0 ? 'red-color' : index.weightChange < 0 ? 'green-color' : ''">
|
||||
{{ index.weightChange ? index.weightChange + '%' : '' }}</td>
|
||||
<td>{{ index.rankNum }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="activeTab === 2">
|
||||
<div class="info-card">
|
||||
<div class="tableDiv">
|
||||
<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="td--column--1">名称</th>
|
||||
<th>净资产(亿)</th>
|
||||
<th>净值</th>
|
||||
<th>股本(亿)</th>
|
||||
<th>调整日</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="fund in fundList" :key="fund.id" @click="$router.push({ name: 'etf-detail', params: { id: fund.etfCode } })">
|
||||
<td class="td--column--1">
|
||||
{{ fund.etfCode }}
|
||||
<div class="van-ellipsis firstTd">{{ fund.etfName }}</div>
|
||||
</td>
|
||||
<td>{{ fund.netAssets }}</td>
|
||||
<td>{{ fund.nav }}</td>
|
||||
<td>{{ fund.sharesOutstanding }}</td>
|
||||
<td><div v-html="fund.day"></div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<van-popup v-model:show="showFileListPopup" position="bottom" :style="{ height: '70%' }">
|
||||
<div class="p-16">
|
||||
<div class="d-flex justify-content-between items-center mb-16">
|
||||
<div class="sub-title f-b">文件列表</div>
|
||||
<van-icon name="close" @click="showFileListPopup = false" />
|
||||
</div>
|
||||
<div class="file-list">
|
||||
<div v-for="(file, index) in fileList" :key="index" class="file-item">
|
||||
<a :href="file.link" target="_blank" class="file-link">
|
||||
<i class="iconfont" :class="`icon-${file.type}`"></i>
|
||||
<span class="file-title van-ellipsis">{{ file.title }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-popup>
|
||||
</template>
|
||||
<script setup lang='ts'>
|
||||
import BackButton from '@/components/back-button.vue'
|
||||
|
||||
import { ref, onMounted, watch, nextTick } from 'vue'
|
||||
|
||||
import { useRoute } from 'vue-router'
|
||||
const route = useRoute()
|
||||
|
||||
const loadFlag = ref(false)
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
(newPath, oldPath) => {
|
||||
if (oldPath?.includes('index-list') && newPath?.includes('index-detail') && loadFlag.value) {
|
||||
activeTab.value = 0
|
||||
charts.value = [null, null, null, null]
|
||||
nextTick(() => {
|
||||
init()
|
||||
})
|
||||
}
|
||||
if (newPath?.includes('index-list')) {
|
||||
disposeAll()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
import { indexInfo, indexInfoIndexHistChart, indexFundHisQueryFundHis, indexInfoIndustry1Chart, indexInfoIndustry2Chart, indexFundList } from '@/utils/api'
|
||||
const indexData: any = ref(null)
|
||||
const fileList = ref<any[]>([])
|
||||
const indexDetailList = ref<any[]>([])
|
||||
const fundList = ref<any[]>([])
|
||||
import { addOrSubtractTime, formatNumberByUnit } from '@/utils'
|
||||
|
||||
const firstIndustry = ref<any[]>([])
|
||||
const secondIndustry = ref<any[]>([])
|
||||
import * as echarts from 'echarts'
|
||||
import { getLineSeries, getBaseOption, getXAxis, getYAxis } from '@/utils/chart'
|
||||
const init = async () => {
|
||||
try {
|
||||
const { data } = await indexInfo(route.params.id + '')
|
||||
indexData.value = data
|
||||
if (data.indexDescription) {
|
||||
const list = JSON.parse(data.indexDescription).data
|
||||
const reg1 = /(\.xls|\.xlsx)$/
|
||||
const reg2 = /(\.pdf)$/
|
||||
const reg3 = /(\.doc|\.docx)$/
|
||||
const reg4 = /(\.ppt|\.pptx)$/
|
||||
list.map((ele: any) => {
|
||||
if (reg1.test(ele.link)) {
|
||||
ele.type = 'excel'
|
||||
}
|
||||
if (reg2.test(ele.link)) {
|
||||
ele.type = 'pdf'
|
||||
}
|
||||
if (reg3.test(ele.link)) {
|
||||
ele.type = 'word'
|
||||
}
|
||||
if (reg4.test(ele.link)) {
|
||||
ele.type = 'ppt'
|
||||
}
|
||||
})
|
||||
fileList.value = list
|
||||
}
|
||||
indexDetailList.value = data.indexDetailList
|
||||
const { data: indexHist } = await indexInfoIndexHistChart(route.params.id + '')
|
||||
setLineChart(indexHist, 0)
|
||||
const { data: fundsHis } = await indexFundHisQueryFundHis({
|
||||
indexId: route.params.id + '',
|
||||
timeB: addOrSubtractTime(new Date(), -3, 'year'),
|
||||
timeE: new Date().toISOString().split('T')[0]
|
||||
})
|
||||
setLineChart(fundsHis, 1)
|
||||
const { data: firstIndustryData } = await indexInfoIndustry1Chart(route.params.id + '')
|
||||
firstIndustryData.map((ele: any) => {
|
||||
ele.name = ele.industry1_name
|
||||
ele.value = ele.weight
|
||||
})
|
||||
firstIndustry.value = firstIndustryData
|
||||
const { data: secondIndustryData } = await indexInfoIndustry2Chart(route.params.id + '')
|
||||
secondIndustryData.map((ele: any) => {
|
||||
ele.name = ele.industry2_name
|
||||
ele.value = ele.weight
|
||||
})
|
||||
secondIndustry.value = secondIndustryData
|
||||
handleTabChange(activeTab.value)
|
||||
const { data: fundData } = await indexFundList({ indexId: route.params.id + '' })
|
||||
fundData.map((ele: any) =>{
|
||||
ele.netAssets = ele.netAssets ? Math.round(ele.netAssets / 100000000) / 100 : ''
|
||||
ele.sharesOutstanding = ele.sharesOutstanding ? Math.round(ele.sharesOutstanding / 100000000) / 100 : ''
|
||||
ele.day = `${ele.lastQuarterDate ? ele.lastQuarterDate.slice(5,10) : ''}<br/>${ele.quarterDate ? ele.quarterDate.slice(5,10) : ''}`
|
||||
})
|
||||
fundList.value = fundData
|
||||
} catch (error) {
|
||||
console.error('初始化数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const setLineChart = (data: any, index: number) => {
|
||||
const sysDate = [] as string[]
|
||||
let seriesList = [] as any[]
|
||||
let color = []
|
||||
const legend = {
|
||||
data: [] as string[],
|
||||
top: '0px',
|
||||
show: false
|
||||
}
|
||||
let yAxis = [] as any[]
|
||||
if (index === 0) {
|
||||
color = ['blue']
|
||||
legend.data = ['指数']
|
||||
seriesList = [getLineSeries({ name: '指数' })]
|
||||
yAxis = [getYAxis({ })]
|
||||
data.forEach((ele: any) => {
|
||||
sysDate.push(ele.sysDate)
|
||||
seriesList[0].data.push({ value: ele.navPerShare })
|
||||
})
|
||||
} else{
|
||||
color = ['blue', 'red']
|
||||
legend.show = true
|
||||
legend.data = ['净资产', '股本']
|
||||
seriesList = [getLineSeries({ name: '净资产' }), getLineSeries({ name: '股本', yAxisIndex: 1 })]
|
||||
yAxis = [getYAxis({ axisLabel: function (data: any) { return formatNumberByUnit(data) }}), getYAxis({ axisLabel: function (data: any) { return formatNumberByUnit(data) }, position: 'right' })]
|
||||
data.forEach((ele: any) => {
|
||||
sysDate.push(ele.sysDate)
|
||||
seriesList[0].data.push({ value: ele.totalNetAssets })
|
||||
seriesList[1].data.push({ value: ele.sharesOutstanding })
|
||||
})
|
||||
}
|
||||
|
||||
if (!destroyedFlag.value) {
|
||||
let chart = echarts.init(document.getElementById(`${chartList.value[index].id}`))
|
||||
const option = getBaseOption({
|
||||
series: seriesList,
|
||||
yAxis: yAxis,
|
||||
xAxis: [getXAxis({ data: sysDate })],
|
||||
tooltipFormatter: function (params: any) {
|
||||
const data = Array.isArray(params) ? params : [params]
|
||||
let backText = `日期:${data[0].name}<br/>`
|
||||
data.forEach((ele) => {
|
||||
backText += `<span style="color: ${ele.color}">${ele.seriesName}</span>:<span class="font-bold">${formatNumberByUnit(ele.value)}</span><br/>`
|
||||
})
|
||||
return backText
|
||||
},
|
||||
grid: [{ top: '20px', left: '50px', right: '50px', bottom: '20px' }],
|
||||
legend: legend,
|
||||
color: color
|
||||
})
|
||||
chart.setOption(option)
|
||||
charts.value[index] = chart
|
||||
chartList.value[index].loading = false
|
||||
}
|
||||
}
|
||||
|
||||
const showFileListPopup = ref(false)
|
||||
const handleViewDetails = () => {
|
||||
showFileListPopup.value = true
|
||||
}
|
||||
|
||||
const activeTab = ref(0)
|
||||
watch(activeTab, (newValue) => {
|
||||
handleTabChange(newValue)
|
||||
})
|
||||
|
||||
const handleTabChange = (tabIndex: number) => {
|
||||
if (charts.value[2]) {
|
||||
charts.value[2].dispose()
|
||||
charts.value[2] = null
|
||||
}
|
||||
if (charts.value[3]) {
|
||||
charts.value[3].dispose()
|
||||
charts.value[3] = null
|
||||
}
|
||||
switch (tabIndex) {
|
||||
case 0:
|
||||
nextTick(() =>{
|
||||
setTreemapChart(firstIndustry.value, 2)
|
||||
setTreemapChart(secondIndustry.value, 3)
|
||||
})
|
||||
break
|
||||
case 1:
|
||||
break
|
||||
case 2:
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const setTreemapChart = (list: any, index: number) => {
|
||||
if (!destroyedFlag.value) {
|
||||
let chart = echarts.init(document.getElementById(`${chartList.value[index].id}`))
|
||||
const option = getBaseOption({
|
||||
series: [{
|
||||
type: 'treemap',
|
||||
visibleMin: 300,
|
||||
nodeClick: 'false',
|
||||
label: {
|
||||
show: true,
|
||||
formatter: function (data: any) {
|
||||
return `${data.data.name}(${data.data.value}%)`
|
||||
}
|
||||
},
|
||||
scaleLimit: {
|
||||
min: 1,
|
||||
max: 1
|
||||
},
|
||||
itemStyle: {
|
||||
borderColor: '#fff'
|
||||
},
|
||||
levels: [{
|
||||
itemStyle: {
|
||||
borderWidth: 0,
|
||||
gapWidth: 3
|
||||
}
|
||||
}],
|
||||
breadcrumb: { show: false },
|
||||
data: list,
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0
|
||||
}]
|
||||
})
|
||||
chart.setOption(option)
|
||||
charts.value[index] = chart
|
||||
chartList.value[index].loading = false
|
||||
}
|
||||
}
|
||||
|
||||
import { chartMixins } from '@/mixins/chart-mixins'
|
||||
const { charts, destroyedFlag, disposeAll } = chartMixins()
|
||||
let chartList = ref([{
|
||||
id: 'index_hist_chart',
|
||||
loading: true,
|
||||
name: '指数走势'
|
||||
}, {
|
||||
id: 'funds_his_chart',
|
||||
loading: true,
|
||||
name: '追踪汇总'
|
||||
}, {
|
||||
id: 'first_industry_chart',
|
||||
loading: true,
|
||||
name: '一级行业'
|
||||
}, {
|
||||
id: 'second_industry_chart',
|
||||
loading: true,
|
||||
name: '二级行业'
|
||||
}])
|
||||
|
||||
onMounted (() =>{
|
||||
charts.value = [null, null, null, null]
|
||||
init()
|
||||
loadFlag.value = true
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.page-div {
|
||||
overflow-y: auto;
|
||||
padding-top: 105px;
|
||||
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
|
||||
@media screen and (min-width: 992px) {
|
||||
left: calc(50vw - 496px);
|
||||
right: calc(50vw - 496px);
|
||||
}
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
height: calc(50vh - 206px);
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
height: calc(50vh - 226px);
|
||||
}
|
||||
}
|
||||
|
||||
.firstTd {
|
||||
min-width: 80px;
|
||||
max-width: 120px;
|
||||
width: auto;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
min-width: 150px;
|
||||
max-width: 400px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.file-list {
|
||||
max-height: calc(100% - 60px);
|
||||
overflow-y: auto;
|
||||
padding-bottom: 20px;
|
||||
|
||||
.file-item {
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
}
|
||||
|
||||
.file-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
|
||||
i {
|
||||
font-size: 20px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.file-title {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
padding: 0 16px 20px;
|
||||
|
||||
.file-item {
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.file-link {
|
||||
i {
|
||||
font-size: 24px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.file-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
68
src/views/target/index/index-list.vue
Normal file
68
src/views/target/index/index-list.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div class="page-div">
|
||||
<back-button />
|
||||
<div class="header flex items-center justify-center">
|
||||
<h1 class="title">{{ seriesName }}</h1>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<van-grid :column-num="1" :gutter="16" :border="false">
|
||||
<van-grid-item v-for="item in list" :key="item.indexId">
|
||||
<div @touchstart="onTouchStart(item.indexName)" @touchend="onTouchEnd(item.indexName, 'index-detail', item.indexId)" class="item"
|
||||
@touchcancel="onTouchCancel(item.indexName)" :class="{ 'tapped': tappedItem === item.indexName }">
|
||||
<div class="card-content" :style="{ backgroundColor: item.bgColor, borderLeftColor: item.color }">
|
||||
<div class="card-code" :style="{ color: item.color }">{{ item.indexCode }}</div>
|
||||
<div class="card-name">{{ item.indexName }}</div>
|
||||
<div class="card-note">{{ item.indexNote }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-grid-item>
|
||||
</van-grid>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BackButton from '@/components/back-button.vue'
|
||||
|
||||
import { tapMixins } from '@/mixins/tap-mixins'
|
||||
const { tappedItem, onTouchStart, onTouchEnd, onTouchCancel } = tapMixins()
|
||||
|
||||
import { ref, onMounted, watch, nextTick } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
const route = useRoute()
|
||||
let seriesName = ref('')
|
||||
let list = ref<any[]>([])
|
||||
import { indexInfoSeries, sysDicListByType } from '@/utils/api'
|
||||
import { colorList } from '@/utils/colorList'
|
||||
|
||||
const init = async () => {
|
||||
const { data: series } = await sysDicListByType({ dicType: 'index_series' })
|
||||
const seriesItem = series.find((item: any) => { return item.dicKey === route.params.id })
|
||||
seriesName.value = seriesItem.dicValue
|
||||
const { data } = await indexInfoSeries({ seriesCode: route.params.id })
|
||||
list.value = data.fundList
|
||||
list.value.forEach((ele, index) => {
|
||||
ele.color = colorList[index % colorList.length].color
|
||||
ele.bgColor = colorList[index % colorList.length].bgColor
|
||||
})
|
||||
}
|
||||
|
||||
onMounted (() =>{
|
||||
init()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
(newPath, oldPath) => {
|
||||
if (oldPath?.includes('series-list') && newPath?.includes('index-list')) {
|
||||
nextTick(() => {
|
||||
init()
|
||||
})
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
</style>
|
||||
53
src/views/target/series/series-list.vue
Normal file
53
src/views/target/series/series-list.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div class="page-div">
|
||||
<back-button />
|
||||
<div class="header">
|
||||
<h1 class="title">全球主要指数系列</h1>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p class="info-text">
|
||||
本页面展示了全球主要指数系列,包括国际知名指数(如MSCI、FTSE、S&P、纳斯达克等)和中国市场重要指数(如中证系列、恒指系列等)。
|
||||
这些指数涵盖了全球主要金融市场,帮助投资者了解全球市场趋势,把握投资机会。
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<van-grid :column-num="2" :gutter="16" :border="false">
|
||||
<van-grid-item class="indicator-card" v-for="item in list" :key="item.dicKey">
|
||||
<div @touchstart="onTouchStart(item.dicValue)" @touchend="onTouchEnd(item.dicValue, 'index-list', item.dicKey)" class="item"
|
||||
@touchcancel="onTouchCancel(item.dicValue)" :class="{ 'tapped': tappedItem === item.dicValue }">
|
||||
<div class="card-content" :style="{ backgroundColor: item.bgColor, borderLeftColor: item.color }">
|
||||
<div class="card-name">{{ item.dicValue }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-grid-item>
|
||||
</van-grid>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BackButton from '@/components/back-button.vue'
|
||||
|
||||
import { tapMixins } from '@/mixins/tap-mixins'
|
||||
const { tappedItem, onTouchStart, onTouchEnd, onTouchCancel } = tapMixins()
|
||||
|
||||
import { ref, onMounted } from 'vue'
|
||||
let list = ref<any[]>([])
|
||||
import { sysDicListByType } from '@/utils/api'
|
||||
import { colorList } from '@/utils/colorList'
|
||||
|
||||
const init = async () => {
|
||||
const { data } = await sysDicListByType({ dicType: 'index_series' })
|
||||
list.value = data
|
||||
list.value.forEach((ele, index) => {
|
||||
ele.color = colorList[index % colorList.length].color
|
||||
ele.bgColor = colorList[index % colorList.length].bgColor
|
||||
})
|
||||
}
|
||||
onMounted (() =>{
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
</style>
|
||||
122
src/views/target/table.vue
Normal file
122
src/views/target/table.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<van-image class="img" src="/img/indicate.jpeg" />
|
||||
<div class="grid">
|
||||
<van-grid :column-num="3" :gutter="16">
|
||||
<van-grid-item v-for="item in tableList" :key="item.name">
|
||||
<div @touchstart="onTouchStart(item.name)" @touchend="onTouchEnd(item.name, item.url)" class="item"
|
||||
@touchcancel="onTouchCancel(item.name)" :class="{ 'tapped': tappedItem === item.name }">
|
||||
<div class="icon d-flex justify-content-center align-items-center" :style="{ backgroundColor: item.bgColor }">
|
||||
<van-icon :style="{ color: item.color }" :name="item.icon" size="28" />
|
||||
</div>
|
||||
<div>{{item.name}}</div>
|
||||
</div>
|
||||
</van-grid-item>
|
||||
</van-grid>
|
||||
</div>
|
||||
<tabbar />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { colorList } from '@/utils/colorList'
|
||||
import { tapMixins } from '@/mixins/tap-mixins'
|
||||
const { tappedItem, onTouchStart, onTouchEnd, onTouchCancel } = tapMixins()
|
||||
|
||||
const tableList = [
|
||||
{
|
||||
url: 'economy-list',
|
||||
name: '全球经济',
|
||||
icon: 'chart-trending-o',
|
||||
color: colorList[0].color,
|
||||
bgColor: colorList[0].bgColor
|
||||
},
|
||||
{
|
||||
url: '',
|
||||
name: '股票数据',
|
||||
icon: 'bar-chart-o',
|
||||
color: colorList[1].color,
|
||||
bgColor: colorList[1].bgColor
|
||||
},
|
||||
{
|
||||
url: 'fred-list',
|
||||
name: 'FRED专题',
|
||||
icon: 'diamond-o',
|
||||
color: colorList[2].color,
|
||||
bgColor: colorList[2].bgColor
|
||||
},
|
||||
{
|
||||
url: 'series-list',
|
||||
name: '指数专题',
|
||||
icon: 'discount-o',
|
||||
color: colorList[3].color,
|
||||
bgColor: colorList[3].bgColor
|
||||
},
|
||||
{
|
||||
url: 'fund-list',
|
||||
name: '基金',
|
||||
icon: 'after-sale',
|
||||
color: colorList[4].color,
|
||||
bgColor: colorList[4].bgColor
|
||||
},
|
||||
{
|
||||
url: 'group-list',
|
||||
name: '精选组合',
|
||||
icon: 'fire-o',
|
||||
color: colorList[5].color,
|
||||
bgColor: colorList[5].bgColor
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.img {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
:deep(.van-grid) {
|
||||
background-color: #F2F3F5;
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
:deep(.van-grid-item) {
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
transition: transform 0.15s ease, opacity 0.15s ease, box-shadow 0.15s ease, font-size 0.2s ease;
|
||||
|
||||
.van-grid-item__content {
|
||||
border-radius: 16px;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
background-color: rgba(240, 240, 240, 0.5);
|
||||
|
||||
.item {
|
||||
padding: 16px 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #ffffff;
|
||||
|
||||
&.tapped {
|
||||
transform: scale(0.9);
|
||||
opacity: 0.7;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||
background-color: rgba(240, 240, 240, 0.5);
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-bottom: 12px;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
128
src/views/user/index.vue
Normal file
128
src/views/user/index.vue
Normal file
@@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<div class="page-content tabbar-list">
|
||||
<div class="user-top">
|
||||
<van-image class="user-bg" src="/img/user-bg.jpeg" fit="cover" />
|
||||
<van-image class="img" :src="isLogin && userInfo.headPic ? userInfo.headPic : '/img/user.png'" />
|
||||
<div>
|
||||
<div class="title" v-if="isLogin">{{ userInfo.realname }}</div>
|
||||
<div class="desc">{{ isLogin ? userInfo.username : '您还未登录' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="card">
|
||||
<div class="title">我的沟通</div>
|
||||
<communicate-item :list="list" />
|
||||
</div>
|
||||
</div>
|
||||
<van-button class="exit" round block :type="isLogin ? 'danger' : 'primary'" size="small" native-type="submit"
|
||||
@click="handleButton">{{ isLogin ? '退出登录' : '立即登录' }}</van-button>
|
||||
<tabbar />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import userInfoStore from '@/stores/userInfo'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
const router = useRouter()
|
||||
const userInfo = userInfoStore()
|
||||
console.log(userInfo);
|
||||
const isLogin = computed(() => !!userInfo.token)
|
||||
const handleButton = async () => {
|
||||
if (isLogin.value) {
|
||||
await showConfirmDialog({
|
||||
title: '提示',
|
||||
message: '确定退出吗',
|
||||
})
|
||||
userInfo.token = ''
|
||||
userInfo.headPic = ''
|
||||
list.value = []
|
||||
} else {
|
||||
router.push({
|
||||
name: 'login',
|
||||
query: {
|
||||
redirect: router.currentRoute.value.fullPath
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
import { cusLinkList } from '@/utils/api'
|
||||
import { convertToUrl } from '@/utils'
|
||||
const list = ref<any>([])
|
||||
const getCusLink = async () => {
|
||||
if (!isLogin.value) return
|
||||
const url = convertToUrl({
|
||||
limit: 3,
|
||||
dataStatus: 1,
|
||||
curPage: 1,
|
||||
createUser: userInfo.id
|
||||
})
|
||||
const data = await cusLinkList(url)
|
||||
list.value = data
|
||||
}
|
||||
getCusLink()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-top {
|
||||
position: relative;
|
||||
display: flex;
|
||||
z-index: 3;
|
||||
padding: 30px 30px 80px;
|
||||
.user-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
.img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
margin-right: 12px;
|
||||
}
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.desc {
|
||||
color: #808080;
|
||||
margin-top: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
.page-content {
|
||||
height: calc(100vh - 60px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #f6f8fa;
|
||||
position: relative;
|
||||
.content {
|
||||
position: relative;
|
||||
height: calc(100% - 210px);
|
||||
overflow: auto;
|
||||
padding-top: 20px;
|
||||
.card {
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
margin: 10px;
|
||||
margin-bottom: 20px;
|
||||
padding: 16px 0;
|
||||
.title {
|
||||
font-weight: 600;
|
||||
margin-left: 16px;
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.exit {
|
||||
width: 80%;
|
||||
position: absolute;
|
||||
bottom: 80px;
|
||||
left: 10%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user