全部文章

<p>最近在做集成腾讯广告的项目,碰到一个交互挺复杂的功能。用了一个上午的时间做了一个原型。</p> <p>这种数据结构应该怎样实现呢?</p> <p>我实现的思路是,一个表单就是数组里的一项,每个表单根据 templateId 来分组。</p> <p>allTabs -> tabs -> currentTab(formData)</p> <p><code>allTabs</code> 是一个数组,里面存的是一个对象,<code>templateId</code> 是对应上面的分组的唯一 <code>id</code> (如“常规图片1图”的 <code>templateId</code> 是 <code>1</code>)。</p> <p><code>formData</code> 就是当前选中的 <code>tab</code> 的表单数据对象。</p> <pre><code>{ "templateId": 2, "formData": { "name": "", "region": "", "date1": "", "date2": "", "delivery": false, "type": [], "resource": "", "desc": "" } }</code></pre> <p><img src="//www.pcdeng.com/uploads/vue-ad.png" alt="" /></p> <p><a href="//www.pcdeng.com/vue-ad/">演示地址</a></p>
详情
<h1>stm32-demo</h1> <h2>前置准备</h2> <table> <thead> <tr> <th>项</th> <th>备注</th> </tr> </thead> <tbody> <tr> <td>操作系统</td> <td>macOS 10.14.6</td> </tr> <tr> <td>编辑器</td> <td>Visual Studio Code</td> </tr> <tr> <td>目标芯片</td> <td>STM32F103C8T6</td> </tr> <tr> <td>仿真器</td> <td>CMSIS-DAP</td> </tr> </tbody> </table> <p>开始之前请确保你的电脑已经安装了:Rust 和 openocd</p> <h2>拉取代码</h2> <p><code>git clone git@github.com:pcdeng/stm32-demo.git</code></p> <h2>Visual Studio Code 安装 rust-analyzer</h2> <p><a href="https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer">rust-analyzer Visual Studio Code 插件</a></p> <h2>编译</h2> <p><code>cargo build</code> 编译出的是 <code>debug</code> 模式的,<code>target/thumbv7m-none-eabi/debug/stm32demo</code> 就是可以下载到开发板的文件,文件大小比较大。</p> <p><code>cargo build --release</code> 编译出的是 <code>release</code> 模式的。<code>target/thumbv7m-none-eabi/release/stm32demo</code> 是可以下载到开发板的文件。文件大小比 <code>debug</code> 模式下的小很多。</p> <h2>接上调试器</h2> <p><img src="/uploads/link.jpeg" alt="" /></p> <h2>开启 openocd 服务</h2> <p><code>openocd -f ./openocd.cfg</code> 成功启动 openocd 服务的话,会出现类似如下信息</p> <pre><code>Open On-Chip Debugger 0.11.0 Licensed under GNU GPL v2 For bug reports, read http://openocd.org/doc/doxygen/bugs.html Info : Listening on port 6666 for tcl connections Info : Listening on port 4444 for telnet connections Info : CMSIS-DAP: SWD Supported Info : CMSIS-DAP: JTAG Supported Info : CMSIS-DAP: FW Version = 2.0.0 Info : CMSIS-DAP: Interface Initialised (SWD) Info : SWCLK/TCK = 1 SWDIO/TMS = 1 TDI = 0 TDO = 1 nTRST = 0 nRESET = 1 Info : CMSIS-DAP: Interface ready Info : clock speed 1000 kHz Info : SWD DPIDR 0x1ba01477 Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints Info : starting gdb server for stm32f1x.cpu on 3333 Info : Listening on port 3333 for gdb connections</code></pre> <h2>登录 telnet 服务</h2> <p>在另一个终端窗口执行 <code>telnet 127.0.0.1 4444</code> 端口 <code>4444</code> 是从上一步的日志信息获取的。<code>Info : Listening on port 4444 for telnet connections</code></p> <h3>停机</h3> <p>在 telnet 那个终端输入 <code>halt</code></p> <h3>写入</h3> <p><code>flash write_image erase ./target/thumbv7m-none-eabi/debug/stm32demo</code></p> <h3>重启</h3> <p><code>reset</code></p> <h1>参考资料</h1> <p><a href="https://www.rust-lang.org/zh-CN/tools/install">安装 Rust</a> <a href="https://doc.rust-lang.org/stable/embedded-book/intro/install/macos.html">macOS Rust 嵌入式需要安装的软件</a> 我没有安 QEMU 因为我有开发板</p> <p><a href="https://detail.tmall.com/item.htm?_u=3ko4pss5f42&id=631928588828&spm=a1z09.2.0.0.d5592e8dy0qIbk">我用的开发板</a></p> <p><a href="https://jzow.github.io/discovery/microbit/index.html">Discovery</a></p> <p><a href="https://github.com/stm32-rs/stm32f1xx-hal">stm32f1xx-hal</a></p> <p><a href="https://space.bilibili.com/500416539/channel/collectiondetail?sid=177577&ctype=0">Rust嵌入式开发入门</a> 强烈推荐,作者解说得非常详细。</p>
详情
<pre><code class="language-vue"><template> <div class="chart-wrapper"> <div ref="chartRef" class="chart"></div> </div> </template> <script lang="ts" setup> import * as echarts from 'echarts'; import { onMounted, ref } from 'vue'; import { EChartsOption } from 'echarts'; import GD_GEO_JSON from './440000_full.json'; const PROVINS = [ { name: '北京市', value: 20057.34 }, { name: '天津市', value: 15477.48 }, { name: '河北省', value: 31686.1 }, { name: '山西省', value: 6992.6 }, { name: '内蒙古自治区', value: 44045.49 }, { name: '辽宁省', value: 40689.64 }, { name: '吉林省', value: 37659.78 }, { name: '黑龙江省', value: 45180.97 }, { name: '上海市', value: 55204.26 }, { name: '江苏省', value: 21900.9 }, { name: '浙江省', value: 4918.26 }, { name: '安徽省', value: 5881.84 }, { name: '福建省', value: 4178.01 }, { name: '江西省', value: 2227.92 }, { name: '山东省', value: 2180.98 }, { name: '河南省', value: 9172.94 }, { name: '湖北省', value: 3368 }, { name: '湖南省', value: 806.98 }, { name: '广东省', value: 40689.44 }, ]; const CITIES = [ { name: '广州市', value: 1 }, { name: '韶关市', value: 20 }, { name: '深圳市', value: 30 }, { name: '珠海市', value: 30 }, { name: '汕头市', value: 40 }, { name: '佛山市', value: 44 }, { name: '江门市', value: 55 }, { name: '湛江市', value: 66 }, { name: '茂名市', value: 77 }, { name: '肇庆市', value: 88 }, { name: '惠州市', value: 99 }, { name: '梅州市', value: 11 }, { name: '汕尾市', value: 22 }, { name: '河源市', value: 33 }, { name: '阳江市', value: 44 }, { name: '清远市', value: 55 }, { name: '东莞市', value: 66 }, { name: '中山市', value: 77 }, { name: '潮州市', value: 88 }, { name: '揭阳市', value: 99 }, { name: '云浮市', value: 33 }, ]; const convertData = () => { const cities = GD_GEO_JSON.features.map(item => item.properties); return CITIES.map(city => { const target = cities.find(c => c.name === city.name); return { ...city, lng: target?.centroid[0], lat: target?.centroid[1], }; }); }; const finalData = convertData(); const option: EChartsOption = { title: { text: '中国', padding: 10, }, tooltip: { trigger: 'item', }, geo: { tooltip: { show: true, }, map: 'CN', roam: false, // 是否支持缩放 label: { show: false, }, emphasis: { label: { show: false, }, }, }, dataset: { dimensions: ['name', 'value', 'lat', 'lng'], source: finalData, }, series: [ { name: '广场', type: 'map', map: 'CN', label: { show: false, }, emphasis: { label: { show: false, }, }, tooltip: { show: false, }, data: CITIES, }, { type: 'effectScatter', coordinateSystem: 'geo', symbolSize: 10, itemStyle: { color: '#2875FB', }, encode: { name: 'name', lng: 'lng', lat: 'lat', value: 'value', }, label: { formatter: '{@value}个广场', position: 'top', show: true, color: '#fff', padding: [0, 8], height: 30, verticalAlign: 'middle', borderRadius: 4, backgroundColor: 'rgba(0,0,0,0.65)', }, tooltip: { show: false, }, }, ], visualMap: { min: 1, max: 100, text: ['High', 'Low'], realtime: false, calculable: true, inRange: { color: ['lightskyblue', 'yellow', 'orangered'], }, show: false, seriesIndex: 0, }, }; const chartRef = ref(); echarts.registerMap('CN', GD_GEO_JSON as any); const pos = ref({ x: 0, y: 0 }); onMounted(() => { const myChart = echarts.init(chartRef.value); myChart.setOption(option); }); </script> <style lang="scss" scoped> .chart-wrapper { position: relative; height: 100%; width: 100%; box-sizing: border-box; } .chart { width: 100%; height: 100%; margin: 0 auto; box-sizing: border-box; } </style></code></pre> <h1>参考</h1> <p><a href="https://echarts.apache.org/examples/zh/editor.html?c=map-HK">https://echarts.apache.org/examples/zh/editor.html?c=map-HK</a></p> <p>获取广东省的 GEO data</p> <p><a href="http://datav.aliyun.com/portal/school/atlas/area_selector">http://datav.aliyun.com/portal/school/atlas/area_selector</a></p>
详情
<h1>app 的名字</h1> <p>Lite Hotel,虽叫 lite,但功能还是挺全的。</p> <h1>功能</h1> <h2>支持更新任务状态</h2> <h2>支持更换任务经办人</h2> <h2>工单管理</h2> <h2>客房管理</h2> <h2>消息管理</h2> <h3>支持消息推送</h3> <h3>支持消息一键标为已读</h3> <h3>支持批量删除消息</h3> <h2>其它</h2> <h3>支持多语言</h3> <h3>支持暗黑模式</h3> <h3>支持在线更新</h3> <h3>支持消息推送</h3> <h1>开发进度表</h1> <p><img src="https://www.pcdeng.com/uploads/mini_hotel_progress.png" alt="" /></p> <h2>实际效果图</h2> <p><img src="https://www.pcdeng.com/uploads/mini_hotel_home.png" alt="" /> <img src="https://www.pcdeng.com/uploads/rooms.png" alt="" /> <img src="https://www.pcdeng.com/uploads/notifications.png" alt="" /> <img src="https://www.pcdeng.com/uploads/my.png" alt="" /> <img src="https://www.pcdeng.com/uploads/settings.png" alt="" /> <img src="https://www.pcdeng.com/uploads/select_locale.png" alt="" /></p> <h1>app 下载</h1> <p><a href="//www.pcdeng.com/uploads/app.apk">apk</a></p> <h1>web 管理端</h1> <p><img src="//www.pcdeng.com/uploads/web.png" alt="" /></p> <p>今天百度了一下才知道,Mini Hotel 已经有人做了。也有一个网站,而且做的功能更多,它的地址是:<a href="https://minihotel.io">https://minihotel.io</a> 这个名字不能用了,迟点要统一改掉。</p> <p>如果想体验一下,可以联系我要演示账号。 <img src="https://www.pcdeng.com/uploads/wechat.png" alt="qrcode" /></p>
详情
<p>最近工作没有那么忙,就抽时间整理了之前写过的一些例子,整理如下: <a href="https://pcdeng.github.io/">https://pcdeng.github.io/</a></p>
详情
<h1>需求</h1> <ul> <li>A 邀请 B, B 邀请 C,统计 A 的会员数。</li> <li>B 下单的总数</li> <li>默认排序按用户注册时间,支持按会员数降序、会员下单数降序排序。</li> </ul> <h1>表结构</h1> <pre><code># user 表 +-------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------------+--------------+------+-----+---------+----------------+ | id | int unsigned | NO | PRI | NULL | auto_increment | | name | varchar(255) | YES | | NULL | | | dateline | int | YES | | NULL | | | inviter_uid | int | YES | | NULL | | +-------------+--------------+------+-----+---------+----------------+ # user_order 表 +----------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +----------+--------------+------+-----+---------+----------------+ | id | int unsigned | NO | PRI | NULL | auto_increment | | user_id | int unsigned | YES | | NULL | | | name | varchar(255) | YES | | NULL | | | dateline | int | YES | | NULL | | +----------+--------------+------+-----+---------+----------------+</code></pre> <h1>实现</h1> <pre><code class="language-php"><?php namespace app\index\controller; use think\Controller; use Db; class Index { protected static $mapping = ['time' => 'dateline', 'member' => 'memberCount', 'order' => 'orderCount']; public function hello() { // 处理 sort 排序参数 $sort = request()->param('sort', 'time'); $sorts = array_keys(self::$mapping); if (!in_array($sort, $sorts)) { die('参数不合法'); } $sort = self::$mapping[$sort]; $model = Db(); $users = $model->query("SELECT u.id, u.dateline, DATE_FORMAT(FROM_UNIXTIME(u.dateline), '%Y-%m-%d %H:%i:%s') time, u.NAME, ( SELECT count( o.id ) FROM user_order o WHERE o.user_id = u.id ) 'orderCount', ( SELECT count( uu.id ) FROM USER uu WHERE ( uu.inviter_uid IN ( SELECT uuu.id FROM USER uuu WHERE uuu.inviter_uid = u.id )) OR uu.inviter_uid = u.id ) 'memberCount' FROM USER u order by $sort DESC"); dump($users); } }</code></pre> <h1>测试</h1> <p>浏览器访问</p> <p><a href="http://127.0.0.1:8083/public/index.php?s=index/index/hello">http://127.0.0.1:8083/public/index.php?s=index/index/hello</a></p> <p><a href="http://127.0.0.1:8083/public/index.php?s=index/index/hello&sort=time">http://127.0.0.1:8083/public/index.php?s=index/index/hello&sort=time</a></p> <p><a href="http://127.0.0.1:8083/public/index.php?s=index/index/hello&sort=order">http://127.0.0.1:8083/public/index.php?s=index/index/hello&sort=order</a></p> <p><a href="http://127.0.0.1:8083/public/index.php?s=index/index/hello&sort=member">http://127.0.0.1:8083/public/index.php?s=index/index/hello&sort=member</a></p>
详情
<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>
详情