feat 配置SQlite

This commit is contained in:
Ironsp
2026-09-07 16:35:13 +08:00
parent 4150739d27
commit 7e81a8ca59
5 changed files with 487 additions and 2 deletions

14
src/auto-imports.d.ts vendored
View File

@@ -9,6 +9,7 @@ declare global {
const EffectScope: typeof import('vue')['EffectScope']
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
const closeDatabase: typeof import('./utils/sqlite')['closeDatabase']
const computed: typeof import('vue')['computed']
const computedAsync: typeof import('@vueuse/core')['computedAsync']
const computedEager: typeof import('@vueuse/core')['computedEager']
@@ -33,11 +34,13 @@ declare global {
const defineComponent: typeof import('vue')['defineComponent']
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
const effectScope: typeof import('vue')['effectScope']
const executeSql: typeof import('./utils/sqlite')['executeSql']
const extendRef: typeof import('@vueuse/core')['extendRef']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
const initTables: typeof import('./utils/sqlite')['initTables']
const inject: typeof import('vue')['inject']
const injectLocal: typeof import('@vueuse/core')['injectLocal']
const isDefined: typeof import('@vueuse/core')['isDefined']
@@ -91,6 +94,7 @@ declare global {
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
const openDatabase: typeof import('./utils/sqlite')['openDatabase']
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
const provide: typeof import('vue')['provide']
const provideLocal: typeof import('@vueuse/core')['provideLocal']
@@ -110,9 +114,11 @@ declare global {
const resolveComponent: typeof import('vue')['resolveComponent']
const resolveRef: typeof import('@vueuse/core')['resolveRef']
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
const selectSql: typeof import('./utils/sqlite')['selectSql']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const sqlite: typeof import('./utils/sqlite')['default']
const storage: typeof import('./utils/storage')['default']
const syncRef: typeof import('@vueuse/core')['syncRef']
const syncRefs: typeof import('@vueuse/core')['syncRefs']
@@ -124,6 +130,7 @@ declare global {
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const transaction: typeof import('./utils/sqlite')['transaction']
const triggerRef: typeof import('vue')['triggerRef']
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
@@ -331,6 +338,7 @@ declare module 'vue' {
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
readonly closeDatabase: UnwrapRef<typeof import('./utils/sqlite')['closeDatabase']>
readonly computed: UnwrapRef<typeof import('vue')['computed']>
readonly computedAsync: UnwrapRef<typeof import('@vueuse/core')['computedAsync']>
readonly computedEager: UnwrapRef<typeof import('@vueuse/core')['computedEager']>
@@ -355,11 +363,13 @@ declare module 'vue' {
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
readonly eagerComputed: UnwrapRef<typeof import('@vueuse/core')['eagerComputed']>
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
readonly executeSql: UnwrapRef<typeof import('./utils/sqlite')['executeSql']>
readonly extendRef: UnwrapRef<typeof import('@vueuse/core')['extendRef']>
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
readonly h: UnwrapRef<typeof import('vue')['h']>
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
readonly initTables: UnwrapRef<typeof import('./utils/sqlite')['initTables']>
readonly inject: UnwrapRef<typeof import('vue')['inject']>
readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
@@ -413,6 +423,7 @@ declare module 'vue' {
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
readonly onWatcherCleanup: UnwrapRef<typeof import('vue')['onWatcherCleanup']>
readonly openDatabase: UnwrapRef<typeof import('./utils/sqlite')['openDatabase']>
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
readonly provide: UnwrapRef<typeof import('vue')['provide']>
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
@@ -432,9 +443,11 @@ declare module 'vue' {
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
readonly resolveUnref: UnwrapRef<typeof import('@vueuse/core')['resolveUnref']>
readonly selectSql: UnwrapRef<typeof import('./utils/sqlite')['selectSql']>
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
readonly sqlite: UnwrapRef<typeof import('./utils/sqlite')['default']>
readonly storage: UnwrapRef<typeof import('./utils/storage')['default']>
readonly syncRef: UnwrapRef<typeof import('@vueuse/core')['syncRef']>
readonly syncRefs: UnwrapRef<typeof import('@vueuse/core')['syncRefs']>
@@ -446,6 +459,7 @@ declare module 'vue' {
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
readonly transaction: UnwrapRef<typeof import('./utils/sqlite')['transaction']>
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
readonly tryOnBeforeMount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeMount']>
readonly tryOnBeforeUnmount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeUnmount']>

1
src/components.d.ts vendored
View File

@@ -35,7 +35,6 @@ declare module 'vue' {
SettingsSensorConfig: typeof import('./components/settings/sensorConfig.vue')['default']
SettingsWifiNetwork: typeof import('./components/settings/wifiNetwork.vue')['default']
SettingsWifiSettings: typeof import('./components/settings/wifiSettings.vue')['default']
ToggleFullscreen: typeof import('./components/toggleFullscreen.vue')['default']
WarningFilter: typeof import('./components/warning/filter.vue')['default']
WarningItem: typeof import('./components/warning/item.vue')['default']
WorkDashboard: typeof import('./components/work/dashboard.vue')['default']

1
src/uni-pages.d.ts vendored
View File

@@ -6,6 +6,7 @@
type _LocationUrl =
"/pages/Ahome/index" |
"/pages/bluetooth/index" |
"/pages/demo/sqlite" |
"/pages/heatmap/index" |
"/pages/message/index" |
"/pages/project/index" |

469
src/utils/sqlite.ts Normal file
View File

@@ -0,0 +1,469 @@
/**
* SQLite 数据库工具类
* 用于 uni-app Android/iOS 本地数据库操作
*
* ======================== 使用说明 ========================
*
* ## 1. 导入
*
* ```typescript
* import DB, { initTables } from '@/utils/sqlite'
* ```
*
* ## 2. 初始化数据库表
*
* ```typescript
* // 首次使用或表结构变更时调用,仅需执行一次
* await initTables()
* ```
*
* ## 3. 插入数据(增)
*
* ```typescript
* // 方式一:使用 insert 方法(推荐,自动拼接 SQL)
* await DB.insert('device', {
* device_name: '徐工XS65H #01',
* device_type: '压路机',
* status: 'online',
* })
*
* // 方式二:使用原生 SQL
* await DB.executeSql(
* `INSERT INTO device (device_name, device_type, status)
* VALUES ('徐工XS65H #01', '压路机', 'online')`
* )
* ```
*
* ## 4. 删除数据(删)
*
* ```typescript
* // 按条件删除
* await DB.delete('device', "id = 1")
* // 删除所有
* await DB.delete('device')
* ```
*
* ## 5. 更新数据(改)
*
* ```typescript
* await DB.update('device',
* { status: 'offline', updated_at: new Date().toISOString() },
* "id = 1"
* )
* ```
*
* ## 6. 查询数据(查)
*
* ### 6.1 查询全部
* ```typescript
* const devices = await DB.select('device')
* ```
*
* ### 6.2 详细查询 —— 按 ID 查询单条
* ```typescript
* const device = await DB.selectById('device', 1)
* // 返回: { id: 1, device_name: '...', ... } 或 null
* ```
*
* ### 6.3 详细查询 —— 按字段精确匹配
* ```typescript
* const devices = await DB.selectByField('device', 'status', 'online')
* ```
*
* ### 6.4 模糊查询 —— LIKE 模糊匹配
* ```typescript
* // 按设备名称模糊查询
* const devices = await DB.selectLike('device', 'device_name', '徐工')
*
* // 指定返回字段的模糊查询
* const devices = await DB.selectLike('device', 'device_name', '徐工', 'id, device_name, status')
* ```
*
* ### 6.5 条件查询 —— 带排序和分页
* ```typescript
* const devices = await DB.select('device', '*', "status = 'online'", 'id DESC', 10)
* ```
*
* ### 6.6 计数查询
* ```typescript
* const count = await DB.count('device', "status = 'online'")
* ```
*
* ### 6.7 原生 SQL 查询(复杂查询时使用)
* ```typescript
* const result = await DB.selectSql('SELECT * FROM device WHERE status = "online"')
* ```
*
* ## 7. 事务操作
*
* ```typescript
* await DB.transaction([
* "INSERT INTO device (device_name, device_type) VALUES ('设备A', '压路机')",
* "INSERT INTO work_record (device_id, speed) VALUES (1, 5.5)",
* ])
* ```
*
* ## 8. 其他
*
* ```typescript
* // 手动打开数据库
* await DB.open()
* // 手动关闭数据库
* await DB.close()
* ```
*/
const DB_NAME = 'road_roller'
const DB_PATH = '_doc/road_roller.db'
let isOpen = false
/**
* 转义 SQL 字符串值,防止单引号注入
*/
function escapeValue(value: any): string {
if (value === null || value === undefined)
return 'NULL'
if (typeof value === 'number')
return String(value)
if (typeof value === 'boolean')
return value ? '1' : '0'
return `'${String(value).replace(/'/g, '\'\'')}'`
}
/**
* 数据库操作类
*/
class DB {
/**
* 打开数据库
*/
static open(): Promise<void> {
return new Promise((resolve, reject) => {
if (isOpen) {
resolve()
return
}
plus.sqlite.openDatabase({
name: DB_NAME,
path: DB_PATH,
success() {
console.log('数据库打开成功')
isOpen = true
resolve()
},
fail(err: any) {
console.error('数据库打开失败:', err)
reject(err)
},
})
})
}
/**
* 关闭数据库
*/
static close(): Promise<void> {
return new Promise((resolve, reject) => {
if (!isOpen) {
resolve()
return
}
plus.sqlite.closeDatabase({
name: DB_NAME,
success() {
console.log('数据库关闭成功')
isOpen = false
resolve()
},
fail(err: any) {
console.error('数据库关闭失败:', err)
reject(err)
},
})
})
}
/**
* 执行 SQL 语句(增、删、改)
*/
static executeSql(sql: string | string[]): Promise<any> {
return DB.open().then(() => {
return new Promise((resolve, reject) => {
plus.sqlite.executeSql({
name: DB_NAME,
sql,
success(res: any) {
resolve(res)
},
fail(err: any) {
console.error('执行SQL失败:', err)
reject(err)
},
})
})
})
}
/**
* 查询 SQL 语句
*/
static selectSql(sql: string): Promise<any[]> {
return DB.open().then(() => {
return new Promise((resolve, reject) => {
plus.sqlite.selectSql({
name: DB_NAME,
sql,
success(res: any) {
resolve(res)
},
fail(err: any) {
console.error('查询SQL失败:', err)
reject(err)
},
})
})
})
}
/**
* 执行事务
*/
static transaction(sqls: string[]): Promise<void> {
return DB.open().then(() => {
return new Promise((resolve, reject) => {
plus.sqlite.transaction({
name: DB_NAME,
sql: sqls,
success() {
console.log('事务执行成功')
resolve()
},
fail(err: any) {
console.error('事务执行失败:', err)
reject(err)
},
})
})
})
}
// ======================== CRUD 封装方法 ========================
/**
* 插入一条记录
* @param table 表名
* @param data 键值对数据对象
* @example
* await DB.insert('device', { device_name: '设备A', device_type: '压路机', status: 'online' })
*/
static async insert(table: string, data: Record<string, any>): Promise<any> {
const keys = Object.keys(data)
const values = keys.map(k => escapeValue(data[k]))
const sql = `INSERT INTO ${table} (${keys.join(', ')}) VALUES (${values.join(', ')})`
return DB.executeSql(sql)
}
/**
* 更新记录
* @param table 表名
* @param data 要更新的键值对
* @param where 条件子句(不含 WHERE 关键字),为空则更新全部
* @example
* await DB.update('device', { status: 'offline' }, "id = 1")
*/
static async update(table: string, data: Record<string, any>, where?: string): Promise<any> {
const sets = Object.keys(data).map(k => `${k} = ${escapeValue(data[k])}`)
let sql = `UPDATE ${table} SET ${sets.join(', ')}`
if (where)
sql += ` WHERE ${where}`
return DB.executeSql(sql)
}
/**
* 删除记录
* @param table 表名
* @param where 条件子句(不含 WHERE 关键字),为空则删除全部
* @example
* await DB.delete('device', "id = 1")
*/
static async delete(table: string, where?: string): Promise<any> {
let sql = `DELETE FROM ${table}`
if (where)
sql += ` WHERE ${where}`
return DB.executeSql(sql)
}
/**
* 查询记录
* @param table 表名
* @param columns 返回字段,默认 '*'
* @param where 条件子句(不含 WHERE 关键字),可选
* @param orderBy 排序子句(不含 ORDER BY 关键字),可选
* @param limit 返回条数限制,可选
* @example
* const list = await DB.select('device', '*', "status = 'online'", 'id DESC', 10)
*/
static async select(
table: string,
columns: string = '*',
where?: string,
orderBy?: string,
limit?: number,
): Promise<any[]> {
let sql = `SELECT ${columns} FROM ${table}`
if (where)
sql += ` WHERE ${where}`
if (orderBy)
sql += ` ORDER BY ${orderBy}`
if (limit)
sql += ` LIMIT ${limit}`
return DB.selectSql(sql)
}
/**
* 按 ID 查询单条记录(详细查询)
* @param table 表名
* @param id 主键值
* @returns 记录对象或 null
* @example
* const device = await DB.selectById('device', 1)
*/
static async selectById(table: string, id: number | string): Promise<any> {
const result = await DB.select(
table,
'*',
`id = ${typeof id === 'string' ? escapeValue(id) : id}`,
)
return result?.[0] || null
}
/**
* 按字段精确匹配查询(详细查询)
* @param table 表名
* @param field 字段名
* @param value 匹配值
* @param columns 返回字段,默认 '*'
* @example
* const devices = await DB.selectByField('device', 'status', 'online')
*/
static async selectByField(
table: string,
field: string,
value: any,
columns: string = '*',
): Promise<any[]> {
return DB.select(table, columns, `${field} = ${escapeValue(value)}`)
}
/**
* 模糊查询(LIKE %keyword%)
* @param table 表名
* @param field 要模糊匹配的字段名
* @param keyword 关键词
* @param columns 返回字段,默认 '*'
* @example
* // 按设备名称模糊查询
* const devices = await DB.selectLike('device', 'device_name', '徐工')
* // 指定返回字段
* const devices = await DB.selectLike('device', 'device_name', '徐工', 'id, device_name')
*/
static async selectLike(
table: string,
field: string,
keyword: string,
columns: string = '*',
): Promise<any[]> {
const escapedKeyword = keyword.replace(/'/g, '\'\'')
return DB.select(table, columns, `${field} LIKE '%${escapedKeyword}%'`)
}
/**
* 计数查询
* @param table 表名
* @param where 条件子句(不含 WHERE 关键字),可选
* @returns 记录数量
* @example
* const count = await DB.count('device', "status = 'online'")
*/
static async count(table: string, where?: string): Promise<number> {
const result = await DB.select(table, 'COUNT(*) as total', where)
return result?.[0]?.total || 0
}
}
/**
* 初始化数据库表
*/
export async function initTables() {
const sqls = [
// 设备信息表
`CREATE TABLE IF NOT EXISTS device (
id INTEGER PRIMARY KEY AUTOINCREMENT,
device_name TEXT NOT NULL,
device_type TEXT,
status TEXT DEFAULT 'offline',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`,
// 作业记录表
`CREATE TABLE IF NOT EXISTS work_record (
id INTEGER PRIMARY KEY AUTOINCREMENT,
device_id INTEGER,
start_time DATETIME,
end_time DATETIME,
speed REAL,
frequency REAL,
amplitude REAL,
compaction_value REAL,
gps_lat REAL,
gps_lng REAL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (device_id) REFERENCES device(id)
)`,
// RTK 轨迹数据表
`CREATE TABLE IF NOT EXISTS rtk_track (
id INTEGER PRIMARY KEY AUTOINCREMENT,
work_record_id INTEGER,
timestamp DATETIME,
latitude REAL,
longitude REAL,
altitude REAL,
speed REAL,
heading REAL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (work_record_id) REFERENCES work_record(id)
)`,
// 告警记录表
`CREATE TABLE IF NOT EXISTS warning_record (
id INTEGER PRIMARY KEY AUTOINCREMENT,
device_id INTEGER,
warning_type TEXT,
warning_level TEXT,
message TEXT,
is_read INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (device_id) REFERENCES device(id)
)`,
]
try {
await DB.transaction(sqls)
console.log('数据库表初始化成功')
}
catch (err) {
console.error('数据库表初始化失败:', err)
throw err
}
}
// 兼容旧版按需导出
export const openDatabase = DB.open
export const closeDatabase = DB.close
export const executeSql = DB.executeSql
export const selectSql = DB.selectSql
export const transaction = DB.transaction
export default DB