init: Initialize the basic project.

This commit is contained in:
2026-01-06 09:58:29 +08:00
commit 1ab79d6f8f
1441 changed files with 129326 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
import config from '@/config'
// 业务自己新加了模块,当然只限制于微服务情况下,单体不用管
const bizCustomization = [
{
label: '/custom/',
value: '/custom/'
}
]
// 微服务环境下如果拆分为多个代码模块那他的url是网关转发这里就要配置
const PREFIX = [
{
label: '/mobile/',
value: '/api/webapp'
},
{
label: '/sys/',
value: '/api/webapp'
},
{
label: '/auth/',
value: '/api/webapp'
},
{
label: '/client/',
value: '/api/webapp'
},
{
label: '/dev/',
value: '/api/webapp'
},
{
label: '/gen/',
value: '/api/webapp'
},
{
label: '/biz/',
value: '/api/bizapp'
}
]
// 转换url
export const convertUrl = (url) => {
if (config.CLOUD_SERVER === false) {
return url
}
const apiArray = [...PREFIX, ...bizCustomization]
const prefixUrlArray = apiArray.filter((f) => url.indexOf(f.label) > -1)
if (prefixUrlArray && prefixUrlArray.length > 0) {
return prefixUrlArray[0].value + url
}
return url
}

64
nl-vue/src/utils/color.js Normal file
View File

@@ -0,0 +1,64 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
/* eslint-disable eqeqeq */
export default {
// 加深
darken(color, level) {
const rgbc = this.hexToRgb(color)
for (let i = 0; i < 3; i++) rgbc[i] = Math.floor(rgbc[i] * (1 - level))
return this.rgbToHex(rgbc[0], rgbc[1], rgbc[2])
},
// 变淡
lighten(color, level) {
const rgbc = this.hexToRgb(color)
for (let i = 0; i < 3; i++) rgbc[i] = Math.floor((255 - rgbc[i]) * level + rgbc[i])
return this.rgbToHex(rgbc[0], rgbc[1], rgbc[2])
},
// rgb颜色转hex颜色
rgbToHex(rgb) {
const bg = rgb.match(/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/)
// 返回空字符串
if (!bg) {
return ''
}
return '#' + toHex(bg[1]) + toHex(bg[2]) + toHex(bg[3])
},
// hex颜色转rgb颜色
hexToRgb(hex) {
// 去除开头 #
if (hex.startsWith('#')) {
hex = hex.substring(1)
}
// 如果当前传入的是 3 位小数值,直接转换为 6 位进行处理
if (hex.length === 3) {
hex = [hex[0], hex[0], hex[1], hex[1], hex[2], hex[2]].join('')
}
if (hex.length !== 6) {
throw new Error('invalid hex:' + hex)
}
const r = parseInt(hex.slice(0, 2), 16)
const g = parseInt(hex.slice(2, 4), 16)
const b = parseInt(hex.slice(4, 6), 16)
if ([r, g, b].some((x) => Number.isNaN(x))) {
throw new Error('invalid hex:' + hex)
}
return [r, g, b]
},
// 透明度
alpha(color, alpha = 1) {
let hex = color.length > 7 ? color.rgbToHex(color) : color
const { r, g, b } = color.hexToRgb(hex)
return `rgba(${r}, ${g}, ${b}, ${alpha})`
}
}
// 转Hex
const toHex = (x) => ('0' + parseInt(x).toString(16)).slice(-2)

View File

@@ -0,0 +1,43 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import enquireJs from 'enquire.js'
export const DEVICE_TYPE = {
DESKTOP: 'desktop',
TABLET: 'tablet',
MOBILE: 'mobile'
}
export const deviceEnquire = function (callback) {
const matchDesktop = {
match: () => {
callback && callback(DEVICE_TYPE.DESKTOP)
}
}
const matchTablet = {
match: () => {
callback && callback(DEVICE_TYPE.TABLET)
}
}
const matchMobile = {
match: () => {
callback && callback(DEVICE_TYPE.MOBILE)
}
}
// screen and (max-width: 1087.99px)
enquireJs
.register('screen and (max-width: 576px)', matchMobile)
.register('screen and (min-width: 576px) and (max-width: 1199px)', matchTablet)
.register('screen and (min-width: 1200px)', matchDesktop)
}

View File

@@ -0,0 +1,37 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { message } from 'ant-design-vue'
export default {
// 对下载的流进行处理,直接从浏览器下载下来
resultDownload(res) {
if (res.data.type === 'application/json') {
// 错误以及无权限
const reader = new FileReader(res.data)
reader.readAsText(res.data)
reader.onload = () => {
const result = JSON.parse(reader.result)
message.error(result.msg)
}
} else {
const blob = new Blob([res.data], { type: 'application/octet-stream;charset=UTF-8' })
const contentDisposition = res.headers['content-disposition']
const patt = new RegExp('filename=([^;]+\\.[^\\.;]+);*')
const $link = document.createElement('a')
$link.href = URL.createObjectURL(blob)
$link.download = decodeURIComponent(patt.exec(contentDisposition)[1])
$link.click()
document.body.appendChild($link)
document.body.removeChild($link) // 下载完成移除元素
window.URL.revokeObjectURL($link.href) // 释放掉blob对象
}
}
}

15
nl-vue/src/utils/enum.js Normal file
View File

@@ -0,0 +1,15 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
export const ThemeModeEnum = {
LIGHT: 'light',
DARK: 'dark',
REAL_DARK: 'realDark'
}

View File

@@ -0,0 +1,29 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
export default (error) => {
// 过滤HTTP请求错误
if (error.code) {
return false
}
const errorMap = {
InternalError: 'Javascript引擎内部错误',
ReferenceError: '未找到对象',
TypeError: '使用了错误的类型或对象',
RangeError: '使用内置对象时,参数超范围',
SyntaxError: '语法错误',
EvalError: '错误的使用了Eval',
URIError: 'URI错误'
}
const errorName = errorMap[error.name] || '未知错误'
nextTick(() => {
console.error(errorName + ' ' + error)
})
}

View File

@@ -0,0 +1,67 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
export const required = (message, trigger = ['blur', 'change']) => ({
required: true,
message,
trigger
})
// 常用正则规则大全https://any86.github.io/any-rule/
// 表单上面使用参照菜单管理的 title 字段,例如:-> title: [required('请输入菜单名称'), rules.horizontalChart]
export const rules = {
phone: {
pattern: /^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\d{8}$/,
message: '请填写符合要求的11位手机号',
trigger: 'blur'
},
email: {
pattern: /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/,
message: '请填写正确的邮箱号',
trigger: 'blur'
},
idCard: {
pattern:
/(^\d{8}(0\d|10|11|12)([0-2]\d|30|31)\d{3}$)|(^\d{6}(18|19|20)\d{2}(0[1-9]|10|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)$)/,
message: '请填写符合要求的身份证号',
trigger: 'blur'
},
lettersNum: {
pattern: /^[A-Za-z0-9]+$/,
message: '填写内容须是字母或数字组成',
trigger: 'blur'
},
number: {
pattern: /^\d{1,}$/,
message: '填写内容必须是纯数字',
trigger: 'blur'
},
price: {
pattern: /(?:^[1-9]([0-9]+)?(?:\.[0-9]{1,2})?$)|(?:^(?:0)$)|(?:^[0-9]\.[0-9](?:[0-9])?$)/,
message: '只支持正数金额',
trigger: 'blur'
},
horizontalChart: {
pattern: /^[^-]*$/,
message: '不可包含横杠 “-”'
},
initialNotBackslashChart: {
pattern: /^(?!\/)[\s\S]*$/,
message: '首字母不可出现反斜杠 “/”'
},
initialYesBackslashChart: {
pattern: /^\/[^/].*$/,
message: '首字母必须是反斜杠 “/”'
},
mustBeLetters: {
pattern: /^[a-zA-Z]+$/,
message: '输入内容必须是英文字母'
}
}

View File

@@ -0,0 +1,39 @@
/**
* 页面全局 Loading
* @method start 创建 loading
* @method done 移除 loading
*/
import sysConfig from '@/config/index'
export const NextLoading = {
// 创建 loading
start: () => {
const el = document.querySelector('.admin-ui')
if (el) return
const body = document.body
const div = document.createElement('div')
div.setAttribute('class', 'admin-ui')
const sysName = sysConfig.SYS_BASE_CONFIG.SNOWY_SYS_NAME
const htmlBefore = `
<div class="app-loading">
<div class="app-loading-logo">
<img src="/img/logo.png"/>
</div>
<div><span class="dot dot-spin"><i></i><i></i><i></i><i></i></span></div>
<div class="app-loading-title"> `
const htmlAfter = ` </div></div>`
div.innerHTML = htmlBefore + sysName + htmlAfter
body.insertBefore(div, body.childNodes[0])
window.nextLoading = true
},
// 移除 loading
done: (time = 0) => {
nextTick(() => {
setTimeout(() => {
window.nextLoading = false
const el = document.querySelector('.admin-ui')
el?.parentNode?.removeChild(el)
}, time)
})
}
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import pinyin from 'js-pinyin'
// 中文转拼音 传入仅首字母
Object.defineProperty(String.prototype, 'toPinyin', {
writable: false,
enumerable: false,
configurable: true,
value: function (first) {
let str = this
if (first) {
return pinyin.getCamelChars(str).replace(/\uD83C[\uDF00-\uDFFF]|\uD83D[\uDC00-\uDE4F]/g, '')
}
return pinyin.getFullChars(str).replace(/\uD83C[\uDF00-\uDFFF]|\uD83D[\uDC00-\uDE4F]/g, '')
}
})
// 字符检索 传入检索值
Object.defineProperty(String.prototype, 'filter', {
writable: false,
enumerable: false,
configurable: true,
value: function (input) {
let str = this
let en = str.toLowerCase().includes(input.toLowerCase())
let zhFull = str.toPinyin().toLowerCase().includes(input.toLowerCase())
let zhFirst = str.toPinyin(true).toLowerCase().includes(input.toLowerCase())
return en || zhFull || zhFirst
}
})

View File

@@ -0,0 +1,40 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import tool from '@/utils/tool'
/**
* 权限判断是否能看到这个按钮,同时后端也做了校验,前端只是显示与不显示
* @param {string, array} data 按钮的权限点,可以是单个字符串,也可以是数组
* @param {string} rule or代表或and代表与
* 使用方法:
* 例如 buttonCodeList 的数据为: ['button1', 'button2', 'button3']
* 想要判断 button1 的权限可以写成hasPerm('button1')
* 想要判断 button1 或 button2 的权限可以写成hasPerm(['button1', 'button2' ])
* 想要判断 button1 与 button2 的权限可以写成hasPerm(['button1', 'button2' ], 'and')
*/
export function hasPerm(data, rule = 'or') {
if (!data) {
return false
}
const userInfo = tool.data.get('USER_INFO')
if (!userInfo) {
return false
}
const { buttonCodeList } = userInfo
if (!buttonCodeList) {
return false
}
if (Array.isArray(data)) {
const fn = rule === 'or' ? 'some' : 'every'
return data[fn]((item) => buttonCodeList.includes(item))
}
return buttonCodeList.includes(data)
}

180
nl-vue/src/utils/request.js Normal file
View File

@@ -0,0 +1,180 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
// 统一的请求发送
import axios from 'axios'
import qs from 'qs'
import { Modal, message } from 'ant-design-vue'
import sysConfig from '@/config/index'
import tool from '@/utils/tool'
import { convertUrl } from './apiAdaptive'
// 以下这些code需要重新登录
const reloadCodes = [401, 1011007, 1011008]
const errorCodeMap = {
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
401: '用户没有权限(令牌、用户名、密码错误)。',
403: '用户得到授权,但是访问是被禁止的。',
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的。',
422: '当创建一个对象时,发生一个验证错误。',
500: '服务器发生错误,请检查服务器。',
502: '网关错误。',
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。'
}
// 定义一个重新登录弹出窗的变量
const loginBack = ref(false)
// 创建 axios 实例
const service = axios.create({
baseURL: '/api', // api base_url
timeout: sysConfig.TIMEOUT // 请求超时时间
})
// HTTP request 拦截器
service.interceptors.request.use(
(config) => {
const token = tool.data.get('TOKEN')
if (token) {
config.headers[sysConfig.TOKEN_NAME] = sysConfig.TOKEN_PREFIX + token
}
if (!sysConfig.REQUEST_CACHE && config.method === 'get') {
config.params = config.params || {}
config.params._ = new Date().getTime()
}
Object.assign(config.headers, sysConfig.HEADERS)
return config
},
(error) => {
return Promise.reject(error)
}
)
// 保持重新登录Modal的唯一性
const error = () => {
loginBack.value = true
Modal.error({
title: '提示:',
okText: '重新登录',
content: '登录已失效, 请重新登录',
onOk: () => {
loginBack.value = false
tool.data.remove('TOKEN')
tool.data.remove('USER_INFO')
tool.data.remove('MENU')
tool.data.remove('PERMISSIONS')
window.location.reload()
}
})
}
// HTTP response 拦截器
service.interceptors.response.use(
(response) => {
// 配置了blob不处理直接返回文件流
if (response.config.responseType === 'blob') {
if (response.status === 200) {
return response
} else {
message.warning('文件下载失败或此文件不存在')
return
}
}
const data = response.data
const code = data.code
if (reloadCodes.includes(code)) {
if (!loginBack.value) {
error()
}
return
}
if (code !== 200) {
const customErrorMessage = response.config.customErrorMessage
message.error(customErrorMessage || data.msg)
return Promise.reject(data)
// 自定义错误提示覆盖后端返回的message
// 使用示例:
// export function customerList (data) {
// return request('list', data, 'get', {
// customErrorMessage: '自定义错误消息提示'
// });
// }
} else {
// 统一成功提示
const functionName = response.config.url.split('/').pop()
const apiNameArray = [
'add',
'edit',
'delete',
'update',
'grant',
'reset',
'stop',
'pass',
'disable',
'enable',
'revoke',
'suspend',
'active',
'turn',
'adjust',
'reject',
'saveDraft'
]
apiNameArray.forEach((apiName) => {
// 上面去掉接口路径后,方法内包含内置的进行统一提示成功
if (functionName.includes(apiName)) {
message.success(data.msg)
}
})
}
return Promise.resolve(data.data)
},
(error) => {
if (error) {
const status = 503
const description = errorCodeMap[status]
console.error({
message: '请求错误',
description
})
return Promise.reject(status)
}
}
)
// 适配器, 用于适配不同的请求方式
export const baseRequest = (url, value = {}, method = 'post', options = {}) => {
url = sysConfig.API_URL + convertUrl(url)
if (method === 'post') {
return service.post(url, value, options)
} else if (method === 'get') {
return service.get(url, { params: value, ...options })
} else if (method === 'formdata') {
// form-data表单提交的方式
return service.post(url, qs.stringify(value), {
headers: {
'Content-Type': 'multipart/form-data'
},
...options
})
} else {
// 其他请求方式例如put、delete
return service({
method: method,
url: url,
data: value,
...options
})
}
}
export default service

View File

@@ -0,0 +1,43 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import userRoutes from '@/config/route'
// 获取第一个界面
const getIndexMenu = (menu) => {
if (menu[0] && menu[0].children) {
let indexMenu = menu[0].children[0]
// 如果第一个菜单为目录,接着往下找
if (indexMenu.meta.type === 'catalog') {
indexMenu = traverseChild(menu)
}
return indexMenu
} else {
return userRoutes.menu[0]
}
}
// 遍历进行判断,其中处理了被隐藏的
const traverseChild = (menu) => {
if (menu[0] && menu[0].children !== undefined) {
if (menu[0].children.length > 0) {
if (menu[0].children[0] && menu[0].children[0].meta.hidden && menu[0].children[0].meta.hidden === true) {
return menu[0]
} else {
return traverseChild(menu[0].children)
}
}
} else {
return menu[0]
}
}
export default {
getIndexMenu
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
/**
* 加解密的工具类
* 使用https://github.com/JuneAndGreen/sm-crypto
*
* @author yubaoshan
*/
import smCrypto from 'sm-crypto'
const sm2 = smCrypto.sm2
const cipherMode = 1 // 1 - C1C3C20 - C1C2C3默认为1
const publicKey =
'04298364ec840088475eae92a591e01284d1abefcda348b47eb324bb521bb03b0b2a5bc393f6b71dabb8f15c99a0050818b56b23f31743b93df9cf8948f15ddb54'
/**
* 国密加解密工具类
*/
export default {
// SM2加密
doSm2Encrypt(msgString) {
return sm2.doEncrypt(msgString, publicKey, cipherMode)
},
// SM2数组加密
doSm2ArrayEncrypt(msgString) {
return sm2.doEncrypt(msgString, publicKey, cipherMode)
}
}

View File

@@ -0,0 +1,326 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
/* !
* template.js v0.7.1 (https://github.com/yanhaijing/template.js)
* API https://github.com/yanhaijing/template.js/blob/master/doc/api.md
* Copyright 2015 yanhaijing. All Rights Reserved
* Licensed under MIT (https://github.com/yanhaijing/template.js/blob/master/MIT-LICENSE.txt)
*/
/* eslint-disable */
;(function(root, factory) {
var template = factory(root);
if (typeof define === 'function' && define.amd) {
// AMD
define('template', function() {
return template;
});
} else if (typeof exports === 'object') {
// Node.js
module.exports = template;
} else {
// Browser globals
var _template = root.template;
template.noConflict = function() {
if (root.template === template) {
root.template = _template;
}
return template;
};
root.template = template;
}
}(this, function(root) {
'use strict';
var o = {
sTag: '<%',//开始标签
eTag: '%>',//结束标签
compress: false,//是否压缩html
escape: true, //默认输出是否进行HTML转义
error: function (e) {}//错误回调
};
var functionMap = {}; //内部函数对象
//修饰器前缀
var modifierMap = {
'': function (param) {return nothing(param)},
'h': function (param) {return encodeHTML(param)},
'u': function (param) {return encodeURI(param)}
};
var toString = {}.toString;
var slice = [].slice;
function type(x) {
if(x === null){
return 'null';
}
var t= typeof x;
if(t !== 'object'){
return t;
}
var c = toString.call(x).slice(8, -1).toLowerCase();
if(c !== 'object'){
return c;
}
if(x.constructor==Object){
return c;
}
return 'unknown';
}
function isObject(obj) {
return type(obj) === 'object';
}
function isFunction(fn) {
return type(fn) === 'function';
}
function isString(str) {
return type(str) === 'string';
}
function extend() {
var target = arguments[0] || {};
var arrs = slice.call(arguments, 1);
var len = arrs.length;
for (var i = 0; i < len; i++) {
var arr = arrs[i];
for (var name in arr) {
target[name] = arr[name];
}
}
return target;
}
function clone() {
var args = slice.call(arguments);
return extend.apply(null, [{}].concat(args));
}
function nothing(param) {
return param;
}
function encodeHTML(source) {
return String(source)
.replace(/&/g,'&amp;')
.replace(/</g,'&lt;')
.replace(/>/g,'&gt;')
.replace(/\\/g,'&#92;')
.replace(/"/g,'&quot;')
.replace(/'/g,'&#39;');
}
function compress(html) {
return html.replace(/\s+/g, ' ').replace(/<!--[\w\W]*?-->/g, '');
}
function consoleAdapter(cmd, msg) {
typeof console !== 'undefined' && console[cmd] && console[cmd](msg);
}
function handelError(e) {
var message = 'template.js error\n\n';
for (var key in e) {
message += '<' + key + '>\n' + e[key] + '\n\n';
}
message += '<message>\n' + e.message + '\n\n';
consoleAdapter('error', message);
o.error(e);
function error() {
return 'template.js error';
}
error.toString = function () {
return '__code__ = "template.js error"';
}
return error;
}
function parse(tpl, opt) {
var code = '';
var sTag = opt.sTag;
var eTag = opt.eTag;
var escape = opt.escape;
function parsehtml(line) {
// 单双引号转义,换行符替换为空格
line = line.replace(/('|")/g, '\\$1');
var lineList = line.split('\n');
var code = '';
for (var i = 0; i < lineList.length; i++) {
code += ';__code__ += ("' + lineList[i] + (i === lineList.length - 1 ? '")\n' : '\\n")\n');
}
return code;
}
function parsejs(line) {
//var reg = /^(:?)(.*?)=(.*)$/;
var reg = /^(?:=|(:.*?)=)(.*)$/
var html;
var arr;
var modifier;
// = := :*=
// :h=123 [':h=123', 'h', '123']
if (arr = reg.exec(line)) {
html = arr[2]; // 输出
if (Boolean(arr[1])) {
// :开头
modifier = arr[1].slice(1);
} else {
// = 开头
modifier = escape ? 'h' : '';
}
return ';__code__ += __modifierMap__["' + modifier + '"](typeof (' + html + ') !== "undefined" ? (' + html + ') : "")\n';
}
//原生js
return ';' + line + '\n';
}
var tokens = tpl.split(sTag);
for (var i = 0, len = tokens.length; i < len; i++) {
var token = tokens[i].split(eTag);
if (token.length === 1) {
code += parsehtml(token[0]);
} else {
code += parsejs(token[0], true);
if (token[1]) {
code += parsehtml(token[1]);
}
}
}
return code;
}
function compiler(tpl, opt) {
var mainCode = parse(tpl, opt);
var headerCode = '\n' +
' var html = (function (__data__, __modifierMap__) {\n' +
' var __str__ = "", __code__ = "";\n' +
' for(var key in __data__) {\n' +
' __str__+=("var " + key + "=__data__[\'" + key + "\'];");\n' +
' }\n' +
' eval(__str__);\n\n';
var footerCode = '\n' +
' ;return __code__;\n' +
' }(__data__, __modifierMap__));\n' +
' return html;\n';
var code = headerCode + mainCode + footerCode;
code = code.replace(/[\r]/g, ' '); // ie 7 8 会报错,不知道为什么
try {
var Render = new Function('__data__', '__modifierMap__', code);
Render.toString = function () {
return mainCode;
}
return Render;
} catch(e) {
e.temp = 'function anonymous(__data__, __modifierMap__) {' + code + '}';
throw e;
}
}
function compile(tpl, opt) {
opt = clone(o, opt);
try {
var Render = compiler(tpl, opt);
} catch(e) {
e.name = 'CompileError';
e.tpl = tpl;
e.render = e.temp;
delete e.temp;
return handelError(e);
}
function render(data) {
data = clone(functionMap, data);
try {
var html = Render(data, modifierMap);
html = opt.compress ? compress(html) : html;
return html;
} catch(e) {
e.name = 'RenderError';
e.tpl = tpl;
e.render = Render.toString();
return handelError(e)();
}
}
render.toString = function () {
return Render.toString();
};
return render;
}
function template(tpl, data) {
if (typeof tpl !== 'string') {
return '';
}
var fn = compile(tpl);
if (!isObject(data)) {
return fn;
}
return fn(data);
}
template.config = function (option) {
if (isObject(option)) {
o = extend(o, option);
}
return clone(o);
};
template.registerFunction = function(name, fn) {
if (!isString(name)) {
return clone(functionMap);
}
if (!isFunction(fn)) {
return functionMap[name];
}
return functionMap[name] = fn;
}
template.unregisterFunction = function (name) {
if (!isString(name)) {
return false;
}
delete functionMap[name];
return true;
}
template.registerModifier = function(name, fn) {
if (!isString(name)) {
return clone(modifierMap);
}
if (!isFunction(fn)) {
return modifierMap[name];
}
return modifierMap[name] = fn;
}
template.unregisterModifier = function (name) {
if (!isString(name)) {
return false;
}
delete modifierMap[name];
return true;
}
template.__encodeHTML = encodeHTML;
template.__compress = compress;
template.__handelError = handelError;
template.__compile = compile;
template.version = '0.7.1';
return template;
}));

View File

@@ -0,0 +1,81 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { generate } from '@ant-design/colors'
import tool from '../utils/tool'
import config from '../config'
import { themeEnum } from '@/layout/enum/themeEnum'
const changeColor = (newPrimaryColor, theme, darkClass = 'snowy-theme-dark') => {
return new Promise((resolve) => {
const themeEleId = 'snowy-theme-var'
const themeEle = document.querySelector(`#${themeEleId}`)
if (themeEle && themeEle.parentNode) {
themeEle.parentNode.removeChild(themeEle)
}
const isRealDark = theme === themeEnum.REAL_DARK
if (newPrimaryColor) {
const colors = generate(newPrimaryColor, isRealDark ? { theme: 'dark' } : {})
const rootClass = isRealDark ? `.${darkClass}` : ':root'
const styleElement = document.createElement('style')
styleElement.id = themeEleId
styleElement.setAttribute('type', 'text/css')
styleElement.innerHTML = `${rootClass}{${colors
.map((c, i) => {
return `--primary-${i + 1}:${c};`
})
.concat([`--primary-color:${newPrimaryColor};`])
.join('')}}`
document.head.appendChild(styleElement)
} else {
document.body.removeAttribute('snowy-theme')
}
if (isRealDark) {
document.body.classList.add(darkClass)
} else {
document.body.classList.remove(darkClass)
}
resolve()
})
}
const loadLocalTheme = (localSetting) => {
if (localSetting) {
let { theme, themeColor } = localSetting
themeColor = themeColor || config.COLOR
theme = theme || config.THEME
changeColor(themeColor, theme)
}
}
/**
* 获取本地保存的配置
* @param loadTheme {boolean} 是否加载配置中的主题
* @returns {Object}
*/
const getLocalSetting = (loadTheme) => {
let localSetting = {}
try {
const theme = tool.data.get('SNOWY_THEME')
const themeColor = tool.data.get('SNOWY_THEME_COLOR')
localSetting = {
theme,
themeColor
}
} catch (e) {
console.error(e)
}
if (loadTheme) {
loadLocalTheme(localSetting)
}
return localSetting
}
export { loadLocalTheme, getLocalSetting, changeColor }

219
nl-vue/src/utils/tool.js Normal file
View File

@@ -0,0 +1,219 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
/**
* @Descripttion: 工具集
* @version: 1.1
* @LastEditors: yubaoshan
* @LastEditTime: 2022年4月19日10:58:41
*/
const tool = {}
// localStorage
tool.data = {
set(table, settings) {
const _set = JSON.stringify(settings)
const SNOWYSTRING = table.slice(0, 6) === 'SNOWY_' && table !== 'SNOWY_SYS_BASE_CONFIG'
if (SNOWYSTRING) {
let localSetting = JSON.parse(localStorage.getItem('SNOWY_SETTING')) || {}
let newSetting = {}
newSetting[table] = _set
return localStorage.setItem('SNOWY_SETTING', JSON.stringify(Object.assign(localSetting, newSetting)))
} else return localStorage.setItem(table, _set)
},
get(table) {
const SNOWYSTRING = table.slice(0, 6) === 'SNOWY_' && table !== 'SNOWY_SYS_BASE_CONFIG'
const SNOWY_SETTING = JSON.parse(localStorage.getItem('SNOWY_SETTING')) || {}
let data = SNOWYSTRING ? SNOWY_SETTING[table] : localStorage.getItem(table)
try {
data = JSON.parse(data)
} catch (err) {
return null
}
return data
},
remove(table) {
return localStorage.removeItem(table)
},
clear() {
return localStorage.clear()
}
}
// sessionStorage
tool.session = {
set(table, settings) {
const _set = JSON.stringify(settings)
return sessionStorage.setItem(table, _set)
},
get(table) {
let data = sessionStorage.getItem(table)
try {
data = JSON.parse(data)
} catch (err) {
return null
}
return data
},
remove(table) {
return sessionStorage.removeItem(table)
},
clear() {
return sessionStorage.clear()
}
}
// 千分符
tool.groupSeparator = (num) => {
num = `${num}`
if (!num.includes('.')) num += '.'
return num
.replace(/(\d)(?=(\d{3})+\.)/g, ($0, $1) => {
return `${$1},`
})
.replace(/\.$/, '')
}
// 获取所有字典数组
tool.dictDataAll = () => {
return tool.data.get('DICT_TYPE_TREE_DATA')
}
// 字典翻译方法,界面插槽使用方法 {{ $TOOL.dictType('sex', record.sex) }}
tool.dictTypeData = (dictValue, value) => {
const dictTypeTree = tool.dictDataAll()
if (!dictTypeTree) {
return '需重新登录'
}
const tree = dictTypeTree.find((item) => item.dictValue === dictValue)
if (!tree) {
return '无此字典'
}
const children = tree.children
const dict = children.find((item) => item.dictValue === value)
return dict ? dict.dictLabel : '无此字典项'
}
// 获取某个code下字典的列表多用于字典下拉框
tool.dictTypeList = (dictValue) => {
const dictTypeTree = tool.dictDataAll()
if (!dictTypeTree) {
return []
}
const tree = dictTypeTree.find((item) => item.dictValue === dictValue)
if (tree && tree.children) {
return tree.children
}
return []
}
// 获取某个code下字典的列表基于dictTypeList 改进,保留老的,逐步替换
tool.dictList = (dictValue) => {
const dictTypeTree = tool.dictDataAll()
if (!dictTypeTree) {
return []
}
const tree = dictTypeTree.find((item) => item.dictValue === dictValue)
if (tree) {
return tree.children.map((item) => {
return {
value: item['dictValue'],
label: item['name']
}
})
}
return []
}
// 树形翻译 需要指定最顶级的 parentValue 和当级的value
tool.translateTree = (parentValue, value) => {
const tree = tool.dictDataAll().find((item) => item.dictValue === parentValue)
const targetNode = findNodeByValue(tree, value)
return targetNode ? targetNode.dictLabel : ''
}
const findNodeByValue = (node, value) => {
if (node.dictValue === value) {
return node
}
if (node.children) {
for (let i = 0; i < node.children.length; i++) {
const result = findNodeByValue(node.children[i], value)
if (result) {
return result
}
}
}
return null
}
// 生成UUID
tool.snowyUuid = () => {
let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
let r = (Math.random() * 16) | 0,
v = c === 'x' ? r : (r & 0x3) | 0x8
return v.toString(16)
})
// 首字符转换成字母
return 'xn' + uuid.slice(2)
}
// 输入位数获得英文字母大小写随机码
tool.generateString = (length = 8) => {
let result = ''
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
let charactersLength = characters.length
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength))
}
return result
}
tool.parseTime = (time, cFormat) => {
if (time == null || time.length === 0) {
return ''
}
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
let date
if (typeof time === 'object') {
date = time
} else {
if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
time = parseInt(time)
}
if ((typeof time === 'number') && (time.toString().length === 10)) {
time = time * 1000
}
date = new Date(time)
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
}
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key]
// Note: getDay() returns 0 on Sunday
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
if (result.length > 0 && value < 10) {
value = '0' + value
}
return value || 0
})
return time_str
}
export default tool

View File

@@ -0,0 +1,14 @@
// 递归选中或取消子节点a-tree专属方法
export const checkOrUnCheckChildren = (checked, node, checkedKeys) => {
if (node.children) {
node.children.forEach((item) => {
if (checked) {
checkedKeys.checked.push(item.id)
} else {
checkedKeys.checked = checkedKeys.checked.filter((k) => k !== item.id)
}
checkOrUnCheckChildren(checked, item, checkedKeys)
})
}
return checkedKeys.checked
}

View File

@@ -0,0 +1,75 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { nextTick } from 'vue'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import router from '@/router'
import { iframeStore, keepAliveStore, viewTagsStore } from '@/store'
export default {
// 刷新标签
refresh() {
NProgress.start()
const keepAlive = keepAliveStore()
const route = router.currentRoute.value
keepAlive.removeKeepLive(route.name)
keepAlive.setRouteShow(false)
nextTick(() => {
keepAlive.pushKeepLive(route.name)
keepAlive.setRouteShow(true)
NProgress.done()
})
},
// 关闭标签
close(tag) {
const route = tag || router.currentRoute.value
const store = viewTagsStore()
store.removeViewTags(route)
iframeStore().removeIframeList(route)
keepAliveStore().removeKeepLive(route.name)
const tagList = store.viewTags
const latestView = tagList.slice(-1)[0]
if (latestView) {
router.push(latestView)
} else {
router.push('/')
}
},
// 关闭标签后处理
closeNext(next) {
const route = router.currentRoute.value
const store = viewTagsStore()
store.removeViewTags(route)
iframeStore().removeIframeList(route)
keepAliveStore().removeKeepLive(route.name)
if (next) {
const tagList = store.viewTags
next(tagList)
}
},
// 关闭其他
closeOther() {
const route = router.currentRoute.value
const store = viewTagsStore()
const tagList = [...store.viewTags]
tagList.forEach((tag) => {
// eslint-disable-next-line prettier/prettier
if ((tag.meta && tag.meta.affix) || route.fullPath == tag.fullPath) {
return true
} else {
this.close(tag)
}
})
},
// 设置标题
setTitle(title) {
viewTagsStore().updateViewTagsTitle(title)
}
}

View File

@@ -0,0 +1,35 @@
// 获取app.js 的哈希值
const getAppHash = (scripts) => {
let localVersion = ''
for (let i = 0; i < scripts.length; i++) {
let src = scripts[i].getAttribute('src')
if (src && src.indexOf('main.') !== -1) {
// 返回时间戳
localVersion = src.split('t=')[1] || ''
}
}
return localVersion
}
// 获取本地的app.js版本号
export const getLocalHash = () => {
return getAppHash(document.getElementsByTagName('script'))
}
// 获取线上的app.js版本号
export const checkHash = () => {
return new Promise((resolve, reject) => {
// 加上时间戳,防止缓存
fetch('/?t=' + Date.now())
.then(async (res) => {
let html = await res.text() //转成字符串判断
let doc = new DOMParser().parseFromString(html, 'text/html')
let newVersion = getAppHash(doc.getElementsByTagName('script'))
resolve(newVersion)
})
.catch((err) => {
console.log('获取版本号失败', err)
reject(err)
})
})
}

View File

@@ -0,0 +1,91 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import tool from '@/utils/tool'
export const watermark = {
set: function (text1, text2) {
const canvas = document.createElement('canvas')
canvas.width = 150
canvas.height = 120
canvas.style.display = 'none'
const shuiyin = canvas.getContext('2d')
// 控制文字的旋转角度和上下位置
shuiyin.rotate((-20 * Math.PI) / 180)
shuiyin.translate(-50, 20)
//文字颜色
shuiyin.fillStyle = '#f5f5f5'
//文字样式
shuiyin.font = '100 16px Microsoft JhengHei '
shuiyin.fillText(text1, canvas.width / 3, canvas.height / 2)
shuiyin.fillText(text2, canvas.width / 3, canvas.height / 2 + 20)
/* 新建一个用于填充canvas水印的标签之所以没有直接在body上添加
是因为z-index对个别内容影响才考虑的不用body */
const watermark = document.createElement('div')
const styleStr = `
position:fixed;
top:0;
left:0;
width:100vw;
height:100vh;
z-index:99999;
pointer-events:none;
background-repeat:repeat;
mix-blend-mode: multiply;
background-image:url('${canvas.toDataURL('image/png')}')`
watermark.setAttribute('style', styleStr)
watermark.classList.add('watermark')
document.body.appendChild(watermark)
//此方法是防止用户通过控制台修改样式去除水印效果
/* MutationObserver 是一个可以监听DOM结构变化的接口。 */
const observer = new MutationObserver(() => {
// 此处根据用户登录状态,判断是否终止监听,避免用户退出后登录页面仍然有水印
if (!tool.data.get('TOKEN')) {
this.close()
observer.disconnect()
}
const wmInstance = document.body.querySelector('.watermark')
if (!wmInstance || wmInstance.getAttribute('style') !== styleStr) {
//如果标签在,只修改了属性,重新赋值属性
if (wmInstance) {
// 避免一直触发
// observer.disconnect();
wmInstance.setAttribute('style', styleStr)
} else {
/* 此处根据用户登录状态,判断是否终止监听,避免用户退出后登录页面仍然有水印 */
if (tool.data.get('TOKEN')) {
//标签被移除,重新添加标签
document.body.appendChild(watermark)
} else {
observer.disconnect()
}
}
}
})
observer.observe(document.body, {
attributes: true,
subtree: true,
childList: true
})
},
close: function () {
/* 关闭页面的水印,即要移除水印标签 */
let watermark = document.body.querySelector('.watermark')
if (watermark) {
document.body.removeChild(watermark)
}
}
}
// 使用方法
// import { watermark } from '@/utils/watermark'
// 添加水印
// watermark.set('Snowy','xiaonuo.vip')
// 移除水印,传 null 移除水印
// watermark.close()