增加VIP的功能

This commit is contained in:
街角小林 2025-02-20 16:57:51 +08:00
parent 6c65826a87
commit f6a72e02c8
21 changed files with 650 additions and 53 deletions

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 2479351 */
src: url('iconfont.woff2?t=1739843331607') format('woff2'),
url('iconfont.woff?t=1739843331607') format('woff'),
url('iconfont.ttf?t=1739843331607') format('truetype');
src: url('iconfont.woff2?t=1740018646112') format('woff2'),
url('iconfont.woff?t=1740018646112') format('woff'),
url('iconfont.ttf?t=1740018646112') format('truetype');
}
.iconfont {
@ -13,6 +13,10 @@
-moz-osx-font-smoothing: grayscale;
}
.iconhuiyuan-:before {
content: "\e615";
}
.iconAIshengcheng:before {
content: "\e6b5";
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 KiB

View File

@ -156,3 +156,5 @@ export const rainbowLinesOptions = [
]
}
]
export const vipFileUrl = 'https://simple-mind-map.oss-cn-beijing.aliyuncs.com/vip.json'

View File

@ -1,5 +1,6 @@
import { BrowserWindow, ipcMain, shell } from 'electron'
import { saveClientConfig, getClientConfig } from './storage'
import { execSync } from 'child_process'
export const bindOtherHandleEvent = () => {
// 处理缩放事件
@ -26,4 +27,61 @@ export const bindOtherHandleEvent = () => {
ipcMain.handle('getClientConfig', () => {
return getClientConfig()
})
// 获取机器码
ipcMain.handle('getClientUUID', () => {
try {
if (process.platform === 'win32') {
const stdout = execSync('wmic csproduct get uuid', {
windowsHide: true
})
return stdout
.toString()
.split('\n')[1]
.trim()
} else if (process.platform === 'darwin') {
const stdout = execSync('wmic csproduct get uuid', {
windowsHide: true
})
return stdout
.toString()
.split('\n')[1]
.trim()
} else if (process.platform === 'linux') {
if (require('fs').existsSync('/etc/machine-id')) {
return require('fs')
.readFileSync('/etc/machine-id')
.toString()
.trim()
}
const stdout = execSync('sudo dmidecode -s system-uuid', {
timeout: 1000
})
return stdout.toString().trim()
}
} catch (e) {
return [
process.arch,
process.env.COMPUTERNAME || process.env.HOSTNAME,
Math.random()
.toString(36)
.slice(2)
].join(':')
}
})
//
ipcMain.handle('openExternal', (event, url) => {
return new Promise((resolve, reject) => {
shell
.openExternal(url)
.then(() => {
resolve()
})
.catch(err => {
console.error('openExternal失败:', err)
reject(err)
})
})
})
}

View File

@ -40,5 +40,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
getIsMaximize: id => ipcRenderer.invoke('getIsMaximize', id),
selectOpenFolder: () => ipcRenderer.invoke('selectOpenFolder'),
getFilesInDir: (dir, ext) => ipcRenderer.invoke('getFilesInDir', dir, ext),
checkFileExist: filePath => ipcRenderer.invoke('checkFileExist', filePath)
checkFileExist: filePath => ipcRenderer.invoke('checkFileExist', filePath),
getClientUUID: () => ipcRenderer.invoke('getClientUUID'),
openExternal: url => ipcRenderer.invoke('openExternal', url),
})

View File

@ -93,14 +93,14 @@ import Themes from 'simple-mind-map-plugin-themes'
//
// import Cooperate from 'simple-mind-map/src/plugins/Cooperate.js'
// FreemindExcel线
// import HandDrawnLikeStyle from 'simple-mind-map-plugin-handdrawnlikestyle'
// import Notation from 'simple-mind-map-plugin-notation'
// import Numbers from 'simple-mind-map-plugin-numbers'
// import Freemind from 'simple-mind-map-plugin-freemind'
// import Excel from 'simple-mind-map-plugin-excel'
// import Checkbox from 'simple-mind-map-plugin-checkbox'
// import LineFlow from 'simple-mind-map-plugin-lineflow'
// import Momentum from 'simple-mind-map-plugin-momentum'
import HandDrawnLikeStyle from 'simple-mind-map-plugin-handdrawnlikestyle'
import Notation from 'simple-mind-map-plugin-notation'
import Numbers from 'simple-mind-map-plugin-numbers'
import Freemind from 'simple-mind-map-plugin-freemind'
import Excel from 'simple-mind-map-plugin-excel'
import Checkbox from 'simple-mind-map-plugin-checkbox'
import LineFlow from 'simple-mind-map-plugin-lineflow'
import Momentum from 'simple-mind-map-plugin-momentum'
// npm link simple-mind-map-plugin-excel simple-mind-map-plugin-freemind simple-mind-map-plugin-numbers simple-mind-map-plugin-notation simple-mind-map-plugin-handdrawnlikestyle simple-mind-map-plugin-checkbox simple-mind-map simple-mind-map-plugin-themes simple-mind-map-plugin-lineflow simple-mind-map-plugin-momentum
import OutlineSidebar from './OutlineSidebar'
import Style from './Style'
@ -147,6 +147,8 @@ import AssociativeLineStyle from './AssociativeLineStyle.vue'
import NodeImgPlacementToolbar from './NodeImgPlacementToolbar.vue'
import AiCreate from './AiCreate.vue'
import AiChat from './AiChat.vue'
import { vipFileUrl } from '@/config/constant'
import axios from 'axios'
//
MindMap.usePlugin(MiniMap)
@ -235,7 +237,8 @@ export default {
extraTextOnExport: state => state.extraTextOnExport,
isDragOutlineTreeNode: state => state.isDragOutlineTreeNode,
isDark: state => state.localConfig.isDark,
enableAi: state => state.localConfig.enableAi
enableAi: state => state.localConfig.enableAi,
isVIP: state => state.isVIP
})
},
watch: {
@ -269,6 +272,7 @@ export default {
}
},
async mounted() {
await this.getVipInfo()
showLoading()
// this.showNewFeatureInfo()
await this.getData()
@ -305,7 +309,20 @@ export default {
this.mindMap.destroy()
},
methods: {
...mapMutations(['setFileName', 'setIsUnSave']),
...mapMutations(['setFileName', 'setIsUnSave', 'setIsVIP']),
//
async getVipInfo() {
try {
const clientUUID = await window.electronAPI.getClientUUID()
const { data } = await axios.get(vipFileUrl, {
responseType: 'json'
})
this.setIsVIP(data.includes(clientUUID))
} catch (error) {
console.log(error)
}
},
handleStartTextEdit() {
this.mindMap.renderer.startTextEdit()
@ -735,39 +752,41 @@ export default {
loadPlugins() {
if (this.openNodeRichText) this.addRichTextPlugin()
if (this.isShowScrollbar) this.addScrollbarPlugin()
if (typeof HandDrawnLikeStyle !== 'undefined') {
this.$store.commit('setSupportHandDrawnLikeStyle', true)
if (this.isUseHandDrawnLikeStyle) this.addHandDrawnLikeStylePlugin()
}
if (typeof Momentum !== 'undefined') {
this.$store.commit('setSupportMomentum', true)
if (this.isUseMomentum) this.addMomentumPlugin()
}
if (typeof Notation !== 'undefined') {
this.mindMap.addPlugin(Notation)
this.$store.commit('setSupportMark', true)
}
if (typeof Numbers !== 'undefined') {
this.mindMap.addPlugin(Numbers)
this.$store.commit('setSupportNumbers', true)
}
if (typeof Freemind !== 'undefined') {
this.mindMap.addPlugin(Freemind)
this.$store.commit('setSupportFreemind', true)
Vue.prototype.Freemind = Freemind
}
if (typeof Excel !== 'undefined') {
this.mindMap.addPlugin(Excel)
this.$store.commit('setSupportExcel', true)
Vue.prototype.Excel = Excel
}
if (typeof Checkbox !== 'undefined') {
this.mindMap.addPlugin(Checkbox)
this.$store.commit('setSupportCheckbox', true)
}
if (typeof LineFlow !== 'undefined') {
this.mindMap.addPlugin(LineFlow)
this.$store.commit('setSupportLineFlow', true)
if (this.isVIP) {
if (typeof HandDrawnLikeStyle !== 'undefined') {
this.$store.commit('setSupportHandDrawnLikeStyle', true)
if (this.isUseHandDrawnLikeStyle) this.addHandDrawnLikeStylePlugin()
}
if (typeof Momentum !== 'undefined') {
this.$store.commit('setSupportMomentum', true)
if (this.isUseMomentum) this.addMomentumPlugin()
}
if (typeof Notation !== 'undefined') {
this.mindMap.addPlugin(Notation)
this.$store.commit('setSupportMark', true)
}
if (typeof Numbers !== 'undefined') {
this.mindMap.addPlugin(Numbers)
this.$store.commit('setSupportNumbers', true)
}
if (typeof Freemind !== 'undefined') {
this.mindMap.addPlugin(Freemind)
this.$store.commit('setSupportFreemind', true)
Vue.prototype.Freemind = Freemind
}
if (typeof Excel !== 'undefined') {
this.mindMap.addPlugin(Excel)
this.$store.commit('setSupportExcel', true)
Vue.prototype.Excel = Excel
}
if (typeof Checkbox !== 'undefined') {
this.mindMap.addPlugin(Checkbox)
this.$store.commit('setSupportCheckbox', true)
}
if (typeof LineFlow !== 'undefined') {
this.mindMap.addPlugin(LineFlow)
this.$store.commit('setSupportLineFlow', true)
}
}
},

View File

@ -5,10 +5,39 @@
</template>
<script>
import axios from 'axios'
import { mapMutations } from 'vuex'
import { vipFileUrl } from '@/config/constant'
export default {
name: 'Workbenche',
created() {
document.title = '思绪思维导图'
this.getVipInfo()
this.$bus.$on('refreshVIP', this.getVipInfo)
},
beforeDestroy() {
this.$bus.$off('refreshVIP', this.getVipInfo)
},
methods: {
...mapMutations(['setIsVIP']),
//
async getVipInfo(showTip = false) {
try {
const clientUUID = await window.electronAPI.getClientUUID()
const { data } = await axios.get(vipFileUrl, {
responseType: 'json'
})
this.setIsVIP(data.includes(clientUUID))
if (showTip) {
this.$message.success('会员状态获取成功')
}
} catch (error) {
console.log(error)
this.$message.error('会员信息获取失败')
}
}
}
}
</script>

View File

@ -21,9 +21,9 @@
<p>
下载最新版本
<span @click="open('baiduNet')">百度云</span>
<span @click="open('releases')">releases</span>
<span @click="open('releases')">Github</span>
</p>
<p>如需进微信交流群参与讨论可微信添加wanglinguanfang备注客户端</p>
<p style="font-size: 12px;">如需进微信交流群参与讨论可微信添加wanglinguanfang备注客户端</p>
</div>
</el-dialog>
</template>

View File

@ -0,0 +1,437 @@
<template>
<div>
<el-dialog
class="vipDialog"
title="思绪会员"
:visible.sync="dialogVisible"
width="700px"
@close="onClose"
>
<div class="vipBox customScrollbar">
<div class="statusBox">
<div class="left" :class="{ isVIP: isVIP }">
<span class="iconfont iconhuiyuan-"></span>{{
isVIP ? '已' : '还不'
}}是会员~
</div>
<div class="center">
<div class="btn" @click="vipFunctionDialogVisible = true">
会员功能一览
</div>
</div>
<div class="right"></div>
</div>
<div class="desc">
<p>
从0.13.1版本开始<span class="btn" @click="open('plugins')"
>SimpleMindMap付费插件</span
>的功能在客户端中也需要付费才能使用
</p>
<p>
一定的收益可以带给开发者持续开发和维护的动力希望您能理解当然您也可以选择安装之前的版本<span
class="btn"
@click="open('baiduNet')"
>百度云</span
><span class="btn" @click="open('releases')">Github</span
>或者使用<span class="btn" @click="open('online')">在线版</span
>
</p>
</div>
<p>
思绪会员为<span class="emphasize">终身制</span
>后续所有新出的功能均无需再次付费目前价格为<span
class="emphasize"
>100</span
>最多允许三台电脑使用
</p>
<p>
<strong>购买方式</strong>扫码进行支付请备注<span class="emphasize"
>会员购买</span
>支付完成后可以手动将下面的<span class="btn" @click="doCopy(uuid)"
>机器码</span
>发送到指定邮箱<span class="btn" @click="doCopy(mailAddress)">{{
mailAddress
}}</span
>
<!-- 也可以直接点击<span class="btn" @click="sendMail">一键发送</span
> -->
</p>
<div class="desc">
<p>
最多可发送三个机器码您可以打开其他需要使用的电脑安装思绪思维导图然后打开会员弹窗复制机器码一并进行发送当然也可以日后再单独发送
</p>
<p>
如果您觉得发邮件比较麻烦也可以直接微信添加<span
class="btn"
@click="doCopy('wanglinguanfang')"
>wanglinguanfang</span
>然后直接发送机器码即可
</p>
</div>
<p>
<strong>支付方式</strong>
<span class="btn" @click="openPayDialog('alipay')"
>点此支付宝支付</span
>
<span class="btn" @click="openPayDialog('wechat')">点此微信支付</span>
</p>
<p class="row">
<span class="text">机器码</span>
<el-input v-model="uuid" size="small" readonly>
<el-button slot="append" @click="doCopy(uuid)">复制</el-button>
</el-input>
</p>
<p class="row">
<span class="text">邮箱地址</span>
<el-input v-model="mailAddress" size="small" readonly>
<el-button slot="append" @click="doCopy(mailAddress)"
>复制</el-button
>
</el-input>
</p>
<div class="desc">
<p>
温馨提醒因为是人工录入所以存在一定延迟录入完成后会发送提醒邮件当您收到邮件后可以尝试重新启动客户端或者点击<span
class="btn"
@click="refreshVIP"
>刷新会员状态</span
>
</p>
<p>
如有疑问可添加微信咨询<span
class="btn"
@click="doCopy('wanglinguanfang')"
>wanglinguanfang</span
>
</p>
</div>
</div>
</el-dialog>
<el-dialog
class="qrCodeDialog"
:title="showPayType === 'alipay' ? '支付宝扫码' : '微信扫码'"
:visible.sync="qrCodeDialogVisible"
width="400px"
@close="qrCodeDialogVisible = false"
>
<div>
<img
src="../../../assets/img/alipay.jpg"
alt=""
v-if="showPayType === 'alipay'"
/>
<img
src="../../../assets/img/wechat.jpg"
alt=""
v-if="showPayType === 'wechat'"
/>
</div>
</el-dialog>
<el-dialog
class="vipFunctionDialog"
title="会员功能一览"
:visible.sync="vipFunctionDialogVisible"
width="800px"
@close="vipFunctionDialogVisible = false"
>
<div class="tableBox">
<el-table :data="functionList" style="width: 100%;" height="450">
<el-table-column
type="index"
width="50"
:index="indexMethod"
></el-table-column>
<el-table-column prop="name" label="功能" width="150">
</el-table-column>
<el-table-column prop="desc" label="描述"> </el-table-column>
<el-table-column prop="img" label="截图">
<template slot-scope="scope">
<img style="width: 200px;" :src="scope.row.img" alt="" />
</template>
</el-table-column>
</el-table>
</div>
</el-dialog>
</div>
</template>
<script>
import { mapState } from 'vuex'
import { copy } from '@/utils'
import img1 from '@/assets/img/vip/1.png'
import img2 from '@/assets/img/vip/2.jpg'
import img3 from '@/assets/img/vip/3.jpg'
import img4 from '@/assets/img/vip/4.png'
import img5 from '@/assets/img/vip/5.png'
import img6 from '@/assets/img/vip/6.png'
import img7 from '@/assets/img/vip/7.gif'
import img8 from '@/assets/img/vip/8.gif'
const functionList = [
{
name: '手绘风格',
desc:
'开启后思维导图连线和形状会显示为手绘风格样式。可以在【设置】里开启或关闭。',
img: img1
},
{
name: '节点标记',
desc:
'可以标记单个节点,也就是可以在单个节点上加一个手绘风格的圈、背景、删除线等等,支持动画效果。',
img: img2
},
{
name: '节点编号',
desc: '可以一键编号某个节点的子节点,支持多种编号形式,支持编号指定层级。',
img: img3
},
{
name: '导出Freemind文件',
desc: '可以导入和导出Freemind软件的格式即.mm文件格式。',
img: img4
},
{
name: '导出Excel文件',
desc: '可以导入和导出Excel软件的格式即.xlsx文件格式。',
img: img5
},
{
name: '节点待办',
desc:
'支持给节点添加待办,即可以给节点添加一个勾选框,点击勾选框可以切换完成和未完成的状态。',
img: img6
},
{
name: '节点连线流动效果',
desc: '可以给节点连线添加流动效果,仅在连线为虚线的情况下生效。',
img: img7
},
{
name: '动量效果',
desc:
'开启后,鼠标按住拖动画布,然后松开后画布会根据惯性继续移动一段距离。可以在【设置】里开启或关闭。',
img: img8
}
]
export default {
model: {
prop: 'value',
event: 'change'
},
props: {
value: {
type: Boolean,
default: false
}
},
data() {
return {
dialogVisible: false,
uuid: '',
qrCodeDialogVisible: false,
mailAddress: '1013335014@qq.com',
showPayType: '',
vipFunctionDialogVisible: false,
functionList
}
},
computed: {
...mapState(['isVIP'])
},
watch: {
value(val, oldVal) {
this.dialogVisible = val
if (val) {
this.getUUID()
}
}
},
created() {
this.getUUID()
},
methods: {
async getUUID() {
try {
if (this.uuid) return
const clientUUID = await window.electronAPI.getClientUUID()
this.uuid = clientUUID
} catch (error) {
console.log(error)
}
},
indexMethod(index) {
return index + 1
},
openPayDialog(type) {
this.showPayType = type
this.qrCodeDialogVisible = true
},
doCopy(text) {
copy(text)
this.$message.success('复制成功')
},
refreshVIP() {
this.$bus.$emit('refreshVIP', true)
},
async sendMail() {
const mailtoUrl = `mailto:${
this.mailAddress
}?subject=${encodeURIComponent('会员购买')}&body=${encodeURIComponent(
this.uuid
)}`
try {
await window.electronAPI.openExternal(mailtoUrl)
} catch (error) {
console.log(error)
this.$message.error('一键发送失败,请手动发送')
}
},
onClose() {
this.$emit('change', false)
},
open(type) {
let url = ''
switch (type) {
case 'plugins':
url =
'https://wanglin2.github.io/mind-map-docs/plugins/handDrawnLikeStyle.html'
break
case 'baiduNet':
url = 'https://pan.baidu.com/s/1huasEbKsGNH2Af68dvWiOg?pwd=3bp3'
break
case 'releases':
url = 'https://github.com/wanglin2/mind-map/releases'
break
case 'online':
url = 'https://wanglin2.github.io/mind-map/'
break
default:
break
}
window.electronAPI.openUrl(url)
}
}
}
</script>
<style lang="less" scoped>
.vipDialog,
.qrCodeDialog,
.vipFunctionDialog {
/deep/ .el-dialog__body {
padding: 0;
}
}
.vipFunctionDialog {
.tableBox {
padding: 20px;
width: 100%;
overflow: hidden;
}
}
.qrCodeDialog {
div {
width: 100%;
padding: 20px;
}
img {
width: 100%;
}
}
.vipBox {
padding: 20px;
padding-top: 0;
height: 400px;
overflow-y: auto;
.statusBox {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
.left {
font-size: 16px;
font-weight: bold;
color: #f56c6c;
display: flex;
align-items: center;
flex: 1;
&.isVIP {
color: #e6a23c;
}
span {
margin-right: 12px;
}
}
.right,
.center {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
.btn {
font-weight: bold;
color: #409eff;
cursor: pointer;
user-select: none;
}
}
}
.desc {
padding: 12px;
background-color: #f5f5f5;
margin: 12px 0;
font-size: 12px;
}
.row {
display: flex;
align-items: center;
}
p {
margin-bottom: 10px;
&:last-of-type {
margin-bottom: 0;
}
.text {
flex-shrink: 0;
}
a {
color: #409eff;
}
.btn {
color: #409eff;
cursor: pointer;
}
.emphasize {
font-weight: bold;
color: #409eff;
}
}
}
</style>

View File

@ -10,11 +10,15 @@
<div class="workbencheHomeHeader">
<MacControl></MacControl>
<div class="rightBar">
<div
class="vipBtn iconfont iconhuiyuan-"
@click="showVipDialog = true"
></div>
<el-dropdown @command="handleCommand" style="margin-right: 12px;">
<span class="settingBtn el-icon-setting"></span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="about">关于软件</el-dropdown-item>
<el-dropdown-item command="sponsor">友情赞助</el-dropdown-item>
<!-- <el-dropdown-item command="sponsor">友情赞助</el-dropdown-item> -->
<el-dropdown-item command="help">使用帮助</el-dropdown-item>
<el-dropdown-item command="doc">开发文档</el-dropdown-item>
<el-dropdown-item command="setting">设置</el-dropdown-item>
@ -30,6 +34,7 @@
<AboutDialog v-model="showAboutDialog"></AboutDialog>
<SponsorDialog v-model="showSponsorDialog"></SponsorDialog>
<SettingDialog v-model="showSettingDialog"></SettingDialog>
<VipDialog v-model="showVipDialog"></VipDialog>
</div>
</template>
@ -41,6 +46,7 @@ import FileList from '../components/FileList.vue'
import AboutDialog from '../components/AboutDialog.vue'
import SponsorDialog from '../components/SponsorDialog.vue'
import SettingDialog from '../components/SettingDialog.vue'
import VipDialog from '../components/VipDialog.vue'
import { getLocalConfig } from '@/api'
import { mapState, mapActions, mapMutations } from 'vuex'
@ -52,13 +58,15 @@ export default {
FileList,
AboutDialog,
SponsorDialog,
SettingDialog
SettingDialog,
VipDialog
},
data() {
return {
showAboutDialog: false,
showSponsorDialog: false,
showSettingDialog: false
showSettingDialog: false,
showVipDialog: false
}
},
computed: {
@ -75,6 +83,9 @@ export default {
this.initLocalConfig()
this.setBodyDark()
},
mounted() {
this.showTip()
},
methods: {
...mapMutations(['setLocalConfig']),
@ -164,6 +175,27 @@ export default {
onDragleave(e) {
e.preventDefault()
e.stopPropagation()
},
showTip() {
const isTipped = localStorage.getItem('vip_tip')
if (!isTipped) {
const n = this.$notify({
title: '提示',
dangerouslyUseHTMLString: true,
message:
'会员功能上线了~<span style="color: #409eff; font-weight: bold; cursor: pointer;">点击了解</span>一下吧。',
duration: 0,
onClick: () => {
this.showVipDialog = true
n.close()
localStorage.setItem('vip_tip', true)
},
onClose: () => {
localStorage.setItem('vip_tip', true)
}
})
}
}
}
}
@ -211,6 +243,14 @@ export default {
display: flex;
align-items: center;
.vipBtn {
font-size: 20px;
cursor: pointer;
color: #e6a23c;
font-weight: bold;
margin-right: 20px;
}
.settingBtn {
font-size: 20px;
cursor: pointer;

View File

@ -51,7 +51,8 @@ const store = new Vuex.Store({
port: 3456,
method: 'POST'
},
currentFolder: '' // 当前打开的目录
currentFolder: '', // 当前打开的目录
isVIP: false // 是否是会员
},
mutations: {
// 设置本地文件名
@ -172,6 +173,11 @@ const store = new Vuex.Store({
// 设置树节点拖拽
setIsDragOutlineTreeNode(state, data) {
state.isDragOutlineTreeNode = data
},
// 设置是否是会员
setIsVIP(state, data) {
state.isVIP = data
}
},
actions: {