antvX6 - Vue自定义节点,并实现多种画布操作,拖拽、缩放、连线、双击、检索等等
vue
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
项目地址:https://gitcode.com/gh_mirrors/vu/vue
免费下载资源
·
一、 首先 antv x6 分为两个版本 低版本和高版本
我这里是使用的2.0版本 并且搭配了相关插件 例如:画布的图形变换、地图等
个人推荐 2.0版本,高版本配置多,可使用相关插件多,但是文档描述小,仍在更新, 低版本文档描述清晰,但是相关插件少
二、antv x6 支持自定义节点!
这里要特别注意 虽然支持自定义节点,但是连线,连线桩也自然只能节点之间互连,所以你看我的例子中,想要列表里的子节点也可以实现 互相连接,但是这是自定义节点无法做到的。
因为此时这一整个盒子就是 一个节点!
三、事件集合
// 事件集合
loadEvents(containerRef) {
// 节点双击
this.graph.on('node:dblclick', ({ node }) => {
const data = node.store.data;
console.log(data);
this.$router.push({
path: '/modeling/homeModeling',
query: {
id: data.modelingId,
name: data.name,
layerTypeId: data.layerTypeId,
tableType: data.modelingType,
},
});
});
// 连线双击
this.graph.on('edge:dblclick', ({ edge }) => {
// const data = edge.store.data;
// const { type, id } = data;
// alert('连线双击');
// console.log('edge:dbclick', edge);
// if (type === 'taskNode') {
// this.nodeId = id;
// this.showRight = true;
// } else {
// this.nodeId = '';
// this.showRight = false;
// }
});
// 节点鼠标移入
this.graph.on(
'node:mouseenter',
FunctionExt.debounce(({ node }) => {
// 添加删除
// const x = node.store.data.size.width - 10;
// node.addTools({
// name: 'button-remove',
// args: {
// x: 0,
// y: 0,
// offset: { x, y: 15 },
// },
// });
}),
500,
);
this.graph.on('node:port-contextmenu', ({ e }) => {
// console.log(
// 'ports',
// e,
// e.currentTarget.parentElement.getAttribute('port'),
// );
});
// 连接线鼠标移入
this.graph.on('edge:mouseenter', ({ edge }) => {
// edge.addTools([
// 'source-arrowhead',
// 'target-arrowhead',
// {
// name: 'button-remove',
// args: {
// distance: '50%',
// },
// },
// ]);
});
// 节点鼠标移出
this.graph.on('node:mouseleave', ({ node }) => {
// // 移除删除
// node.removeTools();
});
this.graph.on('edge:mouseleave', ({ edge }) => {
// edge.removeTools();
});
this.graph.on('edge:connected', ({ isNew, edge }) => {
// console.log('connected', edge.source, edge.target);
// if (isNew) {
// // 对新创建的边进行插入数据库等持久化操作
// }
});
},
四、画布初始化
graphInit() {
// 容器生成图表
const containerRef = this.$refs.containerRef;
const graph = new Graph({
container: containerRef,
background: {
color: '#F1F6F9',
},
grid: {
size: 10, // 网格大小 10px
visible: true, // 绘制网格,默认绘制 dot 类型网格
type: 'fixedDot',
args: {
color: '#AFB0B1', // 网点颜色
thickness: 1, // 网点大小
},
},
panning: true, // 画布拖拽
history: true, // 启动历史记录
selecting: {
// 选择与框选
enabled: true,
rubberband: true,
movable: true,
strict: true,
showNodeSelectionBox: true, // 显示节点的选择框(才能进行移动)
modifiers: ['alt'],
},
// Scroller 使画布具备滚动、平移、居中、缩放等能力
scroller: {
enabled: true,
pageVisible: true,
pageBreak: true,
pannable: true,
},
// 鼠标滚轮的默认行为是滚动页面 使用ctrl+滚轮 实现缩放
mousewheel: {
enabled: true,
modifiers: ['ctrl', 'meta'], // +按键为缩放
minScale: 0.5,
maxScale: 2,
},
snapline: true, // 对齐线
// 节点连接
connecting: {
router: {
name: 'er',
args: {
offset: 25,
direction: 'H',
},
},
snap: true, // 自动吸附
allowBlank: false, // 是否允许连接到画布空白位置的点
allowLoop: false, // 是否允许创建循环连线,即边的起始节点和终止节点为同一节点
allowNode: false, // 是否允许边链接到节点(非节点上的链接桩)
createEdge() {
return new Shape.Edge({
attrs: {
line: {
stroke: '#1684FC',
strokeWidth: 2,
},
},
});
},
},
// 连接桩样式 -- 高亮
highlighting: {
magnetAvailable: {
name: 'stroke',
args: {
padding: 4,
attrs: {
strokeWidth: 4,
stroke: '#1684FC',
},
},
},
},
});
// 小地图
const minimapContainer = this.$refs.minimapContainer;
graph.use(
new MiniMap({
container: minimapContainer,
width: '250',
height: '150',
scalable: true, // 是否可缩放
minScale: 0.01,
maxScale: 16,
}),
);
// 图形
graph.use(
new Transform({
enabled: true,
resizing: map,
}),
);
// 缩放画布内容,使画布内容充满视口
graph.zoomToFit({ padding: 10, maxScale: 1 });
// 赋值生成
this.graph = graph;
// 事件集合
this.loadEvents(containerRef);
},
五、创建Vue自定义节点
<template> <div ref="node_dom" class="node_warp" :style="{ width: node.size.width + 'px', height: node.size.height + 'px', borderTopColor: color, }" > <div class="head_top" :style="{ backgroundColor }"> <svg-icon :icon-class="icon" :style="{ color }"></svg-icon> <div class="code_warp"> <span class="code ellipsis_text">{{ node.code }}</span> <span class="name ellipsis_text">{{ node.name }}</span> </div> <el-popover ref="popoverDom" placement="bottom-end" width="60" :value="popShow" trigger="click" popper-class="filter_column_popover" @hide="popShow = false" @show="popShow = true" > <svg-icon slot="reference" class="icon" type="primary" size="mini" style="opacity: 0.5;" icon-class="table_column_settings" ></svg-icon> <p class="header_wrap_filter_column"> <el-checkbox v-model="checkAll" :indeterminate="isIndeterminate" @change="handleCheckAllChange" > 全选 </el-checkbox> <!-- --> <!-- <el-button size="mini" type="text" @click="resetColumn"> 重置 </el-button> --> </p> <el-checkbox-group v-model="checkList" @change="handleCheckedCitiesChange" > <el-checkbox v-for="item in checkData" :key="item" :label="item"> {{ item }} </el-checkbox> </el-checkbox-group> <div v-if="!checkData.length" class="empy">暂无数据</div> </el-popover> </div> <div class="main"> <div v-for="(item, index) in node.columnVersions" :key="index" class="text " > <svg-icon v-if="item.isPrimaryKey" icon-class="key"></svg-icon> <span v-show="checkList.includes('英文名称')" class="ellipsis_text"> {{ item.code }} </span> <div v-show="checkList.includes('字段类型')" class="type ellipsis_text"> {{ item.dataType }} </div> <span v-show="checkList.includes('中文名称')" class="ellipsis_text"> {{ item.name }} </span> </div> <div v-if="!node.columnVersions || !node.columnVersions.length" class="empy flex" > 暂无数据 </div> </div> <div class="footer"> {{ `共${node.columnSize || 0}个字段` }} </div> </div> </template> <script> import { manage } from './config'; const cityOptions = ['英文名称', '字段类型', '中文名称']; export default { name: 'Node', inject: ['getNode'], data() { return { num: 0, icon: '', color: '', node: {}, popShow: false, checkAll: false, checkList: ['英文名称', '字段类型'], checkData: cityOptions, isIndeterminate: true, backgroundColor: null, typeMap: manage.typeMap, }; }, watch: { checkList(val) { console.log(val); }, }, created() { const node = this.getNode(); const typeMap = this.typeMap; this.node = node.store.data; const type = this.node.modelingType; this.icon = typeMap[type].icon; this.color = typeMap[type].color; this.backgroundColor = typeMap[type].backgroundColor; }, methods: { handleCheckAllChange(val) { this.checkList = val ? cityOptions : []; this.isIndeterminate = false; }, handleCheckedCitiesChange(value) { const checkedCount = value.length; this.checkAll = checkedCount === this.checkData.length; this.isIndeterminate = checkedCount > 0 && checkedCount < this.checkData.length; }, resetColumn() { this.checkList = ['英文名称', '字段类型']; }, }, }; </script> <style lang="scss" scoped> .node_warp { display: flex; border-radius: 4px; flex-direction: column; border: 1px solid #d9dae2; border-top: 5px solid #d9dae2; position: relative; user-select: none; transition: all 0.4s ease-in 0.2s; transition: width 0.25s; -webkit-transition: width 0.25s; -moz-transition: width 0.25s; -webkit-transition: width 0.25s; -o-transition: width 0.25s; .head_top { width: 100%; height: 48px; display: flex; padding-left: 10px; align-items: center; position: relative; border-bottom: 1px solid #d9dae2; .code_warp { width: 85%; font-size: 12px; margin-left: 8px; display: flex; flex-direction: column; .code { color: black; font-weight: 700; } .name { color: #b3b2bf; font-weight: 600; } } .icon { position: absolute; right: 5px; bottom: 5px; } } .main { flex: 1; width: 100%; overflow: auto; padding-right: 2px; background: #fff; .text { height: 32px; display: flex; gap: 1px; font-size: 13px; position: relative; padding-left: 20px; align-items: center; svg { position: absolute; left: 4px; top: 10px; } .type { flex: 1; height: 24px; font-size: 12px; line-height: 24px; text-align: center; border-radius: 4px; margin-right: 5px; display: inline-block; background-color: #f7f7f9; } span { flex: 1; text-align: center; } &:hover { background: #f8f8fa; } } } .footer { height: 20px; font-size: 12px; line-height: 20px; padding-left: 10px; color: rgb(156, 160, 184); border-top: 1px solid #d9dae2; background: rgb(247, 247, 249); } .ellipsis_text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; word-break: break-all; line-height: 18px; } .empy { color: #ccc; font-size: 14px; margin: 10px auto; width: fit-content; } .flex { display: flex; height: calc(100% - 30px); align-items: center; } } </style>
六、注册引入Vue自定义节点
1、安装依赖
"@antv/x6-vue-shape": "2.0.6",
yarn add antv/x6-vue-shape@2.0.6
2、引入 Vue 自定义组件
import CustomNode from '../node';
3、引入插件的方法
import { register } from '@antv/x6-vue-shape'; // vue节点
4、注册节点
register({
shape: 'custom-vue-node',
component: CustomNode,
});
import CustomNode from '../node'; import { register } from '@antv/x6-vue-shape'; // vue节点 // 注册 Vue component register({ shape: 'custom-vue-node', component: CustomNode, });
七、创建节点、创建连线、渲染节点
// 连接线 const lineNewData = newData.map((item, index) => { return { id: String(new Date().getTime() + index), shape: 'edge', // 连接源 source: { cell: item.sourceTableId, }, // 连接目标 target: { cell: item.targetTableId, }, attrs: { line: { stroke: '#1684FC', strokeWidth: 2, }, }, // 名字 labels: [ { attrs: { label: { text: item.name || '', }, }, }, ], zIndex: 0, }; }); // 节点 const nodeData = result.map(item => { return { ...item, id: item.modelingVersionId, width: Number(item.width || 300), height: Number(item.heigh || 270), // 节点类型 shape: item.shape || 'custom-vue-node', position: { x: Number(item.posX || this.getRandomInt()), y: Number(item.posY || this.getRandomInt()), }, }; }); this.erData = [...nodeData, ...lineNewData];
通过数据 渲染节点
watch: { data(val) { const cells = []; this.data.forEach(item => { console.log(item, item.shape); if (item.shape === 'edge') { cells.push(this.graph.createEdge(item)); // 创建连线 } else { cells.push(this.graph.createNode(item)); // 创建节点 } }); // 清空画布并添加用指定的节点/边 this.graph.resetCells(cells); }, },
八、canvas主页面 全部代码
<template>
<div id="container" class="antv-x6">
<div ref="minimapContainer" class="app-mini"></div>
<div ref="containerRef" class="app-content"></div>
<div class="operating">
<el-select
v-model="value"
clearable
filterable
placeholder="请选择"
size="mini"
:popper-append-to-body="false"
:class="isShow ? 'showSelect' : 'hideSelect'"
@change="valChange"
>
<el-option
v-for="item in data.filter(i => i.modelingType)"
:key="item.id"
:label="item.code"
:value="item.id"
>
<div class="head_top">
<svg-icon
:icon-class="typeMap[item.modelingType].icon"
:style="{ color: typeMap[item.modelingType].color }"
/>
<div class="code_warp">
<span class="code ellipsis_text">{{ item.code }}</span>
<span class="name ellipsis_text">{{ item.name }}</span>
</div>
</div>
</el-option>
</el-select>
<div class="icon_oper">
<el-tooltip
class="item"
effect="dark"
content="搜索"
placement="bottom"
>
<svg-icon icon-class="search_canvas" @click="search" />
</el-tooltip>
<el-tooltip
class="item"
effect="dark"
content="放大"
placement="bottom"
>
<svg-icon icon-class="amplify_canvas" @click="zoomInFn" />
</el-tooltip>
<el-tooltip
class="item"
effect="dark"
content="缩小"
placement="bottom"
>
<svg-icon icon-class="reduce_canvas" @click="zoomOutFn" />
</el-tooltip>
<el-tooltip
class="item"
effect="dark"
content="还原"
placement="bottom"
>
<svg-icon icon-class="1_1_canvas" @click="resetFn" />
</el-tooltip>
<el-tooltip
class="item"
effect="dark"
content="保存"
placement="bottom"
>
<svg-icon icon-class="saveModel" @click="submit" />
</el-tooltip>
<el-tooltip
class="item"
effect="dark"
:content="isFullScreen ? '退出全屏' : '全屏'"
placement="bottom"
>
<svg-icon icon-class="screen" @click="fullScreen" />
</el-tooltip>
<el-tooltip
class="item"
effect="dark"
content="刷新"
placement="bottom"
>
<svg-icon icon-class="refresh" @click="redoFn" />
</el-tooltip>
</div>
</div>
</div>
</template>
<script>
import { manage } from '../config';
import CustomNode from '../node';
import { Graph, Shape, FunctionExt } from '@antv/x6';
import { register } from '@antv/x6-vue-shape'; // vue节点
import { MiniMap } from '@antv/x6-plugin-minimap'; // 地图
import { Transform } from '@antv/x6-plugin-transform'; // 图形变换
// import { Scroller } from '@antv/x6-plugin-scroller'; // 滚动画布
const map = {
enabled: true,
minWidth: 200,
maxWidth: 700,
minHeight: 100,
maxHeight: 500,
orthogonal: false,
restrict: false,
preserveAspectRatio: false,
};
// 注册 Vue component
register({
shape: 'custom-vue-node',
component: CustomNode,
});
export default {
name: 'Er',
props: {
data: {
type: Array,
default: () => [],
},
},
data() {
return {
value: '',
graph: null,
isShow: false,
showRight: false,
isFullScreen: false,
typeMap: manage.typeMap,
};
},
watch: {
data(val) {
const cells = [];
this.data.forEach(item => {
console.log(item, item.shape);
if (item.shape === 'edge') {
cells.push(this.graph.createEdge(item)); // 创建连线
} else {
cells.push(this.graph.createNode(item)); // 创建节点
}
});
// 清空画布并添加用指定的节点/边
this.graph.resetCells(cells);
},
},
mounted() {
this.graphInit();
},
methods: {
graphInit() {
// 容器生成图表
const containerRef = this.$refs.containerRef;
const graph = new Graph({
container: containerRef,
background: {
color: '#F1F6F9',
},
grid: {
size: 10, // 网格大小 10px
visible: true, // 绘制网格,默认绘制 dot 类型网格
type: 'fixedDot',
args: {
color: '#AFB0B1', // 网点颜色
thickness: 1, // 网点大小
},
},
panning: true, // 画布拖拽
history: true, // 启动历史记录
selecting: {
// 选择与框选
enabled: true,
rubberband: true,
movable: true,
strict: true,
showNodeSelectionBox: true, // 显示节点的选择框(才能进行移动)
modifiers: ['alt'],
},
// Scroller 使画布具备滚动、平移、居中、缩放等能力
scroller: {
enabled: true,
pageVisible: true,
pageBreak: true,
pannable: true,
},
// 鼠标滚轮的默认行为是滚动页面 使用ctrl+滚轮 实现缩放
mousewheel: {
enabled: true,
modifiers: ['ctrl', 'meta'], // +按键为缩放
minScale: 0.5,
maxScale: 2,
},
snapline: true, // 对齐线
// 节点连接
connecting: {
router: {
name: 'er',
args: {
offset: 25,
direction: 'H',
},
},
snap: true, // 自动吸附
allowBlank: false, // 是否允许连接到画布空白位置的点
allowLoop: false, // 是否允许创建循环连线,即边的起始节点和终止节点为同一节点
allowNode: false, // 是否允许边链接到节点(非节点上的链接桩)
createEdge() {
return new Shape.Edge({
attrs: {
line: {
stroke: '#1684FC',
strokeWidth: 2,
},
},
});
},
},
// 连接桩样式 -- 高亮
highlighting: {
magnetAvailable: {
name: 'stroke',
args: {
padding: 4,
attrs: {
strokeWidth: 4,
stroke: '#1684FC',
},
},
},
},
});
// 小地图
const minimapContainer = this.$refs.minimapContainer;
graph.use(
new MiniMap({
container: minimapContainer,
width: '250',
height: '150',
scalable: true, // 是否可缩放
minScale: 0.01,
maxScale: 16,
}),
);
// 图形
graph.use(
new Transform({
enabled: true,
resizing: map,
}),
);
// 缩放画布内容,使画布内容充满视口
graph.zoomToFit({ padding: 10, maxScale: 1 });
// 赋值生成
this.graph = graph;
// 事件集合
this.loadEvents(containerRef);
},
// 事件集合
loadEvents(containerRef) {
// 节点双击
this.graph.on('node:dblclick', ({ node }) => {
const data = node.store.data;
console.log(data);
this.$router.push({
path: '/modeling/homeModeling',
query: {
id: data.modelingId,
name: data.name,
layerTypeId: data.layerTypeId,
tableType: data.modelingType,
},
});
});
// 连线双击
this.graph.on('edge:dblclick', ({ edge }) => {
// const data = edge.store.data;
// const { type, id } = data;
// alert('连线双击');
// console.log('edge:dbclick', edge);
// if (type === 'taskNode') {
// this.nodeId = id;
// this.showRight = true;
// } else {
// this.nodeId = '';
// this.showRight = false;
// }
});
// 节点鼠标移入
this.graph.on(
'node:mouseenter',
FunctionExt.debounce(({ node }) => {
// 添加删除
// const x = node.store.data.size.width - 10;
// node.addTools({
// name: 'button-remove',
// args: {
// x: 0,
// y: 0,
// offset: { x, y: 15 },
// },
// });
}),
500,
);
this.graph.on('node:port-contextmenu', ({ e }) => {
// console.log(
// 'ports',
// e,
// e.currentTarget.parentElement.getAttribute('port'),
// );
});
// 连接线鼠标移入
this.graph.on('edge:mouseenter', ({ edge }) => {
// edge.addTools([
// 'source-arrowhead',
// 'target-arrowhead',
// {
// name: 'button-remove',
// args: {
// distance: '50%',
// },
// },
// ]);
});
// 节点鼠标移出
this.graph.on('node:mouseleave', ({ node }) => {
// // 移除删除
// node.removeTools();
});
this.graph.on('edge:mouseleave', ({ edge }) => {
// edge.removeTools();
});
this.graph.on('edge:connected', ({ isNew, edge }) => {
// console.log('connected', edge.source, edge.target);
// if (isNew) {
// // 对新创建的边进行插入数据库等持久化操作
// }
});
},
// 放大
zoomInFn() {
this.graph.zoom(0.1);
},
// 缩小
zoomOutFn() {
const Num = Number(this.graph.zoom().toFixed(1));
if (Num > 0.1) {
this.graph.zoom(-0.1);
}
},
// 重置1:1
resetFn() {
this.graph.centerContent();
this.graph.zoomTo(1); // 缩放画布到指定的比例
},
// 刷新
redoFn() {
this.$emit('detailsEr');
},
// 全屏
fullScreen() {
// const element = document.documentElement;
const element = document.getElementById('container');
// 判断是否已经是全屏
if (this.isFullScreen) {
// 退出全屏
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitCancelFullScreen) {
document.webkitCancelFullScreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
} else {
// 全屏
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.webkitRequestFullScreen) {
element.webkitRequestFullScreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.msRequestFullscreen) {
// IE11
element.msRequestFullscreen();
}
}
this.isFullScreen = !this.isFullScreen;
},
// 搜索
search() {
this.isShow = !this.isShow;
},
// 保存
submit() {
const data = this.graph.getNodes();
this.$emit('submitEr', data);
},
// 检索
valChange(val) {
if (val) {
// false - 清空
const nodes = this.graph.getNodes() || [];
const node = nodes.filter(item => item.id === val)[0] || {};
this.graph.centerCell(node); // 将节点/边的中心与视口中心对齐
} else {
this.resetFn();
}
},
},
};
</script>
<style lang="scss" scoped>
.antv-x6 {
width: 100%;
height: 100%;
padding: 0;
display: flex;
position: relative;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
::v-deep body {
min-width: auto;
}
.node-c {
width: 200px;
border-right: 1px solid #eee;
padding: 20px;
dl {
margin-bottom: 20px;
line-height: 30px;
display: flex;
cursor: move;
dt {
&.circle {
width: 30px;
height: 30px;
border-radius: 50%;
&.start {
border: 1px solid green;
background: greenyellow;
}
&.end {
border: 1px solid salmon;
background: red;
}
}
&.rect {
width: 30px;
height: 30px;
border: 1px solid #ccc;
}
}
dd {
font-size: bold;
font-size: 14px;
padding: 0 0 0 10px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
.template-c {
padding: 10px 0;
li {
line-height: 40px;
font-size: 14px;
border-bottom: 1px solid #dcdfe6;
cursor: pointer;
display: flex;
justify-content: space-between;
span {
flex: 1;
padding-right: 10px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
i {
font-size: 14px;
color: #2d8cf0;
width: 20px;
line-height: 40px;
}
}
}
.container {
flex: 1;
}
.operating {
position: absolute;
z-index: 999;
right: 20px;
top: 10px;
padding: 5px 10px;
border-radius: 6px;
background-color: #ffffff;
border: 1px solid rgb(187, 187, 187);
box-shadow: 1px 1px 4px 0 #0a0a0a2e;
display: flex;
height: 34px;
align-items: center;
.el-select {
transition: width 0.6s ease-in-out;
::v-deep .el-input__inner {
height: 26px;
line-height: 26px;
}
::v-deep .el-input--mini .el-input__icon {
line-height: 26px;
}
::v-deep .el-select-dropdown__item {
height: 48px;
max-width: 410px;
line-height: 48px;
}
&.hideSelect {
width: 0px;
::v-deep .el-input__inner {
display: none;
}
::v-deep .el-input__suffix {
display: none;
}
}
&.showSelect {
width: 180px;
::v-deep .el-input__inner {
display: block;
}
::v-deep .el-input__suffix {
display: block;
}
}
}
.icon_oper {
svg {
font-size: 18px;
cursor: pointer;
margin: 0 5px;
&:hover {
color: #2d8cf0;
}
&.opacity {
opacity: 0.5;
}
}
}
}
}
.app-mini {
position: fixed;
z-index: 999;
bottom: 10px;
right: 20px;
border-radius: 6px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}
.app-content {
flex: 1;
height: 100% !important;
}
::v-deep .x6-graph-scroller {
border: 1px solid #f0f0f0;
margin-left: -1px;
width: 100% !important;
height: 100% !important;
}
.head_top {
width: 100%;
height: 48px;
display: flex;
align-items: center;
.code_warp {
width: 90%;
height: 100%;
font-size: 12px;
margin-left: 8px;
display: flex;
gap: 4px;
flex-direction: column;
justify-content: center;
.code {
color: black;
font-weight: 700;
line-height: normal;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
}
.name {
color: #b3b2bf;
font-weight: 600;
line-height: normal;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
}
}
}
::v-deep .text {
height: 32px;
display: flex;
gap: 1px;
font-size: 13px;
position: relative;
padding-left: 20px;
align-items: center;
svg {
position: absolute;
left: 4px;
top: 10px;
}
.type {
width: 25%;
height: 24px;
font-size: 12px;
line-height: 24px;
text-align: center;
border-radius: 4px;
margin-right: 5px;
display: inline-block;
background-color: #f7f7f9;
}
span {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
line-height: 18px;
}
&:hover {
background: #f8f8fa;
}
}
</style>
GitHub 加速计划 / vu / vue
207.54 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:2 个月前 )
73486cb5
* chore: fix link broken
Signed-off-by: snoppy <michaleli@foxmail.com>
* Update packages/template-compiler/README.md [skip ci]
---------
Signed-off-by: snoppy <michaleli@foxmail.com>
Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 4 个月前
e428d891
Updated Browser Compatibility reference. The previous currently returns HTTP 404. 5 个月前
更多推荐
已为社区贡献1条内容
所有评论(0)