全部文章

<pre><code><!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>跨标签页共享 sessionStorage</title> </head> <body> <div><input type="text" id="input" /><button id="btn">设置</button></div> <div id="content"></div> <a href="./storage.html" target="_blank">在新窗口打开</a> <script type="text/javascript"> const input = document.getElementById('input'); const btn = document.getElementById('btn'); const content = document.getElementById('content'); btn.addEventListener('click', () => { sessionStorage.setItem('ab', input.value); content.innerHTML = input.value; }); // 为了简单明了删除了对 IE 的支持 (function () { if (!sessionStorage.length) { // 这个调用能触发目标事件,从而达到共享数据的目的 localStorage.setItem('getSessionStorage', Date.now()); } // 该事件是核心 window.addEventListener('storage', function (event) { if (event.key == 'getSessionStorage') { // 已存在的标签页会收到这个事件 localStorage.setItem('sessionStorage', JSON.stringify(sessionStorage)); localStorage.removeItem('sessionStorage'); } else if (event.key == 'sessionStorage' && !sessionStorage.length) { // 新开启的标签页会收到这个事件 content.innerHTML = event.newValue; var data = JSON.parse(event.newValue); for (key in data) { sessionStorage.setItem(key, data[key]); } } }); })(); </script> </body> </html></code></pre>
详情
<p>基于 TypeScript + Electron + Vue3 实现了一个简单的番茄钟。纯属用于练习。</p> <h2>工作中</h2> <p><img src="/uploads/%E7%95%AA%E8%8C%84%E9%92%9F.jpg" alt="番茄钟" /></p> <h2>休息中</h2> <p><img src="/uploads/clock-wallpaper.jpg" alt="休息中" /></p> <p>用 <code>electron-builder</code> 可以打包 exe 和 dmg 。</p>
详情
<h1>Canvas 坐标变换</h1> <p>Canvas 宽为 500, 高为 256。</p> <h2>变换前</h2> <pre><code>// 需要先引入 roughjs const rc = rough.canvas(document.getElementById('stage')); const hillOpts = { roughness: 2.8, stokeWidth: 2, fill: 'blue' }; rc.path('M76 256 L176 156 L276 256', hillOpts); rc.path('M236 256 L336 156 L436 256', hillOpts); rc.circle(256, 106, 105, { stroke: 'red', strokeWidth: 4, fill: 'rgba(255, 255, 0, 0.4)', fillStyle: 'solid' });</code></pre> <h1>变换后</h1> <pre><code>// 需要先引入 roughjs const rc = rough.canvas(document.getElementById('stage')); const ctx = rc.ctx; ctx.translate(256, 256); // 原点移动到底边中间 ctx.scale(1, -1); // Y轴向上 const hillOpts = { roughness: 2.8, stokeWidth: 2, fill: 'blue' }; rc.path('M-180 0 L-80 100 L 20 0', hillOpts); rc.path('M -20 0 L 80 100 L 180 0', hillOpts); rc.circle(0, 150, 105, { stroke: 'red', strokeWidth: 4, fill: 'rgba(255, 255, 0, 0.4)', fillStyle: 'solid' });</code></pre> <h2>效果</h2> <p><img src="https://www.pcdeng.com/uploads/%E5%B1%B1.png" alt="" /></p>
详情
<h1>Python 自动生成二维码并填充到 Excel 单元格</h1> <p>最近工作中遇到一个需求,需要为某个酒店的全部房间生成二维码,手机扫二维码就显示对应的房间信息。</p> <p>我的解决办法:</p> <h2>导出数据</h2> <p>所有的房间号从数据导出生成 <code>rooms.xlsx</code> ,第一个列是房号,第二列是二维码。</p> <h2>读取房号</h2> <pre><code>wb = load_workbook("rooms.xlsx") sheet = wb.active rowNum = sheet.max_row colNum = sheet.max_column align = Alignment(horizontal='center', vertical='center', wrap_text=True) # Calculated number of cells width or height from cm into EMUs for i in range(2, rowNum + 1): if i > 0: for j in range(1, colNum): roomName = sheet.cell(row=i, column=j).value imageName = generateQrCode(roomName)</code></pre> <h2>生成二维码</h2> <pre><code class="language-py">def generateQrCode(roomName): if roomName: img = qrcode.make(roomName) img_name = "./qrcode/" + roomName + ".png" with open(img_name, 'wb') as f: img.save(f) return img_name</code></pre> <h2>把二维码填充到单元格</h2> <pre><code>img = Image(imageName) newsize = (90, 90) img.width, img.height = newsize column = 1 coloffset = cellw(0.05) row = i - 1 rowoffset = cellh(0.5) h, w = img.height, img.width size = XDRPositiveSize2D(p2e(h), p2e(w)) marker = AnchorMarker( col=column, colOff=coloffset, row=row, rowOff=rowoffset) img.anchor = OneCellAnchor(_from=marker, ext=size) sheet.add_image(img)</code></pre> <h2>依赖 <code>openpyxl</code> 和 <code>qrcode</code></h2> <p>完整代码可以看 <a href="https://github.com/pcdeng/excel-qrcode-py">Demo</a></p>
详情
<p>今天下班后,朋友在微信发了一个问题给我:</p> <blockquote> <p>给定两个字符串(字符串仅包含字母),计算他们之间相差的“距离”,如 a 与 c 之间相差的“距离”是 2; aa 与 bc 之间相差的“距离”是 28; aa 与 abc 之间相差的“距离”是 704。请编程实现。</p> </blockquote> <p>说真的,看到这题目,我是一头雾水,后来问了一下大神,他说这是二十六进制,瞬间迷雾散开,然后开始思考,怎样解题。 公式:</p> <pre><code class="language-javascript">// 先来看看我们熟悉的十进制:6543:6 * 10^3 + 5 * 10^2 + 4 * 10^1 + 3 * 10^0,其中 10 就是十进制中的 10 // 假设映射 a -> 1, b -> 2, c -> 3...z->26 // 那么来看看二十六进制:a -> 1, c -> 3, 它们的距离 c - a = 3 -1 = 2; // aa -> 1 * 26^1 + 1 * 26^0 -> 27 // bc -> 2 * 26^1 + 3 * 26^0 -> 55 // 它们的距离是 bc - aa = 55 - 27 = 28; const a = 'aa'; const b = 'abc'; // [a...z] -> ASCII 码点 -> [97, 122] // -> 字符的码点 - 96 得到 [a...z] -> [1...27] let result = letter2Number(b) - letter2Number(a); console.log(`distance is ${result}`); function letter2Number(str) { let total = 0; const len = str.length; for (let i = len - 1; i >= 0; i--) { const letter = str[len - i - 1]; const value = letter.charCodeAt(0) - 96; total += (value)* Math.pow(26, i); console.log(`letter is ${letter}, ${value} * 26^${i}`); } console.log(`str is ${str}, num is ${total}`); return total; }</code></pre>
详情
<table> <thead> <tr> <th>Mini Hotel</th> <th>嘉豪轩</th> <th>小咔吧</th> </tr> </thead> <tbody> <tr> <td><img src="https://www.pcdeng.com/uploads/sp1.png" alt="" /></td> <td><img src="https://www.pcdeng.com/uploads/jhx.png" alt="" /></td> <td><img src="https://www.pcdeng.com/uploads/kaba.png" alt="" /></td> </tr> </tbody> </table>
详情
<h2>使用</h2> <pre><code><div> <sp-circle color="red" size="mini"></sp-circle> <sp-circle color="green" size="mini"></sp-circle> <sp-circle color="gray" size="mini"></sp-circle> <sp-circle color="empty" size="mini"></sp-circle> </div> <div> <sp-circle color="red"></sp-circle> <sp-circle color="green"></sp-circle> <sp-circle color="gray"></sp-circle> <sp-circle color="empty"></sp-circle> </div></code></pre> <h2>组件样式</h2> <pre><code>:host { display: inline-block; width: 24px; height: 24px; box-sizing: border-box; background-color: transparent; border: 3px solid transparent; border-radius: 50%; } :host[color=red] { background-color: #ff4a44; border-color: #ff8285; } :host[color=green] { background-color: #14a23a; border-color: #51bb79; } :host[color=gray] { background-color: #4a4b4e; border-color: #878789; } :host[color=empty] { border-color: #646567; } :host[size=mini] { width: 8px; height: 8px; border-width: 2px; } // 如果是类,可以这样写 :host(.active) { border-color: red; }</code></pre> <h2>效果</h2> <p><img src="//www.pcdeng.com/uploads/dot-circle.jpg" alt="圆点" /></p> <h2>参考</h2> <p><a href="https://segmentfault.com/a/1190000008878888">Angular 2 HostListener & HostBinding</a></p>
详情
<p>思路:总任务数组 -> 过滤出分配给我的任务 -> 过滤出 pending 的任务 -> 过滤出清房的任务 -> 按选定字段排序 -> 分页</p> <p>优点:变量少,只需维护一个数组,当有 <code>websocket</code> 推送任务变更到客户端,只需要在一个数组找到对应的任务然后替换,再深拷贝一次数组。</p> <p>缺点:<code>pipe</code> 的 <code>transform</code> 方法会执行多次。</p> <p>使用方</p> <pre><code><sp-tasks [showEmpty]="false" [animationDisabled]="isAnimationDisabled" [tasks]="myTasks | myTasks:userId:organizationUnitIds | filterTasks:'pending' | filterTasks:'cleanRoom' | sortTasks:selectedSortId | pagination:myTasksPageIndex" (actionEvent)="changeTaskStaging($event)" (chatEvent)="taskChat($event)" (taskEvent)="showDetail($event)" (pressEvent)="onPressTask($event)" ></sp-tasks></code></pre> <p>当滑动页面底部时,只需把 <code>myTasksPageIndex</code> 加一即可,分页的逻辑交给 <code>pipe</code> 处理</p> <p>filterTasks pipe</p> <pre><code>import { Pipe, PipeTransform } from '@angular/core'; import { TaskStage } from '@app/pages/add-task/constants'; import { Task } from '../models/task.model'; @Pipe({ name: 'filterTasks', }) export class FilterTaskPipe implements PipeTransform { transform( tasks: Task[], type: 'cleanRoom' | 'normal' | 'pending' | 'done' = 'cleanRoom', pageIndex = 1, ): any { switch (type) { case 'cleanRoom': return tasks.filter(task => task.isCleanRoomTask); case 'normal': return tasks.filter(task => !task.isCleanRoomTask); case 'pending': return tasks.filter(task => task.stage !== TaskStage.Done); case 'done': return tasks.filter(task => task.stage === TaskStage.Done); default: return tasks; } } }</code></pre> <h3>分页 pipe</h3> <pre><code>import { Pipe, PipeTransform } from '@angular/core'; import { Task } from '../models/task.model'; @Pipe({ name: 'pagination' }) export class PaginationPipe implements PipeTransform { transform(tasks: Task[], pageIndex = 1, pageSize = 10): Task[] { const start = (pageIndex - 1) * pageSize; const end = start + pageSize; return tasks.slice(0, end); } }</code></pre>
详情
<p>本文假设你已经看过并熟悉了<a href="https://zhuanlan.zhihu.com/p/68527022">参考文章</a> </p> <p>其他和参考文章的都一样,我就不赘述了。建议先看参考文章。</p> <p>我遇到的问题是,在两个及以上组件使用装饰器会出现问题,具体就是找不到属性或方法。</p> <h2>使用</h2> <pre><code>export class ChatPanelComponent extends MessageListenersManager { constructor(wsMessageService: WsMessageService) { super(wsMessageService); } @MessageListener(Receive.SingleChatMessage) onSingleChatMessageRead(message: SingleChatMessage) { console.log('[ChatPanelComponent][onSingleChatMessageRead]: ', message); this.pushMessageToChatPanel(message); } }</code></pre> <h3>出错信息如下</h3> <pre><code>core.js:15724 ERROR TypeError: this.pushMessageToChatPanel is not a function at ClientComponent.push../src/client/shared/components/chat-panel/chat-panel.component.ts.ChatPanelComponent.onTaskChatMessage (chat-panel.component.ts:132) at SafeSubscriber._next (message-listener.ts:29) at SafeSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.SafeSubscriber.__tryOrUnsub (Subscriber.js:194) at SafeSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.SafeSubscriber.next (Subscriber.js:132) at Subscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber._next (Subscriber.js:76) at Subscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next (Subscriber.js:53) at TakeUntilSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber._next (Subscriber.js:76) at TakeUntilSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next (Subscriber.js:53) at MapSubscriber.push../node_modules/rxjs/_esm5/internal/operators/map.js.MapSubscriber._next (map.js:41) at MapSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next (Subscriber.js:53)</code></pre> <p><code>pushMessageToChatPanel</code> 在组件 <code>chat-panel.component.ts</code> 上是存在 ,很显然是 <code>this</code> 的指向问题。 既然知道是 <code>this</code> 指向的问题,那么就让 <code>this</code> 指向就好了。</p> <p>思路:在装饰器中把一个数组压入 <code>__messageListeners__</code>,数组第一个元素是组件的 <code>constructor</code>,第二个是函数</p> <h3>解决方法</h3> <ol> <li> <p>改装饰器</p> <pre><code>import { MessageReceiveData } from '@client/core/models/ws-message.model'; import { takeUntil } from 'rxjs/operators'; import { MessageListenersManager } from './message-listeners-manager'; export type ReceiveArgumentsType<T extends keyof MessageReceiveData> = MessageReceiveData[T] extends undefined ? () => void : (data?: MessageReceiveData[T]) => void; export function MessageListener<T extends keyof MessageReceiveData>(type: T) { return ( target: MessageListenersManager, propertyKey: string, descriptor: TypedPropertyDescriptor<ReceiveArgumentsType<T>>, ) => { // 获取构造上的静态属性 __messageListeners__ const constructor = Object.getPrototypeOf(target).constructor; if (constructor && constructor.__messageListeners__) { // 将创建订阅的方法推入 __messageListeners__ , 以便在构造时调用 constructor.__messageListeners__.push([target.constructor, function () { // 创建指定类型的订阅 this.wsMessageService .receive(type) // 使用 takeUntil 操作符以便自动取消订阅 .pipe(takeUntil(this.__messageListenersTakeUntilDestroy$__)) .subscribe((data) => { // 收到返回后调用被装饰得方法 descriptor.value.call(this, data); }); }]); } return descriptor; }; }</code></pre> </li> <li> <p>改管事件理器</p> <pre><code>import { OnDestroy } from '@angular/core'; import { WsMessageService } from '@client/core'; import { Subject } from 'rxjs'; export class MessageListenersManager implements OnDestroy { static __messageListeners__: [any, Function][] = []; readonly __messageListenersTakeUntilDestroy$__ = new Subject<void>(); constructor(public wsMessageService: WsMessageService) { MessageListenersManager.__messageListeners__.forEach((item) => { const [ctr, fun] = item; if (this instanceof ctr) { const index = MessageListenersManager.__messageListeners__.findIndex(a => a == item); if (index > -1) { // console.log(`${index} matched:, ${key.name}`); fun.apply(this); // MessageListenersManager.__messageListeners__.splice(index, 1); } } }); } ngOnDestroy(): void { this.__messageListenersTakeUntilDestroy$__.next(); this.__messageListenersTakeUntilDestroy$__.complete(); } }</code></pre> </li> </ol> <p>以上的解决方法虽不是很优雅,读起来也有点苦涩,但好在能解决我的问题,如果有更好的解决方法,欢迎<a href="https://zhuanlan.zhihu.com/p/68527022">回复</a>。</p> <h2>参考</h2> <p><a href="https://zhuanlan.zhihu.com/p/68527022">使用 Typescript 构建类型安全的 Websocket 应用</a></p>
详情
<h2>按楼层名称排序</h2> <p>昨天接到一个需求,如下: 一个乱序的楼层数组</p> <pre><code class="language-javascript">[ '7D', '7A', '15A', '19B', '9D', '8B', '16A', '21B', '11D', '15C', '10B', '12A', '15B', '14C', '16D', '10A', '7C', '16C', '16B', '17C', '8D', '19A', '17B', '11B', '21A', '14D', '21C', '14B', '20C', '11A', '20D', '20A', '12D', '18B', '14A', '9A', '20B', '8C', '10C', '18D', '7B', '8A', '19D', '11C', '19C', '18C', '9C', '10D', '18A', '12B', '15D', '17A', '17D', '12C', '9B' ]</code></pre> <p>排序后的结果要求是:</p> <pre><code class="language-javascript">[ '7A', '7B', '7C', '7D', '8A', '8B', '8C', '8D', '9A', '9B', '9C', '9D', '10A', '10B', '10C', '10D', '11A', '11B', '11C', '11D', '12A', '12B', '12C', '12D', '14A', '14B', '14C', '14D', '15A', '15B', '15C', '15D', '16A', '16B', '16C', '16D', '17A', '17B', '17C', '17D', '18A', '18B', '18C', '18D', '19A', '19B', '19C', '19D', '20A', '20B', '20C', '20D', '21A', '21B', '21C' ]</code></pre> <p>我的的实现,借助 <code>loadsh</code> 的 <code>sortBy</code> 函数,先按数字排,然后再按字母排。 完整代码如下:</p> <pre><code class="language-javascript">const _ = require('loadsh'); let zoneNames = [ '15B', '7C', '11D', '14D', '18B', '14A', '12C', '8C', '16B', '16C', '17B', '17D', '14C', '21C', '20D', '10C', '17A', '9A', '10B', '21A', '19C', '12D', '9D', '15A', '11A', '18A', '14B', '20B', '10A', '18D', '7A', '15C', '9B', '11B', '19A', '16A', '11C', '16D', '19D', '7B', '20C', '21B', '10D', '18C', '12A', '19B', '8B', '20A', '8D', '17C', '15D', '8A', '7D', '9C', '12B' ] zoneNames = _.shuffle(zoneNames); console.log('before sort:', zoneNames); const sortedZoneNames = _.sortBy(zoneNames, function (floor) { const name = floor || ''; return parseInt(name, 10); }, function (floor) { let name = floor || ''; name = name.replace(/[0-9]/g, ''); return name; }, ); console.log('after sort:', sortedZoneNames);</code></pre> <p>以上,纯属记录一下。88。</p>
详情
<p>手贱升级了 <code>Android Studio</code>,也升级 <code>Android Gradle 插件</code> 然而执行</p> <pre><code class="language-bash">flutter run</code></pre> <p>报错了: <code>Minimum supported Gradle version is 6.5. Current version is 6.4.1.</code></p> <p>搜索找到了问题所在: 两个版本不匹配。</p> <p>分析第一句: <code>Minimum supported Gradle version is 6.5.</code> 哪个版本和哪个版本对比? 找到 <code>android 项目/build.gradle 文件</code></p> <pre><code>buildscript { dependencies { classpath 'com.android.tools.build:gradle:4.1.0' } }</code></pre> <p>如上,<code>4.1.0</code> 是 <code>Android Gradle 插件</code>的版本 <a href="https://developer.android.google.cn/studio/releases/gradle-plugin.html">查对应关系</a> 发现</p> <pre><code>4.1.0+ -> 6.5+</code></pre> <p>翻译过来就是 <code>Android Gradle 插件</code>从 <code>4.1.0</code> 开始,需要 <code>Gradle</code> 最低的版本为 <code>6.5</code>。</p> <p>理解了第一句的错误提示之后,那么,如果查看 <code>Gradle</code> 的版本呢? 打开 <code>android 项目/gradle/wrapper/gradle-wrapper.properties</code> 看 <code>distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-all.zip</code> 匹配上 <code>Current version is 6.4.1</code></p> <p>理解 Android Gradle 版本和 Gradle 版本的对应关系之后,问题就很容易解决了。 两个方向</p> <p>一、改 Android Gradle 的版本,例如我的改成如下</p> <pre><code>// android 项目/build.gradle buildscript { dependencies { classpath 'com.android.tools.build:gradle:4.0.0' } }</code></pre> <p>二、改 Gradle 的版本。 如</p> <pre><code>// android 项目/gradle/wrapper/gradle-wrapper.properties distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip</code></pre> <p>下载 gradle 会很慢,请做好心理准备。</p> <p>2021-04-03 补充:</p> <p>问:如何解决国内下载 gradle zip 包越来越慢的问题?</p> <p>答:困扰我多日的麻烦,经探索,我得出的办法是用迅雷下载:<code>https://services.gradle.org/distributions/gradle-6.4.1-all.zip</code></p> <p>2024-09-08 补充:</p> <p>答上一个问题:使用国内镜像,如:<code>mirrors.cloud.tencent.com/gradle/</code>。比如例子中的完整地址是:<code>https://mirrors.cloud.tencent.com/gradle/gradle-6.4.1-all.zip</code></p>
详情
<h1>悦阅 app</h1> <p>框图 <img src="//www.pcdeng.com/uploads/yueyue-app.png" alt="框图" /> 完成的功能点有:</p> <ul> <li>下拉刷新。</li> <li>滑到到底部加载更多</li> <li>加载状态</li> </ul> <p>悦阅 app Android app 已经开发完毕,如果你想体验,可以通过这里下载 <a href="http://img.qulang8.com/YueYue.apk">悦阅 apk</a></p> <h2>学到的</h2> <h3>Dart 基础</h3> <p><img src="//www.pcdeng.com/uploads/dart-basic.png" alt="Dart 基础" /></p> <h3>存储</h3> <p>用的包是 <code>shared_preferences</code> 基础用法</p> <ul> <li>安装</li> </ul> <p>在 <code>pubspec.yaml</code> 文件的 <code>dependencies</code> 段添加 <code>shared_preferences: "^0.4.2"</code> 依赖。</p> <ul> <li> <p>封装</p> <pre><code class="language-dart">class LocalStorage { static save(String key, value) async { SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.setString(key, value); } static get(String key) async { SharedPreferences prefs = await SharedPreferences.getInstance(); return prefs.get(key); } static remove(String key) async { SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.remove(key); } }</code></pre> </li> </ul> <h3>Redux</h3> <p>这有一部分花了比较多时间,基本都是调试出来的,对我来说,最难的是如何获得 store 的实例。我还没有理清楚它内部的实现,只知道如何使用,而且使用的方法可能不太优雅,看起来是这样的。 第一步:添加依赖 在 <code>pubspec.yaml</code> 文件的 <code>dependencies</code> 段,添加</p> <pre><code>redux: ^3.0.0 flutter_redux: ^0.5.3</code></pre> <p>第二步:初始化</p> <pre><code>void main() async { final store = new Store<UserState>( appReducer, initialState: UserState.initState( isLogin: false, email: '', accessToken: '', ), ); runApp(MyApp( store: store, )); }</code></pre> <p>第三步:在 Widget 中使用</p> <pre><code>@override Widget build(BuildContext context) { return StoreConnector<UserState, UserState>( converter: (store) => store.state, builder: (context, state) { if (state != null && state.isLogin) { return _userInfo(state); } return _loginButton(context); }, ); }</code></pre> <h3>页面的常用布局。</h3> <ul> <li>Container</li> <li>Row,</li> <li>Expanded</li> <li>Column</li> <li>Card</li> <li>Image</li> <li>Padding</li> <li>Align</li> </ul> <h3>事件</h3> <ul> <li>GestureDetector</li> </ul>
详情