前言
作为小约翰可汗的歌迷,他的视频我都看过了至少一遍。每次听到这句因为那个敢于为劳动者说话的超级强权已经不在了 都会感到震撼,每次重看布基纳法索,也就是上沃尔特的故事都会感到惋惜。历史的发展有其客观规律,但是荒诞的事情也时有发生,以史为鉴可知兴替,但是人们时常忘记过去的经验教训,容易好了伤疤忘了疼。
预览
很显然,小约翰可汗的"通辽宇宙"还没有一幅地图。为了直观的感受通辽宇宙,也为了记住一些发生的事,我做了一幅"通辽宇宙地图 (lishiyuan.cn)"。点击链接可以查看,预览如下:
地图标出了已经被小约翰讲过的奇葩小国,以及通辽宇宙的基本面积单位------通辽市和基本人口单位------天通苑与回龙观,点击地图区域可以查看该地区已经发生的故事。目前奇葩小国集中在非洲大区与南美洲大区,硬核狠人最多的国家是英国与美国。
设计
在最初的设计中,我只想标识出已经讲过的奇葩小国,后面加上了单位、神奇组织和硬核狠人的信息。 人们有时会为了学习某项技术而实现某个项目,有时是为了实现某个项目而学习的某些技能。本项目属于后后面的情况,项目是自己想写,然后为了实现这个项目而学习了很多东西。
技术选型
本项目使用Python写爬虫爬取小约翰的投稿数据与信息数据,使用Echarts绘制地图,使用vue + antdv构建前端界面。
实现
主要包括两个部分:数据部分与界面部分。
数据部分
数据部分主要是数据的爬取与整理。这里使用写python爬取B站的合集信息(小约翰创建了奇葩小国、硬核狠人与神奇组织三个合集)。这里使用到了requests与json库。
python
# 合集信息 https://api.bilibili.com/x/polymer/web-space/seasons_archives_list?mid=23947287&season_id=665&sort_reverse=false&page_num=1&page_size=30
def seasons_archives_list(mid,seasonId,pageNum):
url = 'https://api.bilibili.com/x/polymer/web-space/seasons_archives_list'
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69'
}
params = {
"mid":mid,
"season_id":seasonId,
"page_num":pageNum,
"sort_reverse": True,
"page_size":30
}
json = requests.get(url,params=params,headers=headers).json()
return json['data']
同时,这里也爬取了小约翰的一些账户信息。
python
# 获取用户信息
# https://api.bilibili.com/x/space/wbi/acc/info?mid=23947287&token=&platform=web&web_location=1550101&w_rid=76d46ebbed24101710fdcb030df91d61&wts=1693982211
def upinfo(mid):
url = 'https://api.bilibili.com/x/space/wbi/acc/info'
headers = {
'cookie':'buvid3=C1571E59-20C1-B15C-9FBD-41C0001894EB67936infoc;' ,
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69',
}
params = {
"mid":mid,
}
json = requests.get(url,params=params,headers=headers).json()
return json['data']
# up主统计数据 https://api.bilibili.com/x/relation/stat?vmid=23947287&w_rid=e6a6e31cc15fa5b806f9f1ad6ce548bb&wts=1695312456&web_location=333.999
def up_stat(mid):
url = 'https://api.bilibili.com/x/relation/stat'
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69'
}
params = {
"vmid":mid,
}
json = requests.get(url,params=params,headers=headers).json()
return json['data']
数据整理
- 小约翰信息数据
python
def john_info():
mid = 23947287
up_stat_info= up_stat(mid)
upinfo_json= upinfo(mid)
john ={
"mid": mid,
"name": upinfo_json['name'],
"sign": upinfo_json['sign'],
"avatar": upinfo_json['face'],
"birthday": upinfo_json['birthday'],
"sex": upinfo_json['sex'],
"title": upinfo_json['official']['title'],
"follower": up_stat_info['follower'],
"space":"https://space.bilibili.com/{0}".format(mid),
"live_room": upinfo_json["live_room"]["url"],
}
path= saveFile(john['avatar'])
john['avatar'] = path
saveInfo(john,"john.json")
return
- 稿件信息数据
python
def season(mid,seasonId,fileName):
pageNum = 1
bvBathPath = "https://www.bilibili.com/video/{bvid}"
durationInfo = "{m}分{s}秒"
all_archives=[]
dataInFile = readFile(fileName=fileName)
while True :
seasons_archives= seasons_archives_list(mid,seasonId,pageNum)
page = seasons_archives['page']
archives = seasons_archives['archives']
for item in archives :
data = None
for f in dataInFile:
if f['bvid'] == item['bvid']:
data = f
all_archives.append({
"countryName":data['countryName'] if data != None and 'countryName' in data else ['#'],
"leader":data['leader'] if data != None and 'leader' in data else ['#'],
"personName":data['personName'] if data != None and 'personName' in data else ['#'],
"organizationName":data['organizationName'] if data != None and 'organizationName' in data else ['#'],
"name": item['title'],
"bvid": item['bvid'],
"aid": item['aid'],
"cover": item['pic'],
"view": item['stat']['view'],
'duration': durationInfo.format(m=math.floor( item['duration'] / 60 ),s=item['duration'] % 60),
"pub_date": time.strftime("%Y-%m-%d", time.localtime(item['pubdate'])) ,
"url": bvBathPath.format(bvid=item['bvid'])
})
if(page['page_num'] * page['page_size'] >= page['total']):
break
pageNum+=1
# 下载封面
for item in all_archives:
path= saveFile(item['cover'])
item['cover'] = path
saveInfo(all_archives,fileName)
return
注意
- 接口校验规则可能会变化,爬取数据可能会失败。
- 人物信息、地区信息和组织信息虽然在有些视频简介会标出来,但是有一部分是没有的,这里选择手动补全。这里最省力的方式应该是提取音频转成文字然后通过AI提取我们需要的关键信息。
- 世界地图数据可以在其他开源项目中找到。
- 完整代码请查看文章末尾的github项目地址。
前端界面
前端界面核心两部分,地图渲染与事件处理。
地图渲染
首先引入Echarts相关组件:
ts
import * as echarts from 'echarts/core';
import {
TitleComponent,
VisualMapComponent,
GeoComponent,
} from 'echarts/components';
import type {
TitleComponentOption,
VisualMapComponentOption,
GeoComponentOption,
} from 'echarts/components'
import { MapChart, EffectScatterChart, CustomChart } from 'echarts/charts';
import type { MapSeriesOption, EffectScatterSeriesOption, CustomSeriesOption } from 'echarts/charts';
import { CanvasRenderer } from 'echarts/renderers';
导入世界地图数据与奇葩小国数据
ts
import usaJson from '@/assets/world.geo.json'
import country from '@/assets/country.json'
渲染基本地图信息
ts
echarts.use([
TitleComponent,
VisualMapComponent,
GeoComponent,
MapChart,
EffectScatterChart,
CustomChart,
CanvasRenderer
]);
type EChartsOption = echarts.ComposeOption<
| TitleComponentOption
| VisualMapComponentOption
| GeoComponentOption
| MapSeriesOption
| EffectScatterSeriesOption
| CustomSeriesOption
>;
let mapChart: any
let option: EChartsOption
let countrySet = new Set<string>(country.flatMap(item => item.countryName))
let countryList = Array.from(countrySet).map(item => {
return {name:item,value:1}
})
onMounted(() => {
const chartDom = document.getElementById('map')!
resize()
mapChart = echarts.init(chartDom, 'dark');
mapChart.showLoading();
const axios = inject<Axios>("axios")
mapChart.hideLoading();
echarts.registerMap('tongliao-world', usaJson as any, {
// 此处需要将回龙观、天通苑与通辽标出
});
option = {
title: {
show: true,
text: '通辽宇宙地图',
borderColor: "#FFF",
// 殷红
textStyle: {
color: "#82111f"
},
subtextStyle: {
color: "#82111f"
},
subtext: 'the map of tongliao universe',
top: 64,
right: 64
},
// 藏花红、金黄、姜红
visualMap: [
{
type: 'piecewise',
show: true,
hoverLink: false,
itemWidth: 32,
id: 'color',
seriesIndex: 0,
pieces: [
{ gte: 1, lte: 1, label: "奇葩小国", color: "#ec2d7a" },
// { gte: 2, lte: 2, label: "硬核狠人", color: "#4d1018" },
// { gte: 3, lte: 3, label: "神奇组织", color: "#45b787" },
{ gte: 4, lte: 4, label: "新大陆", color: "#eeb8c3" },
{ gte: 5, lte: 5, label: "计量单位", color: "#f26b1f" },
],
inverse: true,
orient: 'vertical',
left: 64,
top: 64,
selectedMode: false,
textStyle: {
color: "#FFF"
}
},
],
geo: [
{
name: '奇葩小国',
type: 'map',
roam: true,
zoom: 1.1,
selectedMode: false,
map: 'tongliao-world',
label: {
color: "#FFF",
},
itemStyle: {
areaColor: "#eeb8c3",
borderWidth: 0
},
emphasis: {
label: {
color: "#FFF",
show: true
},
itemStyle: {
areaColor: "#1661ab",
shadowColor: 'rgba(0, 0, 0, 0.7)',
shadowBlur: 10
}
}
}
],
series: [
{
name: '奇葩小国',
type: 'map',
geoIndex: 0,
selectedMode: false,
map: 'tongliao-world',
data: countryList
}
]
};
})
面积单位与人口单位需要添加新的serie渲染出来,其中通辽需要自定义渲染器才能展示出来。
ts
series: [
{
name: '奇葩小国',
type: 'map',
geoIndex: 0,
selectedMode: false,
map: 'tongliao-world',
data: countryList
},
{
name: '人口单位',
type: 'effectScatter',
geoIndex: 0,
selectedMode: false,
coordinateSystem: 'geo',
data: [
{
name: "天通苑",
value: [116.420862, 40.061552],
itemStyle: {
color: "#f26b1f"
}
},
{
name: "回龙观",
value: [116.324618, 40.064687],
itemStyle: {
color: "#f26b1f"
},
}
],
symbolSize: 4,
label: {
formatter: '{b}',
color:'#fff',
textBorderColor: '#000',
textBorderWidth: 2,
position: 'right',
show: true
},
},
{
name: "面积单位",
type: 'custom',
geoIndex: 0,
coordinateSystem: 'geo',
selectedMode: false,
data: [['通辽']],
renderItem: (params: any, api: any)=> {
let coords = [
[123.087539, 44.522239],
[123.399602, 44.139572],
[123.577924, 43.646975],
[123.355022, 43.528576],
[123.741386, 43.301896],
[123.488763, 43.02007],
[123.280721, 42.802395],
[122.626874, 42.824197],
[122.374251, 42.682347],
[121.973026, 42.660495],
[121.586662, 42.474443],
[121.111137, 42.243849],
[120.858514, 42.232847],
[120.427569, 43.052654],
[120.769353, 43.366749],
[120.48701, 43.388351],
[120.992255, 43.603948],
[120.813933, 43.7866],
[120.769353, 44.075551],
[120.368128, 44.363098],
[119.253616, 45.269626],
[119.283336, 45.447136],
[119.550819, 45.634481],
[119.996624, 45.457561],
[120.011484, 45.634481],
[120.293827, 45.572102],
[120.947675, 45.196371],
[121.958166, 44.320587],
[122.463412, 44.235473],
[123.087539, 44.522239]
]
let points = []
for (let i = 0; i < coords.length; i++) {
points.push(api.coord(coords[i]))
}
return {
type: 'polygon',
shape: {
points: points
},
style: {
fill: '#f26b1f'
},
emphasis: {
style: {
shadowColor: 'rgba(0, 0, 0, 0.7)',
shadowBlur: 10
}
},
focus:'self',
textContent: {
type:'text',
style: {
textStroke: '#000',
lineWidth:2,
text: api.value(0)
}
},
textConfig: {
insideFill:'#FFF',
position: 'inside',
},
}
}
}
]
为了响应事件,需要向外发送区域点击事件,需要响应resize操作
ts
const clickArea = defineEmits(['clickArea'])
mapChart.on("click", (params: any) => {
clickArea('clickArea', params)
})
window.onresize = () => {
resize()
mapChart.resize()
};
function resize() {
chartDom.style.width = window.innerWidth + 'px'
chartDom.style.height = window.innerHeight + 'px'
}
function mapResize() {
console.log('mapResize')
mapChart.setOption(option, true)
}
defineExpose({
mapResize,
})
完整代码
html
<script setup lang="ts">
// 地图主页
import * as echarts from 'echarts/core';
import {
TitleComponent,
VisualMapComponent,
GeoComponent,
} from 'echarts/components';
import type {
TitleComponentOption,
VisualMapComponentOption,
GeoComponentOption,
} from 'echarts/components'
import { MapChart, EffectScatterChart, CustomChart } from 'echarts/charts';
import type { MapSeriesOption, EffectScatterSeriesOption, CustomSeriesOption } from 'echarts/charts';
import { CanvasRenderer } from 'echarts/renderers';
import { inject, onMounted } from 'vue';
import type { Axios } from 'axios';
import usaJson from '@/assets/world.geo.json'
import country from '@/assets/country.json'
const clickArea = defineEmits(['clickArea'])
echarts.use([
TitleComponent,
VisualMapComponent,
GeoComponent,
MapChart,
EffectScatterChart,
CustomChart,
CanvasRenderer
]);
type EChartsOption = echarts.ComposeOption<
| TitleComponentOption
| VisualMapComponentOption
| GeoComponentOption
| MapSeriesOption
| EffectScatterSeriesOption
| CustomSeriesOption
>;
let mapChart: any
let option: EChartsOption
let countrySet = new Set<string>(country.flatMap(item => item.countryName))
let countryList = Array.from(countrySet).map(item => {
return {name:item,value:1}
})
onMounted(() => {
const chartDom = document.getElementById('map')!
resize()
mapChart = echarts.init(chartDom, 'dark');
mapChart.showLoading();
const axios = inject<Axios>("axios")
mapChart.hideLoading();
echarts.registerMap('tongliao-world', usaJson as any, {
// 此处需要将回龙观、天通苑与通辽标出
});
option = {
title: {
show: true,
text: '通辽宇宙地图',
borderColor: "#FFF",
// 殷红
textStyle: {
color: "#82111f"
},
subtextStyle: {
color: "#82111f"
},
subtext: 'the map of tongliao universe',
top: 64,
right: 64
},
// 藏花红、金黄、姜红
visualMap: [
{
type: 'piecewise',
show: true,
hoverLink: false,
itemWidth: 32,
id: 'color',
seriesIndex: 0,
pieces: [
{ gte: 1, lte: 1, label: "奇葩小国", color: "#ec2d7a" },
// { gte: 2, lte: 2, label: "硬核狠人", color: "#4d1018" },
// { gte: 3, lte: 3, label: "神奇组织", color: "#45b787" },
{ gte: 4, lte: 4, label: "新大陆", color: "#eeb8c3" },
{ gte: 5, lte: 5, label: "计量单位", color: "#f26b1f" },
],
inverse: true,
orient: 'vertical',
left: 64,
top: 64,
selectedMode: false,
textStyle: {
color: "#FFF"
}
},
],
geo: [
{
name: '奇葩小国',
type: 'map',
roam: true,
zoom: 1.1,
selectedMode: false,
map: 'tongliao-world',
label: {
color: "#FFF",
},
itemStyle: {
areaColor: "#eeb8c3",
borderWidth: 0
},
emphasis: {
label: {
color: "#FFF",
show: true
},
itemStyle: {
areaColor: "#1661ab",
shadowColor: 'rgba(0, 0, 0, 0.7)',
shadowBlur: 10
}
}
}
],
series: [
{
name: '奇葩小国',
type: 'map',
geoIndex: 0,
selectedMode: false,
map: 'tongliao-world',
data: countryList
},
{
name: '人口单位',
type: 'effectScatter',
geoIndex: 0,
selectedMode: false,
coordinateSystem: 'geo',
data: [
{
name: "天通苑",
value: [116.420862, 40.061552],
itemStyle: {
color: "#f26b1f"
}
},
{
name: "回龙观",
value: [116.324618, 40.064687],
itemStyle: {
color: "#f26b1f"
},
}
],
symbolSize: 4,
label: {
formatter: '{b}',
color:'#fff',
textBorderColor: '#000',
textBorderWidth: 2,
position: 'right',
show: true
},
},
{
name: "面积单位",
type: 'custom',
geoIndex: 0,
coordinateSystem: 'geo',
selectedMode: false,
data: [['通辽']],
renderItem: (params: any, api: any)=> {
let coords = [
[123.087539, 44.522239],
[123.399602, 44.139572],
[123.577924, 43.646975],
[123.355022, 43.528576],
[123.741386, 43.301896],
[123.488763, 43.02007],
[123.280721, 42.802395],
[122.626874, 42.824197],
[122.374251, 42.682347],
[121.973026, 42.660495],
[121.586662, 42.474443],
[121.111137, 42.243849],
[120.858514, 42.232847],
[120.427569, 43.052654],
[120.769353, 43.366749],
[120.48701, 43.388351],
[120.992255, 43.603948],
[120.813933, 43.7866],
[120.769353, 44.075551],
[120.368128, 44.363098],
[119.253616, 45.269626],
[119.283336, 45.447136],
[119.550819, 45.634481],
[119.996624, 45.457561],
[120.011484, 45.634481],
[120.293827, 45.572102],
[120.947675, 45.196371],
[121.958166, 44.320587],
[122.463412, 44.235473],
[123.087539, 44.522239]
]
let points = []
for (let i = 0; i < coords.length; i++) {
points.push(api.coord(coords[i]))
}
return {
type: 'polygon',
shape: {
points: points
},
style: {
fill: '#f26b1f'
},
emphasis: {
style: {
shadowColor: 'rgba(0, 0, 0, 0.7)',
shadowBlur: 10
}
},
focus:'self',
textContent: {
type:'text',
style: {
textStroke: '#000',
lineWidth:2,
text: api.value(0)
}
},
textConfig: {
insideFill:'#FFF',
position: 'inside',
},
}
}
}
]
};
mapChart.setOption(option);
mapChart.on("click", (params: any) => {
clickArea('clickArea', params)
})
window.onresize = () => {
resize()
mapChart.resize()
};
function resize() {
chartDom.style.width = window.innerWidth + 'px'
chartDom.style.height = window.innerHeight + 'px'
}
})
function mapResize() {
console.log('mapResize')
mapChart.setOption(option, true)
}
defineExpose({
mapResize,
})
</script>
<template>
<div id="map"></div>
</template>
<style scoped></style>
事件处理
主要处理区域点击、界面重置与打开关于页
ts
function clickArea(params: any) {
// 如果data有数据则打开弹窗
console.log(params)
let data= params.data
let name = '';
if(data instanceof Array){
name = data[0]
}else{
if(data==undefined){
name = params.name
}else{
name = data?.name
}
}
// 通辽,回龙观,天通苑
if('通辽' == name){
activeUnit.value = unitInfos.tongliao
unitInfoVisible.value = true
}else if('回龙观' == name){
activeUnit.value = unitInfos.huilongguang
unitInfoVisible.value = true
}else if('天通苑' == name){
activeUnit.value = unitInfos.tiantongyuan
unitInfoVisible.value = true
}else{
// 从json筛选数据
activeTitle.value = name
personInfo.value = []
countryInfo.value = []
organizationInfo.value = []
personInfo.value = person.filter(it=>it.countryName.includes(name))
countryInfo.value = country.filter(it=>it.countryName.includes(name))
organizationInfo.value = organization.filter(it=>it.countryName.includes(name))
aeraInfoVisible.value = true
}
}
function toAbout() {
router.push({ name: "about" })
}
function resize() {
map.value.mapResize()
}
完整代码
html
<script setup lang="ts">
import MapView from './map/MapView.vue';
import {
QuestionCircleOutlined,
SyncOutlined,
ShareAltOutlined,
EyeOutlined,
CopyOutlined
} from '@ant-design/icons-vue';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import john from '@/assets/john.json'
import liganma from '@/assets/liganma.json'
import ClipboardJS from 'clipboard'
import { message } from 'ant-design-vue';
import Card from '@/components/Card.vue';
import List from '@/components/List.vue';
import country from '@/assets/country.json'
import person from '@/assets/person.json'
import organization from '@/assets/organization.json'
const [messageApi, contextHolder] = message.useMessage();
const map = ref()
const qrcodeVisible = ref<boolean>(false);
const shareVisible = ref<boolean>(false);
const aeraInfoVisible = ref<boolean>(false);
const unitInfoVisible = ref<boolean>(false)
const activeTitle = ref<string>();
const personInfo = ref<Array<{
}>>([]);
const countryInfo = ref<Array<{
}>>([]);
const organizationInfo = ref<Array<{
}>>([]);
const href = window.location.href
const followInfo = {
john: john,
liganma: liganma,
}
const router = useRouter()
function toAbout() {
router.push({ name: "about" })
}
function resize() {
map.value.mapResize()
}
function share() {
console.log(href)
// 打开弹窗展示二维码
shareVisible.value = true
new ClipboardJS("#copyShareLink")
}
function follow() {
// 打开李干嘛与小约翰的弹窗
qrcodeVisible.value = true
}
const unitInfos= {
tongliao:{
title: "内蒙古通辽市",
sign: "通辽,简写T,通辽宇宙基本面积单位。总面积59535平方千米。截至2022年10月,全市辖1个区、1个县和5个旗,代管1个县级市。"
},
huilongguang:{
title:"北京回龙观社区",
sign: "回龙观,通辽宇宙基本人口小单位。回龙观是具有850万平方米的超大规模社区,常驻人口将达到30万人。"
},
tiantongyuan:{
title:"北京天通苑社区",
sign: "天通苑,通辽宇宙基本人口大单位。天通苑是亚洲最大社区,人口约50万人。"
}
}
const activeUnit = ref<{
title?:string,
sign?:string
}>()
function clickArea(params: any) {
// 如果data有数据则打开弹窗
console.log(params)
let data= params.data
let name = '';
if(data instanceof Array){
name = data[0]
}else{
if(data==undefined){
name = params.name
}else{
name = data?.name
}
}
// 通辽,回龙观,天通苑
if('通辽' == name){
activeUnit.value = unitInfos.tongliao
unitInfoVisible.value = true
}else if('回龙观' == name){
activeUnit.value = unitInfos.huilongguang
unitInfoVisible.value = true
}else if('天通苑' == name){
activeUnit.value = unitInfos.tiantongyuan
unitInfoVisible.value = true
}else{
// 从json筛选数据
activeTitle.value = name
personInfo.value = []
countryInfo.value = []
organizationInfo.value = []
personInfo.value = person.filter(it=>it.countryName.includes(name))
countryInfo.value = country.filter(it=>it.countryName.includes(name))
organizationInfo.value = organization.filter(it=>it.countryName.includes(name))
aeraInfoVisible.value = true
}
}
</script>
<template>
<div class="content">
<MapView ref="map" @click-area="clickArea"></MapView>
<!-- <Card></Card> -->
<a-float-button-group shape="square" :style="{ right: '64px' }">
<!-- 关于 -->
<a-float-button @click="toAbout" tooltip="关于">
<template #icon>
<QuestionCircleOutlined />
</template>
</a-float-button>
<!-- 同步 -->
<a-float-button @click="resize" tooltip="重置地图">
<template #icon>
<SyncOutlined />
</template>
</a-float-button>
<!-- 分享 -->
<a-float-button @click="share" tooltip="分享">
<template #icon>
<ShareAltOutlined />
</template>
</a-float-button>
<!-- 关注 -->
<a-float-button @click="follow" tooltip="关注">
<template #icon>
<EyeOutlined />
</template>
</a-float-button>
</a-float-button-group>
<div class="models">
<a-modal v-model:open="qrcodeVisible" title="感谢关注" centered @ok="qrcodeVisible = false">
<a-row :gutter="[16, 24]" justify="space-around" >
<a-col :span="11" justify="space-around" align="middle" class="space-box">
<Card :title="followInfo.john.name" :avatar="'image/'+ followInfo.john.avatar" :qrcode="followInfo.john.space" :sign="followInfo.john.sign"></Card>
</a-col>
<a-col :span="11" justify="space-around" align="middle" class="space-box">
<Card :title="followInfo.liganma.name" :avatar="'image/'+ followInfo.liganma.avatar" :qrcode="followInfo.liganma.space" :sign="followInfo.liganma.sign"></Card>
</a-col>
</a-row>
<template #footer>
</template>
</a-modal>
<a-modal v-model:open="shareVisible" title="分享给朋友" centered @ok="shareVisible = false">
<a-row :gutter="[16, 24]" justify="space-around" align="middle">
<a-col :span="24" justify="space-around" align="middle">
<a-space direction="vertical" style="width: 100%;">
<a-input-group compact>
<a-button>
链接
</a-button>
<a-input :value="href" style="width: calc(100% - 200px)" id="shareLink" />
<a-tooltip title="复制链接">
<a-button id="copyShareLink" data-clipboard-action="copy" data-clipboard-target="#shareLink">
<template #icon>
<CopyOutlined />
</template>
</a-button>
</a-tooltip>
</a-input-group>
<a-qrcode error-level="H" :value="href" icon="favicon.ico" />
</a-space>
</a-col>
</a-row>
<template #footer></template>
</a-modal>
<a-modal v-model:open="aeraInfoVisible" :title="activeTitle" centered @ok="aeraInfoVisible = false" width="1080px" >
<a-row justify="space-around" >
<a-col :span="7" justify="space-around" align="middle" class="space-box">
<List title="奇葩小国" :list="countryInfo"></List>
</a-col>
<a-col :span="7" justify="space-around" align="middle" class="space-box">
<List title="硬核狠人" :list="personInfo"></List>
</a-col>
<a-col :span="7" justify="space-around" align="middle" class="space-box">
<List title="神奇组织" :list="organizationInfo"></List>
</a-col>
</a-row>
<template #footer></template>
</a-modal>
<a-modal v-model:open="unitInfoVisible" title="单位信息" centered @ok="unitInfoVisible = false" >
<a-row justify="space-around" >
<a-col :span="24" justify="space-around" align="middle">
<Card :title="activeUnit?.title" :sign="activeUnit?.sign" ></Card>
</a-col>
</a-row>
<template #footer></template>
</a-modal>
</div>
</div>
</template>
<style scoped>
.content {
position: relative;
}
.space-box{
box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px;
border-radius: 15px;
padding: 10px;
}
</style>
其他部分
dockerfile
dockerfile
FROM nginx:mainline-alpine3.18-slim
COPY ./dist /usr/share/nginx/html
打包
npm run build-only
打包镜像
docker build -t tongliao-universe .
docker部署
docker run -p 8080:80 --name tongliao-universe -d tongliao-universe
注意
- 项目地址:GitHub - imlishiyuan/Tongliao-Universe: 作为小约翰可汗的歌迷,为大可汗献通辽宇宙地图
- 转载与引用请标明作者与出处。