vue2 d3實(shí)現(xiàn)企查查股權(quán)穿透圖股權(quán)結(jié)構(gòu)圖效果詳解
前言
vue3 框架中使用vue2代碼結(jié)合d3完成股權(quán)穿透圖和股權(quán)結(jié)構(gòu)圖(h5)
(沒(méi)錯(cuò)聽(tīng)上去很違規(guī),但我懶得把代碼從vue2改成vue3了,所以是在vue3框架里用vue2寫法完成的)
最終效果:


版本信息:
"d3": "4.13.0",
"vant": "^3.1.5",
"vue": "^3.0.0",
股權(quán)穿透圖基礎(chǔ)功能:
1、默認(rèn)上下游信息展示,如果沒(méi)有上下游信息只展示自己
2、點(diǎn)擊請(qǐng)求子節(jié)點(diǎn)信息展示,收起子節(jié)點(diǎn)
3、全屏功能
4、放大器放大縮?。╮eact項(xiàng)目中不知道為啥使用d3.zoom方法不好使,可能跟網(wǎng)頁(yè)中滾動(dòng)事件沖突有關(guān),最后選擇單獨(dú)防止放大器進(jìn)行放大縮小功能)
5、移動(dòng)功能
股權(quán)結(jié)構(gòu)圖基礎(chǔ)功能:
1、tab切換展示上游或下游信息
2、默認(rèn)展示一層
3、點(diǎn)擊請(qǐng)求子節(jié)點(diǎn)信息展示,收起子節(jié)點(diǎn)
股權(quán)穿透圖代碼:
<template>
<div class="father-box">
<div
id="rightPenetrationpage"
:style="{ 'transition': 'transform .5s ease', '-ms-transition': 'transform .5s ease', '-moz-transition': 'transform .5s ease','-webkit-transition': 'transform .5s ease','-o-transition': 'transform .5s ease'}">
<custom-nav-bar
:title="title"
left-arrow
@on-clickleft="onClickLeft">
</custom-nav-bar>
<!-- <div
class="full"
@click.stop="showFullScreen">
<div class="full-icon"></div>
<span>{{isFull ? '退出全屏' :'全屏'}}</span>
</div> -->
<div
id="penetrateChart"
:style="{width:'100%',display:'block',margin:'auto'}"
>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent} from 'vue'
import { useStore } from 'vuex'
import CustomNavBar from '@/components/common/CustomNavbar.vue'
import { fetchCompanySearchDetail, fetchEquityUpperInfo, fetchEquityBelowInfo } from '@/api/companySearch'
import { Notify, Toast } from 'vant'
import { formatMoney, getBLen } from '@/utils/tool'
import { sm2Decrypted } from '@/enrich/crypto-gm'
import { GlobalMutation } from '@/store/types/mutation-types'
import * as $d3 from 'd3'
// 過(guò)渡時(shí)間
const DURATION = 0
// 加減符號(hào)半徑
const SYMBOLA_S_R = 9
// 公司
const COMPANY = '0'
// 人
const PERSON = '1'
//x,y距離
// let x0 = 0, y0 = 0, dx = 0, dy = 0
export default defineComponent({
props: {},
components: {
CustomNavBar
},
data () {
return {
layoutTree: {} as any,
diamonds: {} as any,
d3: $d3,
// hasChildNodeArr: [],
originDiamonds: {} as any,
diagonalUp: '',
diagonalDown: '',
tree: {} as any,
rootUp: {} as any,
rootDown: {} as any,
svg: {} as any,
svgW: document.documentElement.clientWidth,
svgH: document.documentElement.clientHeight - 44,
title: '股權(quán)穿透圖',
isFull: false,
name: '',
id: '',
token: '',
regCapi: '',
userid: '',
parents: [] as any[], // 下游信息
children: [] as any[], // 上游信息
}
},
// beforeCreate () {
// document.body.style.overflow = 'hidden'
// },
// beforeDestroy () {
// document.body.style.overflow = 'auto'
// },
// created () {
// // window.addEventListener('orientationchange', this.changeOrient)
// },
mounted () {
const store = useStore()
const data = this.$route.query.data ? JSON.parse(sm2Decrypted(this.$route.query.data)) : {}
const id = data.id
const token = data.token ? data.token : store.state.global.token
const userid = data.userid
this.id = id
this.token = token
this.userid = userid
store.commit(`global/${GlobalMutation.SET_TOKEN}`, token)
Toast.loading({
message: '加載中',
forbidClick: true,
duration: 0,
});
this.getInit()
},
beforeUnmount() {
this.d3.select('#treesvg').remove()
console.log('頁(yè)面關(guān)閉')
},
methods: {
// changeOrient () {
// const box = document.getElementById('penetrateChart').children[0]
// const g = document.getElementById('penetrateChart').children[0].children[0]
// let navbar = document.querySelector('.navbar')
// let flag = false
// flag = isOrient()
// setTimeout(()=>{
// if(flag){
// navbar?.classList.add('smallBar')
// }else{
// navbar?.classList.remove('smallBar')
// }
// console.log(document.documentElement.clientWidth, document.documentElement.clientHeight)
// box.setAttribute('width', document.documentElement.clientWidth)
// box.setAttribute('height', document.documentElement.clientHeight)
// g.setAttribute('transform', 'translate(' + (document.documentElement.clientWidth / 2) + ',' + (document.documentElement.clientHeight / 2) + ')')
// }, 100)
// },
async getDetailInfo(){
await fetchCompanySearchDetail({
token: this.token,
instId: this.id,
userId: this.userid
}).then((response)=>{
const {code =0, records = [] } = response
if (code > 0 && records != null) {
this.regCapi = records[0].reg_capi
this.name = records[0].chn_full_nm
}
})
},
async getUpper(){
await fetchEquityUpperInfo({
token: this.token,
instId: this.id,
regCapi: this.regCapi,
currentPage: 0,
pageSize: 200,
}).then((response) => {
const {code =0, records = [] } = response
if (code > 0 && records != null) {
const dataSource = [] as any[];
records.forEach(element =>{
// let children = []
// // 設(shè)置children節(jié)點(diǎn)
// if(element.list){
// element.list.forEach(child =>{
// children.push({
// money: child.amount ? formatMoney((child.amount / 10000).toFixed(2)) :'--',
// scale: child.hold_rati || '--%',
// name: child.chn_full_nm || '--',
// id: child.inst_cust_id || '--',
// type: '0'
// })
// })
// }
dataSource.push({
// children: children,
isHaveChildren: element.dataType === '1' ? true : false,
money: element.amount ? formatMoney((element.amount / 10000).toFixed(2)) : '--',
scale: element.hold_rati || '--%',
name: element.chn_full_nm || '--',
id: element.inst_cust_id || '--',
type: '0',
regCapi: element.reg_capi
})
})
this.parents = dataSource
}
})
},
async getBelow(){
await fetchEquityBelowInfo({
token: this.token,
instId: this.id,
currentPage: 0,
pageSize: 200,
}).then((response) => {
const {code =0, records = []} = response
if (code > 0 && records != null) {
const dataSource = [] as any[];
records.forEach(element =>{
// let children = []
// // 設(shè)置children節(jié)點(diǎn)
// if(element.list){
// element.list.forEach(child =>{
// children.push({
// money: child.amount ? formatMoney((child.amount / 10000).toFixed(2)) :'--',
// scale: child.hold_rati || '--%',
// name: child.chn_full_nm || '--',
// id: child.inst_cust_id || '--',
// type: '0'
// })
// })
// }
dataSource.push({
// children: children,
isHaveChildren: element.dataType === '1' ? true : false,
money: element.amount ? formatMoney((element.amount / 10000).toFixed(2)) :'--',
scale: element.hold_rati || '--%',
name: element.chn_full_nm || '--',
id: element.inst_cust_id || '--',
type: '0',
})
})
this.children = dataSource
}
})
},
// 獲取樹(shù)狀數(shù)據(jù)
getTreeData(){
console.log( this.children, this.parents, '111111111')
let obj = {
id: this.id,
name: this.name,
tap: '節(jié)點(diǎn)',
children: this.children,
parents: this.parents,
}
this.tree = {...obj}
Toast.clear()
},
async getInit(){
await this.getDetailInfo()
// await this.getUpper()
// await this.getBelow()
Promise.all([this.getUpper(), this.getBelow()]).finally(()=>{
this.getTreeData()
this.init()
})
},
init () {
let d3 = this.d3
let svgW = this.svgW
let svgH = this.svgH
// x0 = svgW / 2,
// y0= svgH / 2
// 方塊形狀
this.diamonds = {
w: 162,
h: 66,
intervalW: 182,
intervalH: 150
}
// 源頭對(duì)象
this.originDiamonds = {
w: 208,
h: 41
}
this.layoutTree = d3.tree().nodeSize([this.diamonds.intervalW, this.diamonds.intervalH]).separation(() => 1);
// 主圖
this.svg = d3.select('#penetrateChart').append('svg').attr('width', svgW).attr('height', svgH).attr('id', 'treesvg')
.call(d3.zoom().scaleExtent([0.3, 2]).on('zoom', () => {
// 設(shè)置縮放位置以及平移初始位
// if(isiOS && this.isFull){
// // 修改ios手機(jī)上才有的移動(dòng)bug,安卓手機(jī),pc端沒(méi)有
// let x = d3.event.transform.x
// d3.event.transform.x = d3.event.transform.y
// d3.event.transform.y = -x
// console.log('222', '出現(xiàn)移動(dòng)bug', d3.event.transform)
// console.log(isiOS, d3.event.transform.x, d3.event.transform.y)
// // dx = d3.event.transform.x - x0
// // dy = d3.event.transform.y - y0
// // x0 = d3.event.transform.x
// // y0 = d3.event.transform.y
// // d3.event.transform.x = d3.event.transform.x + dy
// // d3.event.transform.y = d3.event.transform.y + dx
// // this.svg.attr('transform', 'translate(' + (svgW / 2) + ',' + (svgH / 2) + ') rotate(90)')
// }
this.svg.attr('transform', d3.event.transform.translate(svgW / 2, svgH / 2));
}))
.on('dblclick.zoom', null)
.attr('style', 'position: relative;z-index: 2') //background-image:url(${setWatermark({name: this.$store.state.global.user.userName, loginName: this.$store.state.global.user.userId}).toDataURL()})
.append('g').attr('id', 'g').attr('transform', `translate(${svgW / 2},${svgH / 2})`)
let upTree = {} as any
let downTree = {} as any
// 拷貝樹(shù)的數(shù)據(jù)
Object.keys(this.tree).map(item => {
if (item === 'parents') {
upTree = JSON.parse(JSON.stringify(this.tree))
upTree.children = this.tree[item]
upTree.parents = null
} else if (item === 'children') {
downTree = JSON.parse(JSON.stringify(this.tree))
downTree.children = this.tree[item]
downTree.parents = null
}
})
// hierarchy 返回新的結(jié)構(gòu) x0,y0初始化起點(diǎn)坐標(biāo)
this.rootUp = d3.hierarchy(upTree, d => d.children);
this.rootUp.x0 = 0
this.rootUp.y0 = 0
this.rootDown = d3.hierarchy(downTree, d => d.children);
this.rootDown.x0 = 0
this.rootDown.y0 = 0;
// 上 和 下 結(jié)構(gòu)
let treeArr = [
{
data: this.rootUp,
type: 'up'
},
{
data: this.rootDown,
type: 'down'
}
]
if(!this.tree['children'].length && !this.tree['parents'].length){
this.updataSelf()
}else{
treeArr.map(item => {
if (item.data.children) {
// item.data.children.forEach(this.collapse);
this.update(item.data, item.type, item.data)
}
})
}
},
updataSelf(){
let nodes = this.rootUp.descendants()
let node = this.svg.selectAll('g.node')
.data(nodes, d => d.data.id || '');
let nodeEnter = node.enter().append('g')
.attr('class', d => 'node node_' + d.depth) //d => showtype === 'up' && !d.depth ? 'hide-node' :
// .attr('transform', 'translate(' + (svgW / 2) + ',' + (svgH / 2) + ')')
.attr('opacity', 1); // 擁有下部分則隱藏初始?jí)K d => showtype === 'up' && !d.depth ? (this.rootDown.data.children. length ? 0 : 1) : 1
// 創(chuàng)建矩形
nodeEnter.append('rect')
.attr('type', d => d.data.id + '_' + d.depth)
.attr('width', d => d.depth ? this.diamonds.w : (getBLen(d.data.name)/2 * 20 + 20))
.attr('height', d => d.depth ? (d.data.type === COMPANY ? this.diamonds.h : this.diamonds.h - 10) : this.originDiamonds.h)
.attr('x', d => d.depth ? -this.diamonds.w / 2 : -(getBLen(d.data.name)/2 * 20 + 20) / 2)
.attr('y', d => d.depth ? 0 : -15)
.attr('stroke', '#DE4A3C')
.attr('stroke-width', 1)
.attr('rx', 10)
.attr('ry', 10)
.style('fill', d => {
if (d.data.type === COMPANY || !d.depth) {
return d.depth ? '#fff' : '#DE4A3C'
} else if (d.data.type === PERSON) {
return '#fff'
}
});
// 文字
nodeEnter.append('text')
.attr('x', 0)
.attr('y', 0)
.attr('dy', `${this.originDiamonds.h/2 - 10}px`)
.attr('text-anchor', 'middle')
.attr('fill', d => d.depth ? '#DE4A3C' : '#fff')
.text(d => d.data.name)
.style('font-size', d => d.depth ? '16px' : '20px')
.style('font-family', 'PingFangSC-Medium')
.style('font-weight', '500')
},
/*
*[update 函數(shù)描述], [click 函數(shù)描述]
* @param {[Object]} source 第一次是初始源對(duì)象,后面是點(diǎn)擊的對(duì)象
* @param {[String]} showtype up表示向上 down表示向下
* @param {[Object]} sourceTree 初始源對(duì)象
*/
update (source, showtype, sourceTree) {
// eslint-disable-next-line
let _this = this
if (source.parents === null) {
source.isOpen = !source.isOpen
}
let nodes
if (showtype === 'up') {
nodes = this.layoutTree(this.rootUp).descendants()
} else {
nodes = this.layoutTree(this.rootDown).descendants()
}
let links = nodes.slice(1);
nodes.forEach(d => {
d.y = d.depth *(d.depth == 1 ? 120 : this.diamonds.intervalH);
});
let node = this.svg.selectAll('g.node' + showtype)
.data(nodes, d => d.data.id || '');
let nodeEnter = node.enter().append('g')
.attr('class', d => showtype === 'up' && !d.depth ? 'hide-node' : 'node' + showtype)
.attr('transform', d => showtype === 'up' ? 'translate(' + d.x + ',' + -(d.y) + ')' : 'translate(' + d.x + ',' + d.y + ')')
.attr('opacity', d => showtype === 'up' && !d.depth ? (this.rootDown.data.children.length ? 0 : 1) : 1); // 擁有下部分則隱藏初始?jí)K
// 創(chuàng)建矩形
nodeEnter.append('rect')
.attr('type', d => d.data.id)
.attr('width', d => d.depth ? this.diamonds.w : (getBLen(d.data.name)/2 * 20 + 20))
.attr('height', d => d.depth ? (d.data.type === COMPANY ? this.diamonds.h : this.diamonds.h - 10) : this.originDiamonds.h)
.attr('x', d => d.depth ? -this.diamonds.w / 2 : -(getBLen(d.data.name)/2 * 20 + 20) / 2)
.attr('y', d => d.depth ? showtype === 'up' ? -this.diamonds.h / 2 : 0 : -15)
.attr('stroke', d => d.data.type === COMPANY || !d.depth ? '#DE4A3C' : '#7A9EFF')
.attr('stroke-width', 1)
.attr('rx', 10)
.attr('ry', 10)
.style('fill', d => {
if (d.data.type === COMPANY || !d.depth) {
return d.depth ? '#fff' : '#DE4A3C'
} else if (d.data.type === PERSON) {
return '#fff'
}
});
// 創(chuàng)建圓 加減
let circle = nodeEnter.append('g')
.attr('class', 'circle')
.on('click', function (d) {
_this.click(d, showtype, sourceTree)
});
circle.append('circle')
.attr('type', d => d.data.id || '')
.attr('r', (d) => d.depth ? (d.data.isHaveChildren ? SYMBOLA_S_R : 0) : 0)
.attr('cy', d => d.depth ? showtype === 'up' ? -(SYMBOLA_S_R + this.diamonds.h / 2) : (this.diamonds.h + SYMBOLA_S_R) : 0)
.attr('cx', 0)
.attr('fill', '#F9DDD9')
.attr('stroke', '#FCEDEB')
.style('stroke-width', 1)
circle.append('text')
.attr('x', 0)
.attr('dy', d => d.depth ? (showtype === 'up' ? -(SYMBOLA_S_R / 2 + this.diamonds.h / 2) : this.diamonds.h + SYMBOLA_S_R + 4) : 0)
.attr('text-anchor', 'middle')
.attr('class', 'fa')
.style('fill', '#DE4A3C')
.text(function(d) {
if(d.depth){
if (d.children) {
return '-';
} else if (d._children || d.data.isHaveChildren) {
return '+';
} else {
return '';
}
}else {
return '';
}
})
.style('font-size', '16px');
node.select('.fa')
.text(function (d) {
if (d.children) {
return '-';
} else if (d._children || d.data.isHaveChildren) {
return '+';
} else {
return '';
}
})
// 持股比例
nodeEnter.append('g')
.attr('transform', () => 'translate(0,0)')
.append('text')
.attr('x', 35)
.attr('y', showtype === 'up' ? this.diamonds.h -10 : -10)
.attr('text-anchor', 'middle')
.attr('fill', d => d.data.type === COMPANY ? '#DE4A3C' : '#7A9EFF')
.attr('opacity', d => !d.depth ? 0 : 1)
.text(d => d.data.scale)
.style('font-size', '14px')
.style('font-family', 'PingFangSC-Regular')
.style('font-weight', '400');
// 公司名稱
// y軸 否表源頭的字體距離
nodeEnter.append('text')
.attr('x', 0)
.attr('y', d => {
// 如果是上半部分
if (showtype === 'up') {
// 如果是1層以上
if (d.depth) {
return -this.diamonds.h / 2
} else {
return 0
}
} else {
if (d.depth) {
return 0
} else {
// if (d.data.name.length > 10) {
// return -5
// }
return 0
}
}
})
.attr('dy', d => d.depth ? (d.data.name.length > 10 ? '1.3em' : '1.8em') : `${this.originDiamonds.h/2 - 10}px`)
.attr('text-anchor', 'middle')
.attr('fill', d => d.depth ? '#DE4A3C' : '#fff')
.text(d =>d.depth ? (d.data.name.length > 10) ? d.data.name.substr(0, 10) : d.data.name : d.data.name)
.style('font-size', d => d.depth ? '14px' : '18px')
.style('font-family', 'PingFangSC-Medium')
.style('font-weight', '500')
.on('click', (d) => {
if(d.data.id && d.depth){
// 跳轉(zhuǎn)操作之類的
}
});
// 名稱過(guò)長(zhǎng) 第二段
nodeEnter.append('text')
.attr('x', 0)
.attr('y', d => {
// ? (d.depth ? -this.diamonds.h / 2 : 0) : 0
if (showtype === 'up') {
if (d.depth) {
return -this.diamonds.h / 2
}
return 8
} else {
if (!d.depth) {
return 8
}
return 0
}
})
.attr('dy', d => d.depth ? '2.5em' : '.3em')
.attr('text-anchor', 'middle')
.attr('fill', d => d.depth ? '#DE4A3C' : '#fff')
.text(d => {
// 索引從第19個(gè)開(kāi)始截取有表示超出
if(d.depth){
if (d.data.name.substr(19, 1)) {
return d.data.name.substr(10, 9) + '...'
}
return d.data.name.substr(10, 9)
}else{
return null
}
})
.style('font-size', '14px')
.style('font-family', 'PingFangSC-Medium')
.style('font-weight', '500');
// 認(rèn)繳金額
nodeEnter.append('text')
.attr('x', 0)
.attr('y', showtype === 'up' ? -this.diamonds.h / 2 : 0)
.attr('dy', d => d.data.name.substr(10, d.data.name.length).length ? '4.5em' : '4.1em')
.attr('text-anchor', 'middle')
.attr('fill', d => d.depth ? '#445166' : '#fff')
.text(d => d.data.money ? d.data.money.length > 12 ? `認(rèn)繳金額:${d.data.money.substr(0, 12)}…` : `認(rèn)繳金額:${d.data.money}萬(wàn)元` : '')
.style('font-size', '12px')
.style('font-family', 'PingFangSC-Regular')
.style('font-weight', '400')
.style('color', '#666666');
/*
* 繪制箭頭
* @param {string} markerUnits [設(shè)置為strokeWidth箭頭會(huì)隨著線的粗細(xì)發(fā)生變化]
* @param {string} viewBox 坐標(biāo)系的區(qū)域
* @param {number} markerWidth,markerHeight 標(biāo)識(shí)的大小
* @param {string} orient 繪制方向,可設(shè)定為:auto(自動(dòng)確認(rèn)方向)和 角度值
* @param {number} stroke-width 箭頭寬度
* @param {string} d 箭頭的路徑
* @param {string} fill 箭頭顏色
* @param {string} id resolved0表示公司 resolved1表示個(gè)人
* 直接用一個(gè)marker達(dá)不到兩種顏色都展示的效果
*/
nodeEnter.append('marker')
.attr('id', showtype + 'resolved0')
.attr('markerUnits', 'strokeWidth')
.attr('markerUnits', 'userSpaceOnUse')
.attr('viewBox', '0 -5 10 10')
.attr('markerWidth', 12)
.attr('markerHeight', 12)
.attr('orient', '90')
.attr('refX', () => showtype === 'up' ? '-50' : '10')
.attr('stroke-width', 2)
.attr('fill', '#DE4A3C')
.append('path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', '#DE4A3C');
nodeEnter.append('marker')
.attr('id', showtype + 'resolved1')
.attr('markerUnits', 'strokeWidth')
.attr('markerUnits', 'userSpaceOnUse')
.attr('viewBox', '0 -5 10 10')
.attr('markerWidth', 12)
.attr('markerHeight', 12)
.attr('orient', '90')
.attr('refX', () => showtype === 'up' ? '-50' : '10')
.attr('stroke-width', 2)
.attr('fill', '#DE4A3C')
.append('path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', '#7A9EFF');
// 將節(jié)點(diǎn)轉(zhuǎn)換到它們的新位置。
let nodeUpdate = node
// .transition()
// .duration(DURATION)
.attr('transform', d => showtype === 'up' ? 'translate(' + d.x + ',' + -(d.y) + ')' : 'translate(' + d.x + ',' + (d.y) + ')');
// 將退出節(jié)點(diǎn)轉(zhuǎn)換到父節(jié)點(diǎn)的新位置.
let nodeExit = node.exit()
// .transition()
// .duration(DURATION)
.attr('transform', () => showtype === 'up' ? 'translate(' + source.x + ',' + -(source.y) + ')' : 'translate(' + source.x + ',' + (parseInt(source.y)) + ')')
.remove();
nodeExit.select('rect')
.attr('width', this.diamonds.w)
.attr('height', this.diamonds.h)
.attr('stroke', 'black')
.attr('stroke-width', 1);
// 修改線條
let link = this.svg.selectAll('path.link' + showtype)
.data(links, d => d.data.id);
// 在父級(jí)前的位置畫線。
let linkEnter = link.enter().insert('path', 'g')
.attr('class', 'link' + showtype)
.attr('marker-start', d => `url(#${showtype}resolved${d.data.type})`)// 根據(jù)箭頭標(biāo)記的id號(hào)標(biāo)記箭頭
.attr('stroke', d => d.data.type === COMPANY ? '#DE4A3C' : '#7A9EFF')
.style('fill-opacity', 1)
.attr('fill', 'none')
.attr('stroke-width', '1px')
.attr('d', () => {
let o = {x: source.x0, y: source.y0};
return _this.diagonal(o, o, showtype)
});
let linkUpdate = linkEnter.merge(link);
// 過(guò)渡更新位置.
linkUpdate
// .transition()
// .duration(DURATION)
.attr('d', d => _this.diagonal(d, d.parent, showtype));
// 將退出節(jié)點(diǎn)轉(zhuǎn)換到父節(jié)點(diǎn)的新位置
link.exit()
// .transition()
// .duration(DURATION)
.attr('d', () => {
let o = {
x: source.x,
y: source.y
};
return _this.diagonal(o, o, showtype)
}).remove();
// 隱藏舊位置方面過(guò)渡.
nodes.forEach(d => {
d.x0 = d.x;
d.y0 = d.y
});
},
// 拷貝到_children 隱藏1排以后的樹(shù)
// collapse (source) {
// if (source.children) {
// source._children = source.children;
// source._children.forEach(this.collapse);
// source.children = null;
// this.hasChildNodeArr.push(source);
// }
// },
// 獲取點(diǎn)擊上游的上游
async fetchUpper (id, regCapi){
Toast.loading({
message: '加載中',
forbidClick: true,
duration: 0,
});
const dataSource = [];
try{
const response = await fetchEquityUpperInfo({
token: this.token,
instId: id,
currentPage: 0,
pageSize: 200,
regCapi: regCapi,
})
const {code =0, records = []} = response
if (code > 0 && records != null) {
const dataSource = [] as any[];
records.forEach(element =>{
dataSource.push({
isHaveChildren: null,
money: element.amount ? formatMoney((element.amount / 10000).toFixed(2)) :'--',
scale: element.hold_rati || '--%',
name: element.chn_full_nm || '--',
id: element.inst_cust_id || '--',
type: '0'
})
})
Toast.clear()
return dataSource
}else{
Toast.clear()
return dataSource
}
}catch(error){
Toast.clear()
return dataSource
}
},
// 獲取點(diǎn)擊下游的下游
async fetchBelow (id){
Toast.loading({
message: '加載中',
forbidClick: true,
duration: 0,
});
const dataSource = [];
try{
const response = await fetchEquityBelowInfo({
token: this.token,
instId: id,
currentPage: 0,
pageSize: 200,
})
const {code =0, records = []} = response
if (code > 0 && records != null) {
const dataSource = [] as any[];
records.forEach(element =>{
dataSource.push({
isHaveChildren: null,
money: element.amount ? formatMoney((element.amount / 10000).toFixed(2)) :'--',
scale: element.hold_rati || '--%',
name: element.chn_full_nm || '--',
id: element.inst_cust_id || '--',
type: '0'
})
})
Toast.clear()
return dataSource
}else{
Toast.clear()
return dataSource
}
}catch(error){
Toast.clear()
return dataSource
}
},
async click (source, showType, sourceTree) {
// 不是起點(diǎn)才能點(diǎn)
// if (source.depth) {
// if (source.children) {
// source._children = source.children;
// source.children = null;
// } else {
// source.children = source._children;
// source._children = null;
// }
// }
if(source.children){
// 點(diǎn)擊減號(hào)
source._children = source.children;
source.children = null;
}else {
// 點(diǎn)擊加號(hào)
if(!source._children){
let res = [] as any[]
if(showType === 'up'){
res = await this.fetchUpper(source.data.id, source.data.regCapi)
}else {
res = await this.fetchBelow(source.data.id)
}
if(!res.length){
Notify({
message: '上游或下游企業(yè)信息為空!',
type: 'warning',
duration: 1500
})
return
}
res.forEach(item =>{
let newNode = this.d3.hierarchy(item)
newNode.depth = source.depth + 1;
newNode.height = source.height - 1;
newNode.parent = source;
if(!source.children){
source.children = [];
source.data.children = [];
}
source.children.push(newNode);
source.data.children.push(newNode.data);
})
}else{
source.children = source._children;
source._children = null;
}
}
this.update(source, showType, sourceTree)
},
diagonal (s, d, showtype) {
// 折線
let endMoveNum = 0;
let moveDistance = 0;
if (d) {
if (showtype == 'down') {
let downMoveNum = d.depth ? this.diamonds.h/2 : this.originDiamonds.h/2 -10 ;
// var downMoveNum = 30;
let tmpNum = s.y + (d.y - s.y) / 2;
endMoveNum = downMoveNum;
moveDistance = tmpNum + endMoveNum;
} else {
let upMoveNum = d.depth ? 0 : -this.originDiamonds.h/2 + 5 ;
let tmpNum = d.y + (s.y - d.y) / 2;
endMoveNum = upMoveNum;
moveDistance = tmpNum + endMoveNum;
}
}
if (showtype === 'up') {
return (
'M' +
s.x +
',' +
-s.y +
'L' +
s.x +
',' +
-moveDistance +
'L' +
d.x +
',' +
-moveDistance +
'L' +
d.x +
',' +
-d.y
);
}else {
return (
'M' +
s.x +
',' +
s.y +
'L' +
s.x +
',' +
moveDistance +
'L' +
d.x +
',' +
moveDistance +
'L' +
d.x +
',' +
d.y
);
}
},
resetSvg () {
this.d3.select('#treesvg').remove()
this.init()
},
// 點(diǎn)擊全屏
showFullScreen(){
let width = document.documentElement.clientWidth,
height = document.documentElement.clientHeight,
wrapper = document.getElementById('rightPenetrationpage') as HTMLElement,
style = '';
const navbar = document.querySelector('.navbar') as HTMLElement
const fullScreen = document.querySelector('.full') as HTMLElement
// const box = document.getElementById('penetrateChart').children[0]
// const g = document.getElementById('penetrateChart').children[0].children[0]
// setTimeout(()=>{
// // box.setAttribute('width', width)
// // box.setAttribute('height', height - 44)
// g.setAttribute('transform', 'translate(' + (width / 2) + ',' + (height / 2) + ')')
// }, 200)
if (this.isFull) { // 豎屏
console.log('豎過(guò)來(lái)')
this.isFull = false
this.svgH = height - 44
this.svgW = width
// 設(shè)置按鈕和頂部樣式
fullScreen.classList.remove('fullRight')
navbar.classList.remove('smallBar')
style += 'width:100%';
style += 'height:100%;';
style += '-webkit-transform: translateX(0) translateZ(0px) rotate(0); -ms-transform: translateX(0) translateZ(0px) rotate(0); -moz-transform:translateX(0) translateZ(0px) rotate(0); -o-transform: translateX(0) translateZ(0px) rotateY(0); transform: translateX(0) translateZ(0px) rotate(0);';
style += '-webkit-transform-origin: 0 0;';
style += '-ms-transform-origin: 0 0;';
style += '-moz-transform-origin: 0 0;';
style += '-o-transform-origin: 0 0;';
style += 'transform-origin: 0 0;';
} else { // 橫屏
console.log('橫過(guò)來(lái)')
this.isFull = true
this.svgH = width - 44
this.svgW = height
// 設(shè)置按鈕和頂部樣式
fullScreen.classList.add('fullRight')
navbar.classList.add('smallBar')
style += 'width:' + height + 'px;';// 注意旋轉(zhuǎn)后的寬高切換
style += 'height:' + width + 'px;';
style += '-webkit-transform: translateX(0) translateZ(0px) rotate(90deg); -ms-transform: translateX(0) translateZ(0px) rotate(90deg); -moz-transform: translateX(0) translateZ(0px) rotate(90deg); -o-transform: translateX(0) translateZ(0px) rotate(90deg); transform:translateX(0) translateZ(0px) rotate(90deg);';
// 注意旋轉(zhuǎn)中點(diǎn)的處理
style += 'transform-origin: ' + width / 2 + 'px ' + width / 2 + 'px;';
style += '-webkit-transform-origin: ' + width / 2 + 'px ' + width / 2 + 'px;';
style += '-ms-transform-origin: ' + width / 2 + 'px ' + width / 2 + 'px;';
style += '-moz-transform-origin: ' + width / 2 + 'px ' + width / 2 + 'px;';
style += '-o-transform-origin: ' + width / 2 + 'px ' + width / 2 + 'px;';
}
wrapper.style.cssText = style;
// 重新渲染圖片
this.resetSvg()
},
// 點(diǎn)擊返回
onClickLeft(){
// jsBridge.callHandler('navigationToSkip', {type: '0'}, ()=> {
// console.log('11111111111')
// })
history.back()
}
}
})
</script>
<style lang="scss" scoped>
.father-box{
transform: perspective(1000px);
-ms-transform: perspective(1000px);
-moz-transform: perspective(1000px);
-webkit-transform: perspective(1000px);
-o-transform: perspective(1000px);
}
.info-icon {
width: 16px;
height: 16px;
background-image: url('../../assets/icon/icon_info.png');
background-repeat: no-repeat;
background-size: cover;
}
.full {
position: absolute;
top: 12px;
right:20px;
font-size: 14px;
color: #DE4A3C;
display: flex;
z-index: 9999;
line-height: 24px;
.full-icon {
width: 24px;
height: 24px;
background-image: url('../../assets/icon/icon_fullscreen.png');
background-repeat: no-repeat;
background-size: cover;
margin-right: 7px;
}
}
.fullRight{
top: 12px !important;
right:35px;
}
.smallBar {
:deep(.van-nav-bar__left){
display: none;
}
:deep(.van-nav-bar__content){
background-color: transparent;
}
:deep(.van-nav-bar__title){
// font-size: 4.8vh;
margin-left:30px;
}
}
</style>
股權(quán)結(jié)構(gòu)圖代碼:
<template>
<div
id="structureChartIn"
:style="{width:'100%',display:'block',margin:'auto'}"
>
</div>
</template>
<script lang="ts">
// import { setWatermark } from '@/utils/tool.js'
import { defineComponent} from 'vue'
import { formatMoney, getBLen } from '@/utils/tool'
import { fetchEquityUpperInfo } from '@/api/companySearch'
import { Notify, Toast } from 'vant'
import * as $d3 from 'd3'
// 過(guò)渡時(shí)間
const DURATION = 400
// 加減符號(hào)半徑
const SYMBOLA_S_R = 9
// // 公司
// const COMPANY = '0'
// // 人
// const PERSON = '1'
export default defineComponent({
props: {
tree: {
type: Object,
default: () => {
return {}
}
},
token: {
type: String,
default: ''
}
},
components: {
},
data () {
return {
diamonds: {} as any,
originDiamonds: {} as any,
d3: $d3,
// hasChildNodeArr: [],
root: {} as any,
svg: {} as any,
svgW: document.documentElement.clientWidth,
svgH: document.documentElement.clientHeight - 88,
title: '股權(quán)結(jié)構(gòu)圖',
lastClickD: null,
}
},
watch: {
tree(newVal){
if(newVal.name){
this.init()
}
}
},
mounted () {
// window.addEventListener('orientationchange', this.changeOrient)
},
// beforeUnmount(){
// window.removeEventListener('orientationchange', this.changeOrient)
// },
methods: {
// changeOrient () {
// const box = document.getElementById('structureChartIn').children[0]
// const g = document.getElementById('structureChartIn').children[0].children[0]
// let navbar = document.querySelector('.navbar')
// let flag = false
// flag = isOrient()
// setTimeout(()=>{
// if(flag){
// navbar?.classList.add('smallBar')
// }else{
// navbar?.classList.remove('smallBar')
// }
// console.log(document.documentElement.clientWidth, document.documentElement.clientHeight)
// box.setAttribute('width', document.documentElement.clientWidth)
// box.setAttribute('height', document.documentElement.clientHeight)
// g.setAttribute('transform', 'translate(' + (document.documentElement.clientWidth / 2) + ',' + (document.documentElement.clientHeight / 2) + ')')
// }, 100)
// },
init () {
let d3 = this.d3
let svgW = this.svgW
let svgH = this.svgH
let margin = {top: 20, right: 20, bottom: 30, left: 10}
// 方塊形狀
this.diamonds = {
w: 320,
h: 60,
}
// 源頭對(duì)象
this.originDiamonds = {
w: 208,
h: 36
}
// 主圖
this.svg = d3.select('#structureChartIn').append('svg').attr('width', svgW).attr('height', svgH).attr('id', 'treesvgIn')
.call(d3.zoom().scaleExtent([0.3, 2]).on('zoom', () => {
const transform = d3.event.transform
this.svg.attr('transform', transform.translate(margin.left, margin.top));
}))
.on('dblclick.zoom', null)
.attr('style', 'position: relative;z-index: 2') // background-image:url(${setWatermark({name: this.$store.state.global.user.userName, loginName: this.$store.state.global.user.userId}).toDataURL()})
.append('g').attr('id', 'gIn')
.attr('transform', `translate(${margin.left},${margin.top})`)
// 拷貝樹(shù)的數(shù)據(jù)
let downTree = {} as any
Object.keys(this.tree).map(item => {
if (item === 'children') {
downTree = JSON.parse(JSON.stringify(this.tree))
downTree.children = this.tree[item]
}
})
// hierarchy 返回新的結(jié)構(gòu) x0,y0初始化起點(diǎn)坐標(biāo)
this.root = d3.hierarchy(downTree);
this.root.x0 = 0
this.root.y0 = 0
if(!this.root.children){
this.update(this.root)
}else {
// this.root.children.forEach(this.collapse)
this.update(this.root)
}
},
/*
*[update 函數(shù)描述], [click 函數(shù)描述]
* @param {[Object]} source 第一次是初始源對(duì)象,后面是點(diǎn)擊的對(duì)象
*/
update (source) {
// eslint-disable-next-line
let _this = this
let nodes= this.root.descendants()
let index = -1, count = 0;
this.root.eachBefore(function(n) {
count+=20;
n.style = 'node_' + n.depth;
n.x = ++index * _this.diamonds.h + count;
n.y = n.depth * 25; // 設(shè)置下一層水平位置向后移25px
});
let node = this.svg.selectAll('g.node')
.data(nodes, d => {
return d.data.id || ''
} );
let nodeEnter = node.enter().append('g')
.attr('class', d => 'node node_' + d.depth)
.attr('transform', 'translate(' + source.y0 + ',' + source.x0 + ')')
.attr('opacity', 0);
// 創(chuàng)建矩形
nodeEnter.append('rect')
.attr('type', d => d.data.id)
.attr('width', d => d.depth ? this.diamonds.w : (d.data.children.length ? (getBLen(d.data.name)/2 * 18 + 62) : (getBLen(d.data.name)/2 * 18 + 20)))
.attr('height', d => d.depth ? this.diamonds.h : this.originDiamonds.h)
.attr('y', -this.diamonds.h / 2)
.style('stroke', '#DE4A3C')
.attr('stroke-width', 1)
.attr('rx', 6)
.attr('ry', 6)
.style('fill', d => {
return d.data.tap ? '#DE4A3C' : '#fff'
});
nodeEnter.append('rect')
.attr('y', -this.diamonds.h / 2)
.attr('height', d => d.depth ? this.diamonds.h : this.originDiamonds.h)
.attr('width', 6)
.attr('rx', 6)
.attr('ry', 6)
.style('fill', '#DE4A3C')
// 文字
nodeEnter.append('text')
.attr('dy', d=> d.depth ? -7 : -5)
.attr('dx', d=> d.depth ? 36 : (d.data.children.length ? 36 : 10))
.style('font-size', d=> d.depth ? '14px' : '18px')
.style('font-weight', '500')
.attr('fill', d => d.depth ? '#333333' : '#fff')
.text(function(d) {
// 名字長(zhǎng)度超過(guò)進(jìn)行截取
if(d.depth){
if(d.data.name.length>20){
return d.data.name.substring(0, 19) + '...';
}
}
return d.data.name;
})
.on('click', (d) => {
if(d.data.id && d.depth){
// 跳轉(zhuǎn)操作之類的
}
});
// 持股比例
nodeEnter.append('text')
.attr('dy', 17)
.attr('dx', 36)
.style('font-size', '12px')
.style('fill', '#666666')
.text(function(d) {
if(!d.data.tap){
return ('持股比例 ' +':')
}
});
nodeEnter.append('text')
.attr('dy', 17)
.attr('dx', 95)
.style('font-size', '12px')
.style('fill', '#DE4A3C')
.text(function(d) {
if(!d.data.tap){
return (d.data.scale)
}
});
// 認(rèn)繳金額
nodeEnter.append('text')
.attr('dy', 17)
.attr('dx', 150)
.style('font-size', '12px')
.style('fill', '#666666')
.text(function(d) {
if(!d.data.tap){
return ('認(rèn)繳金額 ' +':')
}
});
nodeEnter.append('text')
.attr('dy', 17)
.attr('dx', 210)
.style('font-size', '12px')
.style('fill', '#DE4A3C')
.text(function(d) {
if(!d.data.tap){
if(d.data.money.length > 14){
return d.data.money.substr(0, 14) + '...'
}else{
return (d.data.money + '萬(wàn)元')
}
}
});
// 箭頭
// nodeEnter.append('text')
// .attr('dy', 5.5)
// .attr('dx', 200 )
// .style('font-size', '20px')
// .style('fill', '#000')
// .text(function(d) {
// if(!d.data.tap){
// return '>'
// }
// });
// 創(chuàng)造圓 加減
let circle = nodeEnter.append('g')
.attr('class', 'circle')
.on('click', _this.click);
circle.append('circle')
.style('fill', '#F9DDD9')
.style('stroke', '#FCEDEB')
.style('stroke-width', 1)
.attr('r', function (d) {
if (d.children || d.data.isHaveChildren) {
return 9;
} else {
return 0;
}
})
.attr('cy', d => d.depth ? 0 : (- SYMBOLA_S_R -3))
.attr('cx', 20)
circle.append('text')
.attr('dy', d => d.depth ? 4.5 : -7)
.attr('dx', 20)
.attr('text-anchor', 'middle')
.attr('class', 'fa')
.style('fill', '#DE4A3C')
.text(function(d) {
if (d.children) {
return '-';
} else if (d._children || d.data.isHaveChildren) {
return '+';
} else {
return '';
}
})
.style('font-size', '16px');
node.select('.fa')
.text(function (d) {
if (d.children) {
return '-';
} else if (d._children || d.data.isHaveChildren) {
return '+';
} else {
return '';
}
})
/*
* 繪制箭頭
* @param {string} markerUnits [設(shè)置為strokeWidth箭頭會(huì)隨著線的粗細(xì)發(fā)生變化]
* @param {string} viewBox 坐標(biāo)系的區(qū)域
* @param {number} markerWidth,markerHeight 標(biāo)識(shí)的大小
* @param {string} orient 繪制方向,可設(shè)定為:auto(自動(dòng)確認(rèn)方向)和 角度值
* @param {number} stroke-width 箭頭寬度
* @param {string} d 箭頭的路徑
* @param {string} fill 箭頭顏色
*/
// nodeEnter.append('marker')
// .attr('id', 'resolvedIn')
// .attr('markerUnits', 'strokeWidth')
// .attr('markerUnits', 'userSpaceOnUse')
// .attr('viewBox', '0 -5 10 10')
// .attr('markerWidth', 8)
// .attr('markerHeight', 8)
// .attr('orient', '0')
// .attr('refX', '10')
// // .attr('refY', '10')
// .attr('stroke-width', 2)
// .attr('fill', '#DE4A3C')
// .append('path')
// .attr('d', 'M0,-5L10,0L0,5')
// .attr('fill', '#DE4A3C');
// 將節(jié)點(diǎn)轉(zhuǎn)換到它們的新位置。
nodeEnter
// .transition()
// .duration(DURATION)
.attr('transform', function(d) { return 'translate(' + d.y + ',' + d.x + ')'; })
.style('opacity', 1);
node
// .transition()
// .duration(DURATION)
.attr('transform', function(d) { return 'translate(' + d.y + ',' + d.x + ')'; })
.style('opacity', 1)
.select('rect');
// 將退出節(jié)點(diǎn)轉(zhuǎn)換到父節(jié)點(diǎn)的新位置.
let nodeExit = node.exit()
// .transition()
// .duration(DURATION)
.attr('transform', () => 'translate(' + source.y + ',' + (parseInt(source.x)) + ')')
.style('opacity', 0)
.remove();
// nodeExit.select('rect')
// .attr('width', this.diamonds.w)
// .attr('height', this.diamonds.h)
// .attr('stroke', 'black')
// .attr('stroke-width', 1);
// 修改線條
let link = this.svg.selectAll('path.link')
.data(this.root.links(), d => d.target.id);
// 在父級(jí)前的位置畫線。
let linkEnter = link.enter().insert('path', 'g')
.attr('class', d =>{
return 'link link_' + d.target.depth
} )
// .attr('marker-end', 'url(#resolvedIn)')// 根據(jù)箭頭標(biāo)記的id號(hào)標(biāo)記箭頭
.attr('stroke', '#DE4A3C')
.style('fill-opacity', 1)
.attr('fill', 'none')
.attr('stroke-width', '1px')
.attr('d', () => {
let o = {x: source.x0, y: source.y0};
return _this.diagonal({source: o, target: o})
})
// .transition()
// .duration(DURATION)
.attr('d', _this.diagonal);
// 過(guò)渡更新位置.
link
// .transition()
// .duration(DURATION)
.attr('d', _this.diagonal);
// 將退出節(jié)點(diǎn)轉(zhuǎn)換到父節(jié)點(diǎn)的新位置
link.exit()
// .transition()
// .duration(DURATION)
.attr('d', () => {
let o = {
x: source.x,
y: source.y
};
return _this.diagonal({source: o, target: o})
}).remove();
// 隱藏舊位置方面過(guò)渡.
this.root.each(d => {
d.x0 = d.x;
d.y0 = d.y
});
},
// 獲取點(diǎn)擊上游的上游
async fetchUpper (id, regCapi){
Toast.loading({
message: '加載中',
forbidClick: true,
duration: 0,
});
const dataSource = [];
try{
const response = await fetchEquityUpperInfo({
token: this.token,
instId: id,
currentPage: 0,
pageSize: 200,
regCapi: regCapi,
})
const {code =0, records = []} = response
if (code > 0 && records != null) {
console.log(records)
const dataSource = [] as any[];
records.forEach(element =>{
dataSource.push({
isHaveChildren: null,
money: element.amount ? formatMoney((element.amount / 10000).toFixed(2)) :'--',
scale: element.hold_rati || '--%',
name: element.chn_full_nm || '--',
id: element.inst_cust_id || '--',
type: '0'
})
})
Toast.clear()
return dataSource
}else{
Toast.clear()
return dataSource
}
}catch(error){
Toast.clear()
return dataSource
}
},
async click (source) {
// if (d.children) {
// d._children = d.children;
// d.children = null;
// } else {
// d.children = d._children;
// d._children = null;
// }
// if (this.lastClickD){
// this.lastClickD._isSelected = false;
// }
// d._isSelected = true;
// this.lastClickD = d;
if(source.children){
// 點(diǎn)擊減號(hào)
source._children = source.children;
source.children = null;
}else {
// 點(diǎn)擊加號(hào)
if(!source._children){
let res = [] as any[]
res = await this.fetchUpper(source.data.id, source.data.regCapi)
if(!res.length){
Notify({
message: '上游或下游企業(yè)信息為空!',
type: 'warning',
duration: 1500
})
return
}
res.forEach(item =>{
let newNode = this.d3.hierarchy(item)
newNode.depth = source.depth + 1;
newNode.height = source.height - 1;
newNode.parent = source;
if(!source.children){
source.children = [];
source.data.children = [];
}
source.children.push(newNode);
source.data.children.push(newNode.data);
})
}else{
source.children = source._children;
source._children = null;
}
}
this.update(source);
},
// 拷貝到_children 隱藏1排以后的樹(shù)
// collapse (source) {
// if (source.children) {
// source._children = source.children;
// source._children.forEach(this.collapse);
// source.children = null;
// this.hasChildNodeArr.push(source);
// }
// },
diagonal (d) {
return `M ${d.source.y} ${d.source.x}
H ${(d.source.y + (d.target.y-d.source.y)/2)}
V ${d.target.x}
H ${d.target.y}`;
},
}
})
</script>
總結(jié):
前端小白一枚,在之前只使用過(guò)echarts進(jìn)行可視化,在開(kāi)發(fā)這個(gè)功能時(shí)候發(fā)現(xiàn)d3版本中文網(wǎng)站內(nèi)容較少,基本出現(xiàn)問(wèn)題討論也是在外文網(wǎng)站,踩過(guò)一堆版本的坑,最終選擇穩(wěn)定且例子比較多的v4版本。還有基本都是默認(rèn)信息展示,很少有點(diǎn)擊請(qǐng)求的功能,進(jìn)行一個(gè)最終功能的整合。
以上就是vue2 d3實(shí)現(xiàn)企查查股權(quán)穿透圖股權(quán)結(jié)構(gòu)圖效果詳解的詳細(xì)內(nèi)容,更多關(guān)于vue2 d3股權(quán)穿透圖結(jié)構(gòu)圖的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue router/Element重復(fù)點(diǎn)擊導(dǎo)航路由報(bào)錯(cuò)問(wèn)題及解決
這篇文章主要介紹了Vue router/Element重復(fù)點(diǎn)擊導(dǎo)航路由報(bào)錯(cuò)問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07
Vue Element前端應(yīng)用開(kāi)發(fā)之獲取后端數(shù)據(jù)
這篇文章主要介紹了Vue Element前端應(yīng)用開(kāi)發(fā)之獲取后端數(shù)據(jù),對(duì)vue感興趣的同學(xué),可以參考下2021-05-05
axios上傳文件錯(cuò)誤:Current?request?is?not?a?multipart?request
最近工作中使用vue上傳文件的時(shí)候遇到了個(gè)問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于axios上傳文件錯(cuò)誤:Current?request?is?not?a?multipart?request解決的相關(guān)資料,需要的朋友可以參考下2023-01-01
vue項(xiàng)目中如何實(shí)現(xiàn)element-ui組件按需引入
這篇文章主要介紹了vue項(xiàng)目中如何實(shí)現(xiàn)element-ui組件按需引入,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05
el-table表頭根據(jù)內(nèi)容自適應(yīng)完美解決表頭錯(cuò)位和固定列錯(cuò)位
這篇文章主要介紹了el-table表頭根據(jù)內(nèi)容自適應(yīng)完美解決表頭錯(cuò)位和固定列錯(cuò)位,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
vue報(bào)錯(cuò)Not?allowed?to?load?local?resource的解決辦法
這篇文章主要給大家介紹了關(guān)于vue報(bào)錯(cuò)Not?allowed?to?load?local?resource的解決辦法,這個(gè)錯(cuò)誤是因?yàn)閂ue不能加載本地資源的原因,需要的朋友可以參考下2023-07-07
Vuex數(shù)據(jù)持久化實(shí)現(xiàn)的思路與代碼
Vuex數(shù)據(jù)持久化可以很好的解決全局狀態(tài)管理,當(dāng)刷新后數(shù)據(jù)會(huì)消失,這是我們不愿意看到的。這篇文章主要給大家介紹了關(guān)于Vuex數(shù)據(jù)持久化實(shí)現(xiàn)的思路與代碼,需要的朋友可以參考下2021-05-05

