首页
更多应用
Search
1
修改iview的标签为i-的形式而不是驼峰的形式
3,148 阅读
2
PHP微信和企业微信签名
2,881 阅读
3
在VUE中怎么全局引入sass文件
2,522 阅读
4
解决Macos下storm系列IDE卡顿的问题
2,231 阅读
5
vscode硬件占用较高解决方案
2,185 阅读
默认分类
JS
VUE
CSS
mac使用技巧
React
fastmock
登录
/
注册
Search
标签搜索
react
js
vue
vscode
nodejs
项目
代码
webpack
工具
nginx
小程序
css
fastmock
eslint
npm
http
vue-cli3
git
浏览器
const
fastmock技术社区
累计撰写
105
篇文章
累计收到
26
条评论
首页
栏目
默认分类
JS
VUE
CSS
mac使用技巧
React
fastmock
页面
更多应用
搜索到
105
篇与
的结果
2025-07-21
小程序给图片添加水印(时间,经纬度信息)
在移动互联网时代,小程序因其轻量、便捷的特性,逐渐成为用户日常生活中不可或缺的工具。无论是社交、购物,还是记录生活,小程序都提供了丰富的功能。然而,随着用户对数据安全和信息追溯需求的增加,如何在图片中添加关键信息(如时间、经纬度等)成为开发者需要解决的问题之一。尤其是在一些特定场景下,如户外打卡、旅行记录、证据留存等,为图片添加时间戳和地理位置信息不仅能增强数据的可信度,还能为用户提供更丰富的上下文信息。本文将详细介绍如何在小程序中实现为图片添加时间、经纬度信息的水印功能。我们将从获取用户地理位置、格式化时间信息,到将水印动态添加到图片中,一步步讲解实现过程。无论你是小程序开发新手,还是有一定经验的开发者,都能通过本文掌握这一实用技能,为你的小程序增添更多实用价值。让我们一起探索如何通过技术手段,让每一张图片都“有迹可循”。实现思路如下:1、使用小程序的canvas来绘制图片,画布的大小根据图片的实际宽高设置2、在画布中左下角位置一个黑色半透明的矩形3、在矩形上绘制文本,文本内容为时间和经纬度4、使用 wx.canvasToTempFilePath 方法将canvas内容导出成图片缓存到小程序本地存储5、使用 wx.compressImage 压缩图片,这一步很重要,因为通过canvas直接导出的图片非常大,所以要根据情况压缩图片import moment from '../../utils/moment'; export const addWatermark = (pageIns, imgpath, position) => { const time = moment().format('YYYY-MM-DD HH:mm:ss'); return new Promise((resolve, reject) => { const dataSrc = imgpath; const query = pageIns.createSelectorQuery(); console.log('query canvas result', query); query.select('#watermark-canvas') .fields({ node: true, size: true }) .exec(async (res) => { console.log('query canvas result', res); const canvas = res[0].node; const ctx = canvas.getContext('2d'); // 加载图片 const img = canvas.createImage(); img.src = dataSrc; // 替换为你的图片地址 img.onload = () => { // 获取原图的宽高 const imgWidth = img.width; const imgHeight = img.height; console.log('待压缩图片宽高', imgWidth, imgHeight); // 设置 Canvas 的实际宽高为原图的宽高 const dpr = wx.getSystemInfoSync().pixelRatio; canvas.width = imgWidth * dpr; canvas.height = imgHeight * dpr; ctx.scale(dpr, dpr); // 绘制图片 ctx.drawImage(img, 0, 0, imgWidth, imgHeight); // 绘制黑色半透明矩形块 ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; // ctx.fillRect(0, imgHeight - 60, imgWidth, 60); // 矩形块位于图片底部 ctx.fillRect(0, imgHeight - 60, 210, 60); // 绘制水印文本 ctx.font = '16px sans-serif'; ctx.fillStyle = '#ffffff'; ctx.fillText(time, 20, imgHeight - 35); // 文本位于矩形块内 ctx.fillText(position.latitude + '--' + position.longitude, 20, imgHeight - 14); // 文本位于矩形块内 // 生成临时文件路径 wx.canvasToTempFilePath({ canvas, fileType: 'jpg', destWidth: imgWidth, destHeight: imgHeight, quality: 0.5, success: (res) => { const tempFilePath = res.tempFilePath; wx.compressImage({ src: tempFilePath, // 图片路径 quality:60, // 压缩质量 success({tempFilePath: tempPathAfterCompress}) { console.log("压缩成功", tempPathAfterCompress) wx.getFileInfo({ filePath: tempPathAfterCompress, success(resInfo) { console.log('添加水印后文件大小(KB):', (resInfo.size / 1024).toFixed(2)); // 输出文件大小(KB),保留两位小数 console.log('添加水印后文件大小(MB):', (resInfo.size / (1024 * 1024)).toFixed(2)); // 输出文件大小(MB),保留两位小数 }, fail(err) { console.error('图片添加水印->获取文件信息失败', err); } }); resolve(tempPathAfterCompress); } }) }, fail: (error) => { console.info('给图片加水印,导出canvas处理后的图片错误', error) } }); }; }); }); } export const batchAddWatermark = (pageIns, paths, position) => { console.log('batchAddWatermark', paths); const promises = paths?.map(p => { return addWatermark(pageIns, p, position); }); console.log('promises of add watermark', promises); return Promise.all(promises) }
2025年07月21日
7 阅读
0 评论
0 点赞
2025-05-20
关于vscode remote-ssh远程开发的常见问题
1、VSCode远程连接报错报错信息:Remote host key has changed, port forwarding is disabled could not establish connection to “”:Remote host key has changed, port forwarding is disabled错误原因是原来用VSCode远程过服务器,后来服务器重装了,再用原来的ssh去连接服务器需要重新添加连接信息解决的方法:客户端是windows:是在C:\Users\用户名.ssh上找到known_hosts文件,是隐藏文件,需要先到文件管理器的设置里显示隐藏文件,然后,将里面原来关于该服务器的内容删掉,再重新添加和连接。客户端是Mac:/Users/用户名/.ssh下找到known_hosts文件,将里面原来关于该服务器的内容删掉,再重新添加和连接。2、免密登录初始状态下,每次连接remote都需要手动输入服务器密码来连接,有点麻烦,可以通过配置安全秘钥的方式,让服务器和本地vscode之间自动通过秘钥认证,操作很简单,就是将本地的ssh公钥(一般在~/.ssh目录下的id_rsa.pub文件)复制到服务器的 ~/.ssh/authorized_keys 文件中PS:如果本地还没有ssh文件,通过 ssh-keygen 生成就好
2025年05月20日
39 阅读
0 评论
0 点赞
2025-04-25
three.js中的重要基础概念
Three.js 是一个功能强大的 JavaScript 库,用于创建和展示基于 WebGL 的三维图形。在学习使用Three.js来构建3D世界之前,有一些基本概念是需要牢记的,否则,在你绘制3D世界时,思绪会是杂乱无章的:场景(Scene)定义:场景是所有三维对象的容器,用于存储和管理几何体、光源、相机等元素。作用:场景是渲染的基础,所有需要显示的对象都必须添加到场景中。创建:const scene = new THREE.Scene();相机(Camera)定义:相机定义了从哪个视角观察三维场景。常见类型:透视相机(PerspectiveCamera):模拟人眼透视效果,远处物体看起来更小。const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);正交相机(OrthographicCamera):不考虑透视效果,所有物体大小一致。const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.1, 1000);渲染器(Renderer)定义:渲染器负责将场景和相机中的内容绘制到屏幕上。常用渲染器:THREE.WebGLRenderer 是最常用的渲染器。创建:const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement);几何体(Geometry)定义:几何体定义了物体的形状。常见几何体:BoxGeometry:立方体。SphereGeometry:球体。PlaneGeometry:平面。BufferGeometry:更高效的自定义几何体。创建:const geometry = new THREE.BoxGeometry(1, 1, 1);材质(Material)定义:材质定义了物体的外观属性,如颜色、纹理、反射等。常见材质:MeshBasicMaterial:基础材质,不考虑光照。MeshLambertMaterial:漫反射材质,适合哑光表面。MeshPhongMaterial:高光材质,适合光滑表面。MeshStandardMaterial:基于物理的材质,适合现代渲染效果。创建:const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });网格(Mesh)定义:网格是几何体和材质的组合,用于创建具体的三维物体。创建:const mesh = new THREE.Mesh(geometry, material); scene.add(mesh);光源(Light)定义:光源用于照亮场景中的物体。现实中的一切物体之所以能被看到,都是因为有了光,在three.js 的世界里,亦是如此,需要看到哪个地方,就需要有光线照亮那个地方常见光源:AmbientLight:环境光,均匀照亮整个场景。DirectionalLight:方向光,模拟太阳光。PointLight:点光源,从一点向四周发射光线。SpotLight:聚光灯,模拟手电筒或舞台灯光。创建:const light = new THREE.DirectionalLight(0xffffff, 1); scene.add(light);控制器(Controls)定义:控制器用于控制相机的移动和旋转,实现用户交互。常见控制器:OrbitControls:允许用户通过鼠标或触摸屏旋转、平移和缩放视图。TrackballControls:类似于轨道球的交互方式。使用:const controls = new THREE.OrbitControls(camera, renderer.domElement);加载器(Loader)定义:加载器用于加载外部资源,如模型、纹理、字体等。这个库很重要,没有这个库,所有的东西都需要我们用代码来绘制,这是相当庞大的工作量,而有了这个库,我们就可以加载设计师和建模师做好了的模型,纹理图等内容到场景中,常见加载器:GLTFLoader:加载 GLTF 格式的 3D 模型。TextureLoader:加载图像作为纹理。FontLoader:加载字体文件。使用:const loader = new THREE.TextureLoader(); const texture = loader.load('texture.jpg');动画(Animation)定义:动画用于实现动态效果,如旋转、移动、缩放等。实现方式:使用 requestAnimationFrame 实现循环更新。使用 THREE.AnimationMixer 播放模型动画。示例:function animate() { requestAnimationFrame(animate); mesh.rotation.y += 0.01; renderer.render(scene, camera); } animate();坐标系(Coordinate System)定义:Three.js 使用右手坐标系,X 轴指向右方,Y 轴指向上方,Z 轴指向屏幕外。注意:默认情况下,原点位于场景中心。缓冲区几何体(BufferGeometry)定义:BufferGeometry 是一种高效的数据结构,用于定义复杂的几何体。优点:性能更高,适合大规模数据处理。使用:const geometry = new THREE.BufferGeometry(); const vertices = new Float32Array([ 0, 0, 0, 1, 0, 0, 0, 1, 0 ]); geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));辅助工具(Helpers)定义:辅助工具用于可视化调试,如网格、轴、光源范围等。常见工具:GridHelper:显示网格。AxesHelper:显示坐标轴。DirectionalLightHelper:显示方向光的方向。雾效(Fog)定义:雾效用于模拟远近物体的模糊效果。使用:scene.fog = new THREE.Fog(0xffffff, 1, 10);
2025年04月25日
18 阅读
0 评论
0 点赞
2025-01-03
好用下电视的网站
记录一些好用,稳定,下电视的网站大米看吧 https://www.dmkanba.com/movie80s http://xiepp.com/ 这个网站从高中老年机看3gp 格式的电视开始就在这里下载了,但是不知道现在为啥总是需要频繁换域名
2025年01月03日
25 阅读
0 评论
0 点赞
2024-12-24
vscode 打开ts文件没有了代码补全和错误提示右下角报错The JS/TS language service immediately crashed 5 times. The service will not be restarted.
更新了vscode的版本到最新版本,突然就不能愉快地编写ts代码了,具体表现为 - 使用对象里面的方法无法提示了 - 方法没有函数注释的提示,没有参数提示了,看不到参数类型和返回值类型了 - 无法 cmd+鼠标左键跳转方法或对象定义
2024年12月24日
136 阅读
0 评论
0 点赞
2024-10-28
element-ui中eslint配置模板参考
element-ui 框架源代码中,关于vue3项目的eslint配置包含了 vue,ts,jsx,的eslint格式检验,还有各种不同文件的overrides,做个记录方便以后参考,index.jsconst { defineConfig } = require('eslint-define-config') module.exports = defineConfig({ env: { es6: true, browser: true, node: true, }, plugins: ['@typescript-eslint', 'prettier', 'unicorn'], extends: [ 'eslint:recommended', 'plugin:import/recommended', 'plugin:eslint-comments/recommended', 'plugin:jsonc/recommended-with-jsonc', 'plugin:markdown/recommended', 'plugin:vue/vue3-recommended', 'plugin:@typescript-eslint/recommended', 'prettier', ], settings: { 'import/resolver': { node: { extensions: ['.js', '.mjs', '.ts', '.d.ts', '.tsx'] }, }, }, overrides: [ { files: ['*.json', '*.json5', '*.jsonc'], parser: 'jsonc-eslint-parser', }, { files: ['*.ts', '*.vue'], rules: { 'no-undef': 'off', }, }, { files: ['**/__tests__/**'], rules: { 'no-console': 'off', 'vue/one-component-per-file': 'off', }, }, { files: ['package.json'], parser: 'jsonc-eslint-parser', rules: { 'jsonc/sort-keys': [ 'error', { pathPattern: '^$', order: [ 'name', 'version', 'private', 'packageManager', 'description', 'type', 'keywords', 'homepage', 'bugs', 'license', 'author', 'contributors', 'funding', 'files', 'main', 'module', 'exports', 'unpkg', 'jsdelivr', 'browser', 'bin', 'man', 'directories', 'repository', 'publishConfig', 'scripts', 'peerDependencies', 'peerDependenciesMeta', 'optionalDependencies', 'dependencies', 'devDependencies', 'engines', 'config', 'overrides', 'pnpm', 'husky', 'lint-staged', 'eslintConfig', ], }, { pathPattern: '^(?:dev|peer|optional|bundled)?[Dd]ependencies$', order: { type: 'asc' }, }, ], }, }, { files: ['*.d.ts'], rules: { 'import/no-duplicates': 'off', }, }, { files: ['*.js'], rules: { '@typescript-eslint/no-var-requires': 'off', }, }, { files: ['*.vue'], parser: 'vue-eslint-parser', parserOptions: { parser: '@typescript-eslint/parser', extraFileExtensions: ['.vue'], ecmaVersion: 'latest', ecmaFeatures: { jsx: true, }, }, rules: { 'no-undef': 'off', }, }, { files: ['**/*.md/*.js', '**/*.md/*.ts'], rules: { 'no-console': 'off', 'import/no-unresolved': 'off', '@typescript-eslint/no-unused-vars': 'off', }, }, ], rules: { // js/ts camelcase: ['error', { properties: 'never' }], 'no-console': ['warn', { allow: ['error'] }], 'no-debugger': 'warn', 'no-constant-condition': ['error', { checkLoops: false }], 'no-restricted-syntax': ['error', 'LabeledStatement', 'WithStatement'], 'no-return-await': 'error', 'no-var': 'error', 'no-empty': ['error', { allowEmptyCatch: true }], 'prefer-const': [ 'warn', { destructuring: 'all', ignoreReadBeforeAssign: true }, ], 'prefer-arrow-callback': [ 'error', { allowNamedFunctions: false, allowUnboundThis: true }, ], 'object-shorthand': [ 'error', 'always', { ignoreConstructors: false, avoidQuotes: true }, ], 'prefer-rest-params': 'error', 'prefer-spread': 'error', 'prefer-template': 'error', 'no-redeclare': 'off', '@typescript-eslint/no-redeclare': 'error', // best-practice 'array-callback-return': 'error', 'block-scoped-var': 'error', 'no-alert': 'warn', 'no-case-declarations': 'error', 'no-multi-str': 'error', 'no-with': 'error', 'no-void': 'error', 'sort-imports': [ 'warn', { ignoreCase: false, ignoreDeclarationSort: true, ignoreMemberSort: false, memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'], allowSeparatedGroups: false, }, ], // stylistic-issues 'prefer-exponentiation-operator': 'error', // ts '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-non-null-asserted-optional-chain': 'off', '@typescript-eslint/consistent-type-imports': [ 'error', { disallowTypeAnnotations: false }, ], '@typescript-eslint/ban-ts-comment': ['off', { 'ts-ignore': false }], // vue 'vue/no-v-html': 'off', 'vue/require-default-prop': 'off', 'vue/require-explicit-emits': 'off', 'vue/multi-word-component-names': 'off', 'vue/prefer-import-from-vue': 'off', 'vue/no-v-text-v-html-on-component': 'off', 'vue/html-self-closing': [ 'error', { html: { void: 'always', normal: 'always', component: 'always', }, svg: 'always', math: 'always', }, ], // prettier 'prettier/prettier': 'error', // import 'import/first': 'error', 'import/no-duplicates': 'error', 'import/order': [ 'error', { groups: [ 'builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type', ], pathGroups: [ { pattern: 'vue', group: 'external', position: 'before', }, { pattern: '@vue/**', group: 'external', position: 'before', }, { pattern: '@element-plus/**', group: 'internal', }, ], pathGroupsExcludedImportTypes: ['type'], }, ], 'import/no-unresolved': 'off', 'import/namespace': 'off', 'import/default': 'off', 'import/no-named-as-default': 'off', 'import/no-named-as-default-member': 'off', 'import/named': 'off', 'no-restricted-imports': [ 'error', { paths: [ { name: 'lodash', message: 'Use lodash-unified instead.' }, { name: 'lodash-es', message: 'Use lodash-unified instead.' }, ], patterns: [ { group: ['lodash/*', 'lodash-es/*'], message: 'Use lodash-unified instead.', }, ], }, ], // eslint-plugin-eslint-comments 'eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }], // unicorn 'unicorn/custom-error-definition': 'error', 'unicorn/error-message': 'error', 'unicorn/escape-case': 'error', 'unicorn/import-index': 'error', 'unicorn/new-for-builtins': 'error', 'unicorn/no-array-method-this-argument': 'error', 'unicorn/no-array-push-push': 'error', 'unicorn/no-console-spaces': 'error', 'unicorn/no-for-loop': 'error', 'unicorn/no-hex-escape': 'error', 'unicorn/no-instanceof-array': 'error', 'unicorn/no-invalid-remove-event-listener': 'error', 'unicorn/no-new-array': 'error', 'unicorn/no-new-buffer': 'error', 'unicorn/no-unsafe-regex': 'off', 'unicorn/number-literal-case': 'error', 'unicorn/prefer-array-find': 'error', 'unicorn/prefer-array-flat-map': 'error', 'unicorn/prefer-array-index-of': 'error', 'unicorn/prefer-array-some': 'error', 'unicorn/prefer-date-now': 'error', 'unicorn/prefer-dom-node-dataset': 'error', 'unicorn/prefer-includes': 'error', 'unicorn/prefer-keyboard-event-key': 'error', 'unicorn/prefer-math-trunc': 'error', 'unicorn/prefer-modern-dom-apis': 'error', 'unicorn/prefer-negative-index': 'error', 'unicorn/prefer-number-properties': 'error', 'unicorn/prefer-optional-catch-binding': 'error', 'unicorn/prefer-prototype-methods': 'error', 'unicorn/prefer-query-selector': 'error', 'unicorn/prefer-reflect-apply': 'error', 'unicorn/prefer-string-slice': 'error', 'unicorn/prefer-string-starts-ends-with': 'error', 'unicorn/prefer-string-trim-start-end': 'error', 'unicorn/prefer-type-error': 'error', 'unicorn/throw-new-error': 'error', }, }) package.json{ "name": "@element-plus/eslint-config", "version": "0.0.1", "description": "ESLint Config", "license": "MIT", "files": [ "index.js" ], "main": "index.js", "publishConfig": { "access": "public" }, "peerDependencies": { "eslint": "^8.0.0" }, "dependencies": { "@typescript-eslint/eslint-plugin": "^5.30.0", "@typescript-eslint/parser": "^5.30.0", "eslint-config-prettier": "^8.5.0", "eslint-define-config": "^1.5.1", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-jsonc": "^2.3.0", "eslint-plugin-markdown": "^3.0.0", "eslint-plugin-prettier": "^4.1.0", "eslint-plugin-unicorn": "^43.0.2", "eslint-plugin-vue": "^9.1.1", "jsonc-eslint-parser": "^2.1.0", "prettier": "^2.7.1", "typescript": "^4.7.4", "yaml-eslint-parser": "^1.0.1" }, "devDependencies": { "eslint": "^8.18.0" } }
2024年10月28日
88 阅读
0 评论
0 点赞
2024-10-17
npm安装提示‘current user ("nobody") does not have permission to access the dev dir XXX’
npm安装提示‘current user ("nobody") does not have permission to access the dev dir XXX’
2024年10月17日
64 阅读
0 评论
0 点赞
2024-09-14
处理TS类型声明文件,保留指定key的类型声明
我的原始需求是这样的,写了一个nodejs命令行工具,工具的功能是,拉取后端接口导出的 postman.json 接口内容,通过接口中的入参出参数据,生成入参出参的TS类型声明文件,达到在ts业务代码中可以校验接口入参和出参类型的目的,postman.json的大致格式如下{ "item": [ { "item": [ { "item": [], "name": "WechatMiniAppNatureController", "description": "WechatMiniAppNatureController" } ], "name": "wpe-miniwe-recycle-srv-api", "description": "exported at 2024-09-09 15:17:40" }, { "item": [ { "item": [ { "request": { "method": "POST", "description": "", "header": [ { "key": "Content-Type", "value": "application/json", "type": "text", "description": "" } ], "body": { "mode": "raw", "options": { "raw": { "language": "json" } }, "raw": "{\n \"Id\": 0\n}" }, "url": { "path": [ "wechat", "rec", "v1", "nature", "apply", "taxRebateInfo" ], "query": [], "host": "{{wpe-miniwe-recycle-srv-web}}", "raw": "{{wpe-miniwe-recycle-srv-web}}/wechat/rec/v1/nature/apply/taxRebateInfo" } }, "response": [ { "name": "退税申请详情接口-Example", "originalRequest": { "method": "POST", "description": "", "header": [ { "key": "Content-Type", "value": "application/json", "type": "text", "description": "" } ], "body": { "mode": "raw", "options": { "raw": { "language": "json" } }, "raw": "{\n \"Id\": 0\n}" }, "url": { "path": [ "wechat", "rec", "v1", "nature", "apply", "taxRebateInfo" ], "query": [], "host": "{{wpe-miniwe-recycle-srv-web}}", "raw": "{{wpe-miniwe-recycle-srv-web}}/wechat/rec/v1/nature/apply/taxRebateInfo" } }, "code": 200, "_postman_previewlanguage": "json", "header": [ { "name": "date", "key": "date", "value": "周一, 09 9月 202415:17:40 GMT", "description": "The date and time that the message was sent" } ], "body": "{\n \"Response\": {\n \"RequestId\": \"\",\n \"Error\": {\n \"Code\": \"\",\n \"Message\": \"\"\n },\n \"Data\": {\n \"id\": 0,\n \"taxRebateNumber\": \"\", //退税申请序号\n \"natureRecordId\": \"\", //自然人档案号\n \"registryNumber\": \"\", //登记序号\n \"collectionItemCode\": \"\", //征收项目代码\n \"collectionItemName\": \"\", //征收项目名称\n \"collectionCode\": \"\", //征收品目代码\n \"taxBureauCode\": \"\", //主管税务所科分局代码\n \"taxAuthorityCode\": \"\", //主管税务机关代码\n \"taxAmountAuthorityCode\": \"\", //税款所属税务机关代码\n \"taxAmountAuthorityName\": \"\", //税款所属税务机关名称\n \"streetTownCode\": \"\", //街道乡镇代码\n \"taxAmount\": 0.0, //应退税额\n \"taxRate\": 0.0, //税率\n \"taxUuid\": \"\", //税票UUID\n \"eleTaxNumber\": \"\", //电子税票号码\n \"taxStartTime\": \"\", //税款所属期起\n \"taxEndTime\": \"\", //税款所属期止\n \"applyStatus\": 0,\n \"rebateStatus\": \"\",\n \"rebateStatusCn\": \"\",\n \"taxStatus\": 0,\n \"rejectReason\": \"\",\n \"bankCardNo\": \"\",\n \"createTime\": \"\"\n }\n }\n}" } ], "name": "退税申请详情接口" } ], "name": "WechatMiniAppTaxController", "description": "WechatMiniAppTaxController" }, { "item": [ { "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json", "type": "text", "description": "" } ], "body": { "mode": "raw", "options": { "raw": { "language": "json" } }, "raw": "{\n \"QrId\": \"\"\n}" }, "url": { "path": [ "wechat", "rec", "v1", "operator", "inviteConfirm" ], "query": [], "host": "{{wpe-miniwe-recycle-srv-web}}", "raw": "{{wpe-miniwe-recycle-srv-web}}/wechat/rec/v1/operator/inviteConfirm" } }, "response": [ { "name": "operatorInviteConfirm-Example", "originalRequest": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json", "type": "text", "description": "" } ], "body": { "mode": "raw", "options": { "raw": { "language": "json" } }, "raw": "{\n \"QrId\": \"\"\n}" }, "url": { "path": [ "wechat", "rec", "v1", "operator", "inviteConfirm" ], "query": [], "host": "{{wpe-miniwe-recycle-srv-web}}", "raw": "{{wpe-miniwe-recycle-srv-web}}/wechat/rec/v1/operator/inviteConfirm" } }, "code": 200, "_postman_previewlanguage": "json", "header": [ { "name": "date", "key": "date", "value": "周一, 09 9月 202415:17:40 GMT", "description": "The date and time that the message was sent" } ], "body": "{\n \"Response\": {\n \"RequestId\": \"\",\n \"Error\": {\n \"Code\": \"\",\n \"Message\": \"\"\n },\n \"Data\": false\n }\n}" } ], "name": "operatorInviteConfirm" } ], "name": "WechatMiniAppOperatorController", "description": "WechatMiniAppOperatorController" } ], "name": "wpe-miniwe-recycle-srv-web", "description": "exported at 2024-09-09 15:17:40" } ], "info": { "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", "name": "wpe-miniwe-recycle-srv-20240909151740", "description": "exported at 2024-09-09 15:17:40" } }上面的接口文档中 接口地址为 /wechat/rec/v1/nature/apply/taxRebateInfo 的接口返回体,经过数据反解析后输出的Ts类型声明代码为export type TWechatRecV1NatureApplyTaxRebateInfoRess = { Code: number; Error: { Code: number; Message: string; }, RequestId: string; Data: { id: number; taxRebateNumber: string; natureRecordId: string; // ... 其他属性 } };上面的代码中,除了Data内的数据,其他的都是所有接口相同的属性内容,所以我需要处理生成的ts文件,只保留 Data 的类型描述。也尝试过好几种方案,主要有从源代码处理,在postman文件的response -> body 代码中处理完内容再去做反解析在生成的文件中通过字符串匹配去查找上面的方法中,主要都存在一个问题,就是postman源代码中,body包含了很多杂七杂八的内容,比如换行符,注释,还有转译字符,加上body内容的层级是不固定,这为我们做正则匹配带来了很多麻烦,所以兜兜转转想到了最终的解决方案,那就是使用AST的方法来处理生成的TS代码,这样在操作AST的过程中,babel 会帮我们处理好注释和其他不相关的内容。先show一下最终的代码// 要先安装下面的依赖 const parser = require('@babel/parser'); const traverse = require('@babel/traverse').default; const generate = require('@babel/generator').default; /** * 取出声明代码中指定key的interface代码 * 使用babel的抽象语法树转换,处理,生成代码 * @param typeCode ts代码 * @param rootName 根类型名 * @param keyName 属性名 * @returns 取出的代码 */ export const subInterfaceByKey = (typeCode: string, rootName: string, keyName: string): string => { // 从ts类型声明代码中取出指定key的interface代码,现将代码转换成ast const ast = parser.parse(typeCode, { sourceType: 'module', plugins: ['typescript'] }); let titleType = 'any'; // 从ast中找到指定key的接口,生成代码返回。 traverse(ast, { TSInterfaceDeclaration(path) { if (path.node.id.name === rootName) { const properties = path.node.body.body; const titleProperty = properties.find(prop => prop.key.name === keyName); // console.log(titleProperty.typeAnnotation); if (titleProperty) { titleType = generate(titleProperty.typeAnnotation).code; } } }, }); if (titleType !== 'any') { // 去掉类型前面的冒号和空格 return titleType.replace(/:\s/, ''); } return titleType; };上面的代码中,通过babel parser将要处理的代码转换成ast,然后通过 traverse 的 TSInterfaceDeclaration 勾子来处理ts interface 类型的代码,最后将处理后的代码生成好赋值给变量返回,处理后的声明文件内容就成了下面这个样子了export type TWechatRecV1NatureApplyTaxRebateInfoRess = { id: number; taxRebateNumber: string; natureRecordId: string; // 。。。其他属性 };至于为什么要用type而不是interface,原因是,Data 数据有可能不是一个对象,而是基础数据类型或数组,比如export type TWechatRecV1JodCancelCmbcBillRess = boolean;经过这次经验,我想以后再遇到这种代码处理的需求,我不会第一时间想到通过正则来处理,而是通过AST来处理了,即安全又逻辑清晰,
2024年09月14日
43 阅读
0 评论
1 点赞
2024-08-21
在线工具箱网站合集
百川工具https://rivers.chaitin.cn/tools/homeCoolTool工具箱https://www.cooltool.app/腾讯实用工具箱https://tool.browser.qq.com/category/data菜鸟工具箱https://www.jyshare.com/即时工具箱https://www.67tool.com/天天工具箱-内含开发常用工具如json处理,json数据生成TS类型声明等https://tooltt.com/#tooltt-life
2024年08月21日
44 阅读
0 评论
0 点赞
2024-07-04
那些平时很少用的npm配置
一、在package.json 中可以指定包为本地归档文件,让项目可离线安装依赖和编译 "devDependencies": { "@commitlint/cli": "^9.1.2", "@commitlint/config-conventional": "^9.1.2", "@commitlint/prompt-cli": "^9.1.2", "@tencent/babel-plugin-tea-component": "file:./lib/babel-plugin-tea-component-1.0.2.tgz", "@tencent/eslint-config-prettier": "file:./lib/eslint-config-prettier-2.0.0.tgz", "@tencent/eslint-config-prettier-typescript-react": "file:./lib/eslint-config-prettier-typescript-react-2.1.0.tgz", "@tencent/eslint-config-react": "file:./lib/eslint-config-react-2.1.0.tgz", |如上面的代码,在./lib 目录下存放npm包的tgz文件,然后通过 file: 前缀指定包的路径即可注意:如果包有其他依赖包,且也有离线需求,也需要放到依赖项里然后指定离线文件的位置二、可以为私有npm仓库单独指定registry 镜像源# ~/.npmrc @tencent:registry=https://mirrors.cloud.tencent.com/npm/如上面的配置,将 @tencent 下的npm包指向到腾讯镜像源,这样,即使没有修改全局的镜像源地址(即registry=https://xxx.xxxxxx.com/npm/)我们在安装依赖时,当安装到dependencies依赖项是 @tencent/xxx 开头的包时,也会从腾讯镜像源拉取依赖包npmrc的配置存在优先级,当我们在多个配置文件中定义相同的键时,npm将按照以下顺序查找和应用配置:1、项目根目录下的.npmrc文件2、用户主目录下的.npmrc文件(即上面的 ~/.npmrc)3、npm内置的默认配置三、在npmrc 中可以配置不同的镜像源的访问信息(如果镜像源设置了鉴权访问)@fm:registry=https://xxx.xxxxxxx.net/npm/ always-auth=true //xxx.xxxxxxx.net/npm/:username=在npm镜像源管理页面生成的用户名 //xxx.xxxxxxx.net/npm/:_password="在npm镜像源管理页面生成的密码" //xxx.xxxxxxx.net/npm/:email=zhangsan123@qq.com在npmrc中配置后,就不需要再拉取依赖的时候进行身份验证了
2024年07月04日
82 阅读
0 评论
0 点赞
1
2
...
11