个人爱好,知识积累,点滴成石
web端
wisdom-plus + 高德自定义地图 DemoMap.vue
鼠标拖拽坐标捕获 useMouseDownToMove.ts
获取事件冒泡路径,兼容ie11,edge,chrome,firefox,safari
服务端
前端资源javascript-obfuscator代码混淆加密
nodejs 17 以下fetch兼容,以axios方式-可解决llama-js 在低版本的nodejs中的正常运行
other
import { defineConfig } from 'unocss';
export default defineConfig({
// ...UnoCSS options
shortcuts: {
'flex-center': 'flex justify-center items-center',
'flex-center-start': 'flex justify-start items-center',
'flex-center-end': 'flex justify-end items-center',
'flex-center-between': 'flex justify-between items-center',
'flex-center-around': 'flex justify-around items-center',
'flex-v': 'flex flex-col',
'abs-f': 'fixed',
'abs-r': 'relative',
abs: 'absolute',
'size-content': 'left-0 top-0 w-100% h-100%',
'abs-content': 'absolute left-0 top-0 w-100% h-100%',
'abs-start': 'absolute left-0 top-0',
'abs-end': 'absolute right-0 top-0',
'abs-end-bottom': 'absolute right-0 bottom-0',
'abs-start-bottom': 'absolute left-0 bottom-0',
'abs-center': 'absolute left-50% top-50% translate--50%',
'abs-x': 'absolute left-50% translate-x--50%',
'abs-y': 'absolute top-50% translate-y--50%',
bold: 'font-bold',
'cur-p': 'cursor-pointer',
'p-e-n': 'pointer-events-none',
},
rules: [
[
// 包含小数点的 flex
/^flex-?([0-9]+(?:\.[0-9]+)?)$/,
(match) => {
return {
flex: match[1],
};
},
],
[
/^tr-?([xy])(?:-?(-?.+))?$/,
(match) => {
return {
transform: `translate${match[1].toUpperCase() || 'Y'}(${match[2] || 0})`,
};
},
],
[
/^frame(?:-?(-?.+))?$/,
(match) => {
const [name, start, time, ...timing] = match[1].split('-');
let timingFn = timing;
let timeStr = time;
if (time === 'cubic') {
timingFn = [time].concat(timing);
timeStr = '';
}
return {
animation: `${name} calc(1 - var(--sy) / ${start}) ${timeStr || ''} ${timingFn.join('-') || 'linear'} forwards reverse`,
};
},
],
[
// c-var--primary-color => color: var(--primary-color)
/^c-var-([a-zA-Z0-9-]+)$/,
(match) => {
return {
color: `var(--${match[1]})`,
};
},
],
[
/^(s|size)-([a-zA-Z0-9-]+)$/,
(match) => {
return {
width: match[2],
height: match[2],
};
},
],
[
/^bg-(lg|rlg|rg|rrg|url)-(.{1,})$/,
(match) => {
return {
'background-image': `${
{
lg: 'linear-gradient',
rlg: 'radial-gradient',
rg: 'repeating-linear-gradient',
rrg: 'repeating-radial-gradient',
url: 'url',
}[match[1]]
}(${match[2].replace(/--/g, ' , ').replace(/-/g, ' ').replace(/\$([^\s]+)/g, 'var(--$1)')})`,
};
},
],
],
});
import { defineConfig } from "unocss";
export default defineConfig({
// ...UnoCSS optionstr
configResolved(config) {
config.preflights = [];
},
rules: [
[
/^u-?(text|bg|color|w)-?(.*)/,
(m) => {
return {
text: `.${m[0]}{color:${m[2].replace("0x", "#")};}`,
color: `.${m[0]}{color:${m[2].replace("0x", "#")};}`,
bg: `.${m[0]}{background-color:${m[2].replace("0x", "#")};}`,
w: `.${m[0]}{width:${m[2]}%;}`,
h: `.${m[0]}{height:${m[2]}%;}`,
}[m[1]];
},
],
],
});
//10进制转成有符号的10进制
function hexToSignedDecimal(hexStr) {
// 将 16 进制字符串转换为无符号的整数
const unsignedInt = parseInt(hexStr, 16);
// 32 位有符号整数的范围
const INT32_MAX = 0x7FFFFFFF;
const INT32_MIN = -0x80000000;
// 判断是否为负数
if (unsignedInt > INT32_MAX) {
// 如果无符号整数大于 0x7FFFFFFF,则它在有符号整数的负数范围内
return unsignedInt - 0x100000000; // 0x100000000 是 2^32,用于从无符号转换为有符号
} else {
// 如果不在负数范围内,直接返回值
return unsignedInt;
}
}
const hexStr = "fe7ae63d";
const signedDecimal = hexToSignedDecimal(hexStr);
console.log(signedDecimal); // 输出 -126813651
// 转符号10进制示例
function signedDecimalToHex(unsignedInt) {
// 判断是否为负数
if (unsignedInt < 0) {
// 如果无符号整数大于 0x7FFFFFFF,则它在有符号整数的负数范围内
return (unsignedInt + 0x100000000).toString(16); // 0x100000000 是 2^32,用于从无符号转换为有符号
} else {
// 如果不在负数范围内,直接返回值
return unsignedInt.toString(16);
}
}
import { readFileSync } from "fs"
/**
* @name sqlCommitFunction sql文件注释解析
* @param sqlFilePath sql文件路径
* @returns
*/
export default function <T = Record<string, any>>(sqlFilePath: string): T{
const sql = readFileSync(sqlFilePath, 'utf8')
const sqlNames = []
sql.replace(/\/\*(.|\n)*?\*\//g, function(m){
const name = m.match(/@[^*\/]*/)?.[0].replace(/@|\n|\s/g,'') || ''
sqlNames.push([name, m])
return ``
})
let sqlCopy = sql
return sqlNames.reverse().reduce((a,b)=>{
const value = sqlCopy.slice(sql.lastIndexOf(b[1]))
a[b[0]] = value.replace(b[1],'')
sqlCopy = sqlCopy.replace(value, '')
return a
},{})
}
const waitForSelector = async (selector: string) => {
return await page.evaluate(async function name(selector) {
if (!document.querySelector(selector)) {
return await new Promise(r => {
requestAnimationFrame(async () => {
await name(selector)
r(true)
})
})
}
},selector)
}
import { CronJob } from 'cron';
import { execSync, execFileSync } from 'child_process';
new CronJob('* * * * * *',()=>{
try {
execSync(`
screen_status=$(adb shell dumpsys power | grep "Display Power" | grep -o 'OFF')
if [ "$screen_status" = "OFF" ]; then
echo "Screen is off";
adb shell input keyevent 26;
fi;
adb shell dumpsys window | grep -i "current=[immersive]"
adb devices
`,{
stdio:'inherit',
});
}catch (error) {
console.log(error)
}
}).start();
aliases command-not-found dirhistory extract git-prompt macos vscode z
colored-man-pages copyfile docker git history nmap wd
colorize copypath dotenv git-commit jsontools sudo web-search
{
manualChunks(id) {
const deps = ['wp-request', 'lodash', 'vueuse/', 'vue/', 'lodash-es', 'vconsole-hide', 'gsap', 'qrcode', 'vant'];
const dep = deps.find((dep) => new RegExp(`${__dirname}/node_modules.*${dep}`).test(id));
if (dep) {
return dep.replace(/\//g, '');
}
const depslocl = ['api', 'alert', 'datas', 'utils'];
const dep2 = depslocl.find((dep) => id.includes(path.resolve(__dirname, 'src', dep)));
if (dep2) {
return dep2;
}
},
}
/* 1. Use a more-intuitive box-sizing model */
*, *::before, *::after {
box-sizing: border-box;
}
/* 2. Remove default margin */
* {
margin: 0;
}
body {
/* 3. Add accessible line-height */
line-height: 1.5;
/* 4. Improve text rendering */
-webkit-font-smoothing: antialiased;
}
/* 5. Improve media defaults */
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
}
/* 6. Inherit fonts for form controls */
input, button, textarea, select {
font: inherit;
}
/* 7. Avoid text overflows */
p, h1, h2, h3, h4, h5, h6 {
overflow-wrap: break-word;
}
/* 8. Improve line wrapping */
p {
text-wrap: pretty;
}
h1, h2, h3, h4, h5, h6 {
text-wrap: balance;
}
/*
9. Create a root stacking context
*/
#root, #__next {
isolation: isolate;
}
import 'package:flutter/foundation.dart';
import 'package:dart_sm/dart_sm.dart';
class CryptoUtil {
static String convertToHex(String input) {
return input.runes.map((rune) {
return rune.toRadixString(16);
}).join();
}
static final String _SM4KEY = "";
static final String iv = convertToHex(_SM4KEY);
//SM4加密
static String encryptedSM4(String content) {
SM4.setKey(iv);
String cipherText = SM4.encrypt(content, mode: SM4CryptoMode.CBC, iv: iv);
return cipherText.toLowerCase();
}
//SM4解密
static String decryptSM4(String content) {
SM4.setKey(iv);
//Stopwatch stopwatch = Stopwatch()..start();
String cbcDecryptData =
SM4.decrypt(content, mode: SM4CryptoMode.CBC, iv: iv);
//stopwatch.stop();
// print('执行时间:${stopwatch.elapsedMilliseconds} 毫秒');
return cbcDecryptData;
}
static Future<String> encryptedSM4ByAsync(String data) async {
return await compute(encryptedSM4, data);
}
static Future<String> decryptSM4ByAsync(String data) async {
return await compute(decryptSM4, data);
}
}
dir='packages'
node_modules_dir='node_modules'
package_json_dir='package.json'
packages=($(echo $(cat $package_json_dir | grep -e 'http' | awk '{print $1}' | sed 's/^"//g' | sed 's/":$//g')))
rm -rf $dir
for i in ${packages[@]};
do
target=$dir/$i
mkdir -p $target
ls $node_modules_dir/$i | grep -E -v "node_modules" | xargs -I {} cp -r $node_modules_dir/$i/{} $target
done
{
'/242': {
target: 'http://192.168.110.242/',
rewrite: (path) => {
console.log(path);
return path.replace(/^\/242/, '');
},
headers: {
Referrer: 'http://192.168.110.242'
},
autoRewrite: true,
selfHandleResponse: true,
// changeOrigin: true,
ws: true,
configure(proxy: HttpProxy.Server) {
proxy.on('proxyRes', (proxyRes: IncomingMessage, req: IncomingMessage, res: ServerResponse) => {
const chunks: any = [];
proxyRes.on('data', (chunk) => {
chunks.push(chunk);
});
proxyRes.on('end', () => {
res.setHeader('access-control-allow-origin', '*');
res.end(Buffer.concat(chunks));
});
});
}
}
}
import { spawn } from "child_process";
import { watch } from "chokidar";
const run = () => {
const child = spawn("flutter", ["run"], {
stdio: "inherit",
cwd: process.cwd(),
});
return child;
};
let child = run();
watch("./pdf_explorer", {
cwd: process.cwd(),
awaitWriteFinish: true,
}).on("change", (event, path) => {
child.kill();
child = run();
});
launch.json
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"command": "tsnd --respawn serve.ts ",
"name": "Run serve",
"request": "launch",
"type": "node-terminal"
},
{
"name": "Flutter Attach",
"request": "attach",
"type": "dart",
"flutterMode": "debug",
"deviceId": "all"
}
]
}
配合浏览器字体api完成,如 document.fonts
document.fonts.values()
// 检查特定字体是否已加载
function isFontAvailable(fontName) {
return document.fonts.check(`16px "${fontName}"`);
}
// 使用示例
if (isFontAvailable('MySpecialFont')) {
console.log('Font is available!');
} else {
console.log('Font is not available.');
}
const pdfjsLib = require('pdfjs-dist/build/pdf');
async function checkMissingFonts(pdfUrl) {
const loadingTask = pdfjsLib.getDocument(pdfUrl);
const pdf = await loadingTask.promise;
const missingFonts = new Set();
for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
const page = await pdf.getPage(pageNum);
const operatorList = await page.getOperatorList();
operatorList.argsArray.forEach((args, index) => {
// 检查操作符是否为使用字体的操作
if (operatorList.fnArray[index] === pdfjsLib.OPS.setFont) {
const fontName = args[0];
// 记录字体名称
missingFonts.add(fontName);
}
});
}
console.log('Missing Fonts:', Array.from(missingFonts));
}
// 使用示例
checkMissingFonts('path/to/your.pdf');
更改字体,需要启用pdfBug模式
window.FontInspector = {
enabled: true,
fontAdded(font) {
if (["g_d0_f1", "g_d0_f20", "g_d0_f3"].includes(font.loadedName)) {
return;
}
font.loadedName = "Nabla";
},
}
{
controlFlowFlattening: true,
stringArrayThreshold: 1,
unicodeEscapeSequence: true,
stringArrayEncoding: ['none', 'base64', 'rc4'],
forceTransformStrings: ['.'],
deadCodeInjection: true,
deadCodeInjectionThreshold: 1,
numbersToExpressions: true,
renameGlobals: true,
splitStrings: true,
stringArray: true,
disableConsoleOutput: true,
}
(function _debuggerInit(){
// Check if the DevTools are open by measuring the time taken to execute a function
const start = Date.now();
new Function(`debugger;`)()
const end = Date.now()
if(Date.now() - start > 100){
location.replace('about:blank')
}
setTimeout(()=>{
_debuggerInit()
})
})()
<template>
<div class="abs-f z-100000 right-0 bottom-$h5-bottom-nav-height tr-y--150px levitated-sphere" :style="style"
ref="el">
<Drager ref="drager" @drag-end="handleDragEnd" @drag-start="handleDragStart" v-bind="info2" v-if="show">
<div class="op-$op levitated-sphere-content">
<slot>
悬浮内容
</slot>
</div>
</Drager>
</div>
</template>
<script setup lang="ts">
import Drager from 'es-drager'
import winframe from 'winframe'
const props = withDefaults(defineProps<{
isOp?: boolean | number
}>(), {
isOp: true
})
const el = ref()
const { top, height } = useElementBounding(el)
const info = ref({
top: 0,
left: 0,
})
const info2 = ref({
top: 0,
left: 0,
})
const style = computed(() => {
return {
right: info.value.left + 'px',
bottom: info.value.top + 'px',
}
})
const posY = computed(() => {
return top.value + height.value
})
const drager = ref(null)
const show = ref(true)
const isOP = ref(true)
const handleDragStart = () => {
isOP.value = false
}
useCssVars(() => ({
op: props.isOp ? (isOP.value ? (typeof props.isOp === 'number' ? props.isOp : 0.5) : 1 as any) : 1
}))
const handleDragEnd = (e: any) => {
isOP.value = true
show.value = false
info.value.left += -e.left
info.value.top += -e.top
const left = info.value.left
nextTick(() => {
show.value = true
const copyPosY = posY.value
const copyPosYOffset = copyPosY - height.value
const top = info.value.top
winframe((p) => {
info.value.left = left * (1 - p)
if (copyPosYOffset < 0) {
info.value.top = top - height.value + copyPosYOffset * p
}
if (copyPosYOffset > innerHeight) {
info.value.top = top + (copyPosYOffset - innerHeight) * p
}
}, 100)
})
}
</script>
<style scoped lang="less">
.levitated-sphere {}
</style>
<template>
<div ref="history_el" class="abs-content hidden" :class="{
'pointer-events-none': !isShowHistory
}">
<div ref="history_mask_el" class="abs left-0 top-0 h-100% w-100% bg-#000 bg-op-36 op-0"
@click="handleShowHistory(false)"></div>
<div ref="history_content_el" class="abs left-0 top-0 h-100% w-80% bg-#fff">
<slot></slot>
</div>
</div>
</template>
<script setup lang="ts">
import winframe from 'winframe';
const history_el = ref() as unknown as Ref<HTMLDivElement>
const history_mask_el = ref() as unknown as Ref<HTMLDivElement>
const history_content_el = ref() as unknown as Ref<HTMLDivElement>
const isShowHistory = ref(false)
const debounceTime = ref(0)
const isDone = ref(true)
// timeout 单位ms,开启或关闭的动画时间
const handleShowHistory = async (bool: boolean, timeout = 300, isMoveMode?: boolean) => {
if (!isDone.value) return
isDone.value = false
debounceTime.value = performance.now()
const opacity = Number(history_mask_el.value.style.opacity)
if (bool) {
history_el.value.style.display = 'block'
history_mask_el.value.style.opacity = '0'
await nextTick()
const width = Math.abs(Number(history_content_el.value.style.transform.match(/translateX\((.*)px\)/)?.[1]) || history_content_el.value.offsetWidth)
history_content_el.value.style.transform = `translateX(${-width}px)`
await winframe(p => {
history_mask_el.value.style.opacity = (isMoveMode ? opacity + (1 - opacity) * p : p) as unknown as string
history_content_el.value.style.transform = `translateX(${-width * (1 - p)}px)`
}, timeout)
isShowHistory.value = true
} else {
history_el.value.style.display = 'block'
await nextTick()
const width = history_content_el.value.offsetWidth
const width2 = Math.abs(Number(history_content_el.value.style.transform.match(/translateX\((.*)px\)/)?.[1]))
history_mask_el.value.style.opacity = '1'
history_content_el.value.style.transform = `translateX(${isMoveMode ? -width2 : 0}px)`
await winframe(p => {
history_mask_el.value.style.opacity = (isMoveMode ? opacity * (1 - p) : (1 - p)) as unknown as string
const translateX = isMoveMode ? -width2 - (width - width2) * p : -width * p
history_content_el.value.style.transform = `translateX(${translateX}px)`
}, timeout)
history_content_el.value.style.transform = `translateX(${-width}px)`
history_mask_el.value.style.opacity = '0'
history_el.value.style.display = 'none'
isShowHistory.value = false
}
if (performance.now() - debounceTime.value > timeout) {
isDone.value = true
}
}
defineExpose({
handleShowHistory
})
const useTouchmove = (cb: (data: {
x: number,
y: number,
event: TouchEvent,
type: 'touchstart' | 'touchmove' | 'touchend',
isTouchstart: boolean,
}) => void) => {
let clientX = 0
let clientY = 0
let offsetX = 0
let offsetY = 0
let isTouchstart = false
const touchstart = (e: TouchEvent) => {
clientX = e.touches[0].clientX
clientY = e.touches[0].clientY
isTouchstart = true
cb({
x: offsetX,
y: offsetY,
event: e,
type: 'touchstart',
isTouchstart,
})
}
const touchmove = (e: TouchEvent) => {
if (!isTouchstart) return
offsetX = e.touches[0].clientX - clientX
offsetY = e.touches[0].clientY - clientY
cb({
x: offsetX,
y: offsetY,
event: e,
type: 'touchmove',
isTouchstart,
})
}
const touchend = (e: TouchEvent) => {
cb({
x: offsetX,
y: offsetY,
event: e,
type: 'touchend',
isTouchstart,
})
isTouchstart = false
clientX = 0
clientY = 0
offsetX = 0
offsetY = 0
}
return {
start() {
window.addEventListener('touchstart', touchstart)
window.addEventListener('touchmove', touchmove)
window.addEventListener('touchend', touchend)
},
stop() {
window.removeEventListener('touchstart', touchstart)
window.removeEventListener('touchmove', touchmove)
window.removeEventListener('touchend', touchend)
}
}
}
const moveRectWidth = ref(0)
const hasScrollbar: any = (element: HTMLElement) => {
if (!element || element.attributes.getNamedItem('history-max-box')) { return false }
return element?.scrollHeight > element?.clientHeight || hasScrollbar(element?.parentElement as any) as unknown as any;
}
const {
start,
stop
} = useTouchmove(async ({ x, y, type, isTouchstart, event }) => {
if (hasScrollbar(event.target as unknown as any)) {
return
}
const mx = 50
if (Math.abs(y) > mx) {
handleShowHistory(false, undefined, true)
return
}
if (isShowHistory.value || !history_el.value) { return }
const offsetMvX = x - mx
const offset = -moveRectWidth.value + offsetMvX
setTimeout(async () => {
if (type === 'touchstart') {
history_el.value.style.display = 'block'
history_mask_el.value.style.opacity = '0'
history_content_el.value.style.transform = `translateX(-100%)`
await nextTick()
moveRectWidth.value = history_content_el.value.offsetWidth
return
}
if (type === 'touchend') {
// Math.abs(offsetMvX) > window.innerWidth / 6 判断是否是现实滑动的最大阀值,默认是屏幕的1/6
handleShowHistory(Math.abs(offsetMvX) > window.innerWidth / 6, undefined, true)
return
}
}, 0)
if (isTouchstart && type === 'touchmove') {
if (offset > 0 && offset < moveRectWidth.value) { return }
if (x > mx) {
history_mask_el.value.style.opacity = (1 - Math.abs(offset / moveRectWidth.value) as unknown as string)
history_content_el.value.style.transform = `translateX(${offset}px)`
}
}
})
onMounted(() => {
start()
})
onBeforeUnmount(() => {
stop()
})
</script>
<style scoped lang="less">
.history {}
</style>
const useTouchmove = (cb: (data: {
x: number,
y: number,
event: TouchEvent,
type: 'touchstart' | 'touchmove' | 'touchend',
isTouchstart: boolean,
}) => void) => {
let clientX = 0
let clientY = 0
let offsetX = 0
let offsetY = 0
let isTouchstart = false
const touchstart = (e: TouchEvent) => {
clientX = e.touches[0].clientX
clientY = e.touches[0].clientY
isTouchstart = true
cb({
x: offsetX,
y: offsetY,
event: e,
type: 'touchstart',
isTouchstart,
})
}
const touchmove = (e: TouchEvent) => {
if (!isTouchstart) return
offsetX = e.touches[0].clientX - clientX
offsetY = e.touches[0].clientY - clientY
cb({
x: offsetX,
y: offsetY,
event: e,
type: 'touchmove',
isTouchstart,
})
}
const touchend = (e: TouchEvent) => {
cb({
x: offsetX,
y: offsetY,
event: e,
type: 'touchend',
isTouchstart,
})
isTouchstart = false
clientX = 0
clientY = 0
offsetX = 0
offsetY = 0
}
return {
start() {
window.addEventListener('touchstart', touchstart)
window.addEventListener('touchmove', touchmove)
window.addEventListener('touchend', touchend)
},
stop() {
window.removeEventListener('touchstart', touchstart)
window.removeEventListener('touchmove', touchmove)
window.removeEventListener('touchend', touchend)
}
}
}
<template>
<n-form
class="formValidate"
ref="formRef"
:rules="rules"
:model="modelValue"
v-bind="config"
>
<n-form-item
v-for="(item, index) in field"
:key="index"
:label="item.label"
:path="item.field"
v-bind="item.config"
>
<template v-if="item.component === 'input'">
<n-input
v-model:value="modelValue[item.field]"
v-bind="item.props"
/>
</template>
<template v-else-if="item.component === 'select'">
<n-select
v-model:value="modelValue[item.field]"
v-bind="item.props"
/>
</template>
<template v-else-if="item.component === 'cascader'">
<n-cascader
v-model:value="modelValue[item.field]"
v-bind="item.props"
/>
</template>
<template v-else-if="item.component === 'datePicker'">
<n-date-picker
v-model:value="modelValue[item.field]"
v-bind="item.props"
/>
</template>
<template v-else-if="item.component === 'switch'">
<n-switch
v-model:value="modelValue[item.field]"
v-bind="item.props"
>
<!-- 动态插槽继承,后续其他组件也可以这样做 -->
<template
v-for="(slotItem, key) in item?.slots"
:key="key"
#[key]="scope"
>
<component
:is="item?.slots?.[key]"
:field="item.field"
:rules="item.rules"
:formConfig="config"
:formData="modelValue"
v-bind="scope"
/>
</template>
</n-switch>
</template>
<template v-else-if="item.component === 'upload'">
<n-pro-upload
v-model:value="modelValue[item.field]"
v-bind="item.props"
>
</n-pro-upload>
</template>
<template v-else>
<component
v-if="item.component"
:is="item.component"
v-model="modelValue[item.field]"
:field="item.field"
:rules="item.rules"
:formConfig="config"
:formData="modelValue"
v-bind="item.props"
/>
</template>
</n-form-item>
</n-form>
</template>
<script setup lang="ts">
import { FormRules, FormProps } from 'naive-ui';
const formRef = ref();
const props = defineProps<{
modelValue: Record<string, any>;
field: FormValidateField;
config?: FormProps;
}>();
const emit = defineEmits(['update:modelValue']);
const { modelValue, field, config } = useVModels(props, emit);
const rules = computed(() => {
return (field.value || []).reduce<FormRules>((acc, item) => {
acc[item.field] = item.rules as FormRules[string];
return acc;
}, {} as Record<string, FormRules[string]>);
});
defineExpose({
form: formRef,
validate: () => {
return formRef.value?.validate();
},
});
</script>
<style scoped lang="less">
.formValidate {
}
</style>
export {};
import {
FormRules,
FormItemProps,
InputProps,
CascaderProps,
SelectProps,
DatePickerProps,
SwitchProps,
UploadProps,
} from 'naive-ui';
type FormValidateFieldItemComponent = {
input: InputProps;
select: SelectProps;
cascader: CascaderProps;
datePicker: DatePickerProps;
switch: SwitchProps;
upload: UploadProps;
};
import { Component, VNode, ExtractPropTypes } from 'vue';
declare global {
type FormValidateField = FormValidateFieldItem[];
type FormValidateFieldItem<
C = keyof FormValidateFieldItemComponent | Component | VNode
> = {
label?: string;
component: C;
field: string;
rules?: FormRules[string];
config?: FormItemProps;
props?: C extends keyof FormValidateFieldItemComponent
? FormValidateFieldItemComponent[C]
: C extends VNode | Component
? ExtractPropTypes<C>
: never;
slots?: {
[key: string]: Component | VNode;
};
};
}
declare module "ncol" {
interface Ncol {
log(...arg: any[]): Ncol;
error(...arg: any[]): Ncol;
errorBG(...arg: any[]): Ncol;
black(...arg: any[]): Ncol;
blue(...arg: any[]): Ncol;
success(...arg: any[]): Ncol;
successBG(...arg: any[]): Ncol;
info(...arg: any[]): Ncol;
infoBG(...arg: any[]): Ncol;
color(callback: (this: Ncol) => void): Ncol;
}
const ncol: Ncol;
export = ncol;
}
import fs from "fs";
import path from "path";
import ExcelJS from "exceljs";
// 定义Excel文件路径和保存图片的目录
const excelFilePath = "2.xlsx"; // 替换为你的Excel文件路径
const outputDir = "./output_images"; // 图片保存目录
// 确保输出目录存在
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
async function extractImagesFromExcel(filePath: any) {
try {
// 创建一个新的工作簿实例
const workbook = new ExcelJS.Workbook();
// 加载Excel文件
await workbook.xlsx.readFile(filePath);
// 遍历每个工作表
for (const file of (workbook as any).model.media) {
if (file.type === "image") {
try {
// 将图片保存到文件
const imagePath = `${outputDir}/${file.name}.png`;
fs.writeFileSync(imagePath, file.buffer);
console.log(`Saved image: ${imagePath}`);
} catch (e) {}
}
}
console.log("All images extracted successfully.");
} catch (error) {
console.error("Error extracting images:", error);
}
}
// 调用函数
extractImagesFromExcel(excelFilePath);