程序员有时候 “提笔忘字”, 以下是此次过程中遇到的问题流水账, 按顺序记录的, 不再细分什么前端后端了.
有些问题可能过于简单, 勿喷!
get 类请求: 直接使用 ctx.query 来获取前端传递来的参数
post 类请求: 需要使用到 koa-bodyparser 中间件, 然后通过 ctx.request.body 来获取前端传递的参数
koa-bodyparser 在使用 koa2 指令创建项目时已经内置.
直接贴一个路由文件的使用吧!
const router = require('koa-router')()
const { database } = require('../config/default')
const queryUtil = require('../service/query');
router.prefix(`${database.BASIC_URL}/insurance`)
router.get('/type', async function (ctx, next) {
ctx.body = await queryUtil.getInsuranceType();
});
router.post('/add', async (ctx, next) => {
ctx.body = await queryUtil.addInsurance(ctx.request.body);
});
router.get('/list', async (ctx, next) => {
ctx.body = await queryUtil.insuranceList(ctx.query);
});
router.put('/delete/validate', async function (ctx, next) {
ctx.body = await queryUtil.deleteInsuranceValidate(ctx.request.body);
})
router.delete('/delete', async (ctx, next) => {
ctx.body = await queryUtil.deleteInsurance(ctx.query);
});
router.get('/detail', async (ctx, next) => {
ctx.body = await queryUtil.insuranceDetail(ctx.query);
});
router.put('/edit', async (ctx, next) => {
ctx.body = await queryUtil.editInsurance(ctx.request.body);
});
module.exports = router
废话不多说, 直接上代码:
src/utils/debouncedRef.js
import { customRef } from 'vue'
export function useDebouncedRef(value, delay = 200) {
let timeout
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger()
}, delay)
}
}
})
}
哪个文件需要用到防抖函数, import 进去即可. 关于 customRef 的作用可参考官网介绍
defineModel(): 这个宏可以用来声明一个双向绑定 prop,通过父组件的 v-model 来使用。
个人理解: Vue 2 中 ---- “在组件上使用v-model” 的简化版.
我们知道, v-model 可以实现数据的双向绑定, 在 input 标签上使用时可以比较方便的获取 input 的 value 值. 这是因为 v-model 这个指令实际上就是一个 语法糖, 内部默认复合了一个 value 属性 和 一个 @input 方法.
因此, 我们想要在自定义组件上使用 v-model 时, 就需要在这个自定义组件中声明对应的 属性 和 方法 来配合.
那 Vue 3, 确切的说是 3.4+ 版本之后, 这个 defineModel() 的出现, 就会让我们在自定义组件中的配合不再那么麻烦.
以下是一个使用示例, 大家一看便知 ( 仅展示核心代码 ):
子组件 LabelInput.vue 核心代码
<script setup>
const model = defineModel()
</script>
<template>
...
<div class="item_con">
<input :id="id" :type="type" v-model="model" :placeholder="placeholder">
</div>
</template>
父组件中使用
<script setup>
import { ref } from 'vue'
import LabelInput from '@/components/LabelInput.vue'
const username = ref('')
const password = ref('')
</script>
<template>
<div class="username">
<LabelInput
v-model="username"
></LabelInput>
</div>
<div class="password">
<LabelInput
v-model="password"
></LabelInput>
</div>
</template>
子组件 Example.vue 核心代码
<script setup>
const insuranceName = defineModel('insuranceName', { required: true })
const insuranceType = defineModel('insuranceType')
</script>
<template>
<div class="item_con">
<el-input v-model="insuranceName" />
</div>
<div class="item_con">
<el-select v-model="insuranceType">
...
</el-select>
</div>
</template>
父组件中使用
<script setup>
import { ref } from 'vue'
import Example from './components/Example.vue'
const insuranceName = ref('')
const insuranceType = ref('')
</script>
<template>
<Example
v-model:insuranceName="insuranceName"
v-model:insuranceType="insuranceType"
></Example>
</template>
网格布局是一个 学起来稍复杂, 但是 用起来舒服 的一种布局方案.
学起来复杂: 属性太多了, 眼花缭乱
用起来舒服: 两三个属性就能实现以往实现起来很麻烦的布局效果.
常用的属性:
grid-template-columns
grid-template-rows
row-gap
没有了解过网格布局的小伙伴, 抽空学一学吧, 很好用!
使用 mysql 模块来辅助实现 MySQL 数据库连接:
安装指令:
npm i mysql
之后就是 koa 中的配置, 以下是我自己的简单封装:
const database = {
HOST: 'xxx.xxx.xxx.xxx', //数据库地址
USER: 'root',
PASSWORD: 'xxxxxx',
DATABASE: 'xxx', //数据库名称
...
}
module.exports = { database}
const mysql = require('mysql')
const { database } = require('../config/default')
const pool = mysql.createPool({
host: database.HOST,
user: database.USER,
password: database.PASSWORD,
database: database.DATABASE
})
exports.query = function (sql, values) {
return new Promise((resolve, reject) => {
pool.getConnection(function (err, connection) {
if (err) {
reject(err)
console.log(err, "数据库连接失败");
resolve({
code: 500,
})
} else {
console.log("数据库连接成功");
connection.query(sql, values, (err, results) => {
if (err) {
reject(err)
resolve({
code: 400
})
} else {
resolve({
code: 200,
results,
})
connection.release()
//resolve(rows)
}
//connection.release() // 释放连接池
})
}
})
})
}
某js 文件中
const db = require('../db/mysql')
function xxx() {
const queryData = await db.query(`select * from ...`);
...
}
...
Element UI 中国省市区级联数据: element-china-area-data
npm i element-china-area-data
某.vue 文件中:
<script setup>
import { reactive } from 'vue'
import { pcaTextArr } from 'element-china-area-data'
const userForm = reactive({
address_city: [],
})
</script>
<template>
<div class="city">
<el-cascader
class="city_con"
:options="pcaTextArr"
clearable
placeholder="选择城市"
v-model="userForm.address_city"
>
</el-cascader>
</div>
</template>
对于 loading 的使用, 可以使用 v-loading 这个 Element UI 提供的自定义指令, 属性值为 Boolean 类型就行.
我这里是以 服务 的形式使用的 loading 加载样式, 做了全局封装.
优点: 全局共用一个 loading
缺点: 全局共用一个 loading
代码:
src/utils/loading.js
import { ElLoading } from "element-plus"
// style
const loadingStyle = {
lock: true,
text: 'Loading',
background: 'rgba(0, 0, 0, 0.7)',
}
let load = null
const loading = {
loading: () => {
!load && (load = ElLoading.service(loadingStyle))
},
close: () => {
load && load.close()
}
}
export default loading
使用:
import loading from '@/utils/loading'
// 显示 loading
loading.loading()
// 关闭 loading
loading.close()
在合适的时机调用 loading 的两个方法即可.
比较简陋, 不喜勿喷!
我是在自己二次封装的 axios.js 文件中使用的, 只要发送网络请求, 就会有 loading 的显示.
上述内容中已经讲了将 mysql 重新封装 ( 创建 db/mysql.js ), 然后再 要使用时 import 引入后 调用 query() 方法即可.
这里简单聊下 query() 方法的使用:
// 1, 完整 sql 语句
const queryData = await db.query(`select * from xxxx`);
// 2, 使用 ?
const queryData = await db.query('SELECT * FROM `books` WHERE `author` = ?', ['David']);
// 3, 对象
const queryData = await db.query({
sql: 'SELECT * FROM `books` WHERE `author` = ?',
timeout: 40000, // 40s
values: ['David']
});
// 4, 对象, value
const queryData = await db.query({
sql: 'SELECT * FROM `books` WHERE `author` = ?',
timeout: 40000, // 40s
},
['David']
);
// 5, sql, value
const queryData = await db.query('SELECT * FROM `books` WHERE `author` = ?', 'David');
使用 node-schedule 来实现定时任务的执行.
npm i node-schedule
1, 根目录/schedule/index.js
const schedule = require('node-schedule');
const queryUtil = require('../service/query');
const REFRESH_REMIND = 'refresh_remind';
function refreshRemind() {
// 重复
const rule2 = new schedule.RecurrenceRule();
// [0,1,2],表示周天、周一、周二都会执行
rule2.dayOfWeek = [0, 1, 2, 3, 4, 5, 6];
rule2.hour = 0;
rule2.minute = 30;
rule2.second = 0;
schedule.scheduleJob(REFRESH_REMIND, rule2, async function() {
// 每天 00:30:00 刷新 is_remind 数据
const refreshResults = await queryUtil.refreshReminded();
console.log('更新 is_remind 结果', refreshResults);
});
}
module.exports = { refreshRemind }
2, app.js
// schedule 定时任务 --- 在路由加载之前调用
const { refreshRemind } = require('./schedule/index')
refreshRemind();
scheduleJob() 方法的第一个参数还有其他传参形式:
const schedule = require('node-schedule');
// 每逢分钟为 42 时触发, 例如: 19:42, 20:42, ...
const job = schedule.scheduleJob('42 * * * *', function(){
console.log('The answer to life, the universe, and everything!');
});
┬ ┬ ┬ ┬ ┬ ┬
│ │ │ │ │ │
│ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun)
│ │ │ │ └───── month (1 - 12)
│ │ │ └────────── day of month (1 - 31)
│ │ └─────────────── hour (0 - 23)
│ └──────────────────── minute (0 - 59)
└───────────────────────── second (0 - 59, OPTIONAL)
更多配置请参考[官方文档[(https://www.npmjs.com/package/node-schedule)
最初找的文档都是要在 vue.config.js 文件中配置 devServer, 但是想着都 Vue 3 了, 应该有变化吧, 就去 vite 的官网去搜索方案, 还真是找到了, 为此还专门写了篇博客, 这里不再赘述, 传送门
项目写完之后, 该上线了, 前端项目 npm run build 之后的 dist 放置在哪里呢? 直接就开始 百度吧, 走了不少弯路.
印象最深的一个说放置于 koa 项目的 public 文件中, 然后修改 app.js 中静态资源的路径后面拼接上 /dist( 默认值: __dirname + ‘/public/’).
诶, 直接运行 koa, 浏览器地址栏 localhost:3000 还真把前端项目给跑起来了, 点了几个页面后, 刷新, 坏了 !!! Vue 的路由 与 Koa 的路由冲突了 !!! 又找方案!!!
找着找着, 发现不对, 我不都用 Nginx 了吗? 还乱配置啥? 于是, 静下来屡思路, 我应该让 Nginx 来管理呀!
于是, 最终 前端的 dist 放置于 Nginx 静态资源文件夹 html 下的一个文件夹中, 具体可参考上一篇博客, 传送门
里面还记录了 Nginx 的 config 文件该怎么配置.
Koa 的配置也在博客里边, 我这里就不赘述了.
直接上代码:
function siblings(elm) {
var a = [] //保存所有兄弟节点
var p = elm && elm.parentNode.children //获取父级的所有子节点
for (var i = 0; i < p.length; i++) { //循环
if (p[i].nodeType == 1 && p[i] != elm) { //如果该节点是元素节点与不是这个节点本身
a.push(p[i]) // 添加到兄弟节点里
}
}
return a
}
export default function(app) {
app.directive("height", {
mounted: function(el) {
// 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
const parentNodesH = el.parentNode.clientHeight
const nodes = document.getElementById(el.id)
const sibNodes = siblings(nodes)
let exitH = 0
sibNodes.forEach(el => {
exitH += el.clientHeight
})
el.style.height = parentNodesH - exitH + 'px'
}
})
}
import registerHeight from './height'
export default function registerDirectives(app) {
registerHeight(app)
}
...
// 自定义指令
import registerDirectives from './directives'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(ElementPlus)
registerDirectives(app)
app.mount('#app')
必须要有 id 属性
<template>
...
<div id="xxx" v-height>
...
</div>
...
</template>
本章完!